我们再PKMS汇总拦截adb 安装的应用,在分析PKMS的时候我们也知道,在installPackageAsUser有如下代码,代表是adb安装的。
if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) { installFlags |= PackageManager.INSTALL_FROM_ADB; }
所以我们可以在startCopy函数中做手脚
final boolean startCopy() { boolean res; try { if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ": " + this); if (++mRetries > MAX_RETRIES) {//超过4次 Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up"); mHandler.sendEmptyMessage(MCS_GIVE_UP); handleServiceError(); return false; } else { handleStartCopy(); res = true; } } catch (RemoteException e) { if (DEBUG_INSTALL) Slog.i(TAG, "Posting install MCS_RECONNECT"); mHandler.sendEmptyMessage(MCS_RECONNECT); res = false; } //add if (mApkPath == null) { handleReturnCode(); return res; } //是adb 安装的 if ((((InstallParams)this).installFlags & PackageManager.INSTALL_FROM_ADB) != 0) { mLock = new ReentrantLock(); mCondition = mLock.newCondition(); class AlertDialogThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); showAlertDialog(); Looper.loop(); } } AlertDialogThread alertDialogThread = new AlertDialogThread(); alertDialogThread.start(); mLock.lock(); try { mCondition.await(); } catch (InterruptedException e) { } mLock.unlock(); /*if (!mContinueToInstall) { res = false; }*/ } handleReturnCode(); return res; }
上面代码就是增加了一个显示的dialog,点击继续才会继续安装,这里我们有一个当点击取消或者5秒没有点击就不继续安装,这里返回值res不能为false。为什么呢?我们来看下调用startCopy的地方,当startCopy返回true才会把mPendingInstalls中的第一项删除,否则就会不断调用startCopy直到超过4次才会删除。所以我们上面不继续安装的话也不能返回一个false。
else if (mPendingInstalls.size() > 0) { HandlerParams params = mPendingInstalls.get(0); if (params != null) { if (params.startCopy()) { // We are done... look for more work or to // go idle. if (DEBUG_SD_INSTALL) Log.i(TAG, "Checking for more work or unbind..."); // Delete pending install if (mPendingInstalls.size() > 0) { mPendingInstalls.remove(0); } if (mPendingInstalls.size() == 0) { if (mBound) { if (DEBUG_SD_INSTALL) Log.i(TAG, "Posting delayed MCS_UNBIND"); removeMessages(MCS_UNBIND); Message ubmsg = obtainMessage(MCS_UNBIND); // Unbind after a little delay, to avoid // continual thrashing. sendMessageDelayed(ubmsg, 10000); } } else { // There are more pending requests in queue. // Just post MCS_BOUND message to trigger processing // of next pending install. if (DEBUG_SD_INSTALL) Log.i(TAG, "Posting MCS_BOUND for next work"); mHandler.sendEmptyMessage(MCS_BOUND); } } }
再看看在HandlerParams 中增加的代码,大部分是显示dialog相关。
private abstract class HandlerParams { private static final int MAX_RETRIES = 4; /** * Number of times startCopy() has been attempted and had a non-fatal * error. */ private int mRetries = 0; /** User handle for the user requesting the information or installation. */ private final UserHandle mUser; HandlerParams(UserHandle user) { mUser = user; mApkPath = null;//add } UserHandle getUser() { return mUser; } private Builder mAlertDialogBuilder; private AlertDialog mAlertDialog; private CountDownTimer mCountDownTimer; protected String mApkPath; protected boolean mContinueToInstall = true;//注意这个很重要 private void cancelAlertDialog(Dialog dialog) { mLock.lock(); if (mHandler.mPendingInstalls.size() != 0) { InstallParams params = (InstallParams)mHandler.mPendingInstalls.get(0); try { if (params.observer != null) { params.observer.onPackageInstalled("", INSTALL_FAILED_USER_CANCELLED, null, null); } } catch (RemoteException re) { } } mHandler.mPendingInstalls.clear(); mHandler.disconnectService(); mContinueToInstall = false;//没有点击继续安装 mCondition.signal(); mLock.unlock(); dialog.dismiss(); } private Drawable getApkIcon(Context context, String apkPath) { PackageManager pm = context.getPackageManager(); PackageInfo info = pm.getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES); if (info != null) { ApplicationInfo appInfo = info.applicationInfo; appInfo.sourceDir = apkPath; appInfo.publicSourceDir = apkPath; try { return appInfo.loadIcon(pm); } catch (OutOfMemoryError e) { Log.e("getApkIcon", e.toString()); } } return null; } private CharSequence getAppLabel(Context context, String apkPath) { PackageManager pm = context.getPackageManager(); PackageInfo info = pm.getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES); if (info != null) { ApplicationInfo appInfo = info.applicationInfo; appInfo.sourceDir = apkPath; appInfo.publicSourceDir = apkPath; return appInfo.loadLabel(pm); } return null; } private void showAlertDialog() { final Context settingsContext = new ContextThemeWrapper(mContext, com.android.internal.R.style.Theme_DeviceDefault_Settings); mAlertDialogBuilder = new AlertDialog.Builder(settingsContext); mAlertDialogBuilder.setTitle(getAppLabel(mContext, mApkPath)); mAlertDialogBuilder.setMessage(R.string.install_hint); mAlertDialogBuilder.setIcon(getApkIcon(mContext, mApkPath)); mAlertDialogBuilder.setNegativeButton(R.string.adb_install_cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { mCountDownTimer.cancel(); cancelAlertDialog(mAlertDialog); } }); mAlertDialogBuilder.setPositiveButton(R.string.adb_install_continue_to_install, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { mCountDownTimer.cancel(); mLock.lock(); mContinueToInstall = true;//点击继续安装 mCondition.signal(); mLock.unlock(); mAlertDialog.dismiss(); } }); mAlertDialog = mAlertDialogBuilder.create(); Window alertDialogWindow = mAlertDialog.getWindow(); alertDialogWindow.setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);// TYPE_SYSTEM_DIALOG mAlertDialog.setCanceledOnTouchOutside(false); mAlertDialog.show(); alertDialogWindow.setGravity(Gravity.BOTTOM); final Button negativeButton = mAlertDialog.getButton(AlertDialog.BUTTON_NEGATIVE); int intervalMs = 1000; int timeLeftMs = 5000; final String cancel = mContext.getString(R.string.adb_install_cancel); mCountDownTimer = new CountDownTimer(timeLeftMs, intervalMs) { public void onTick(long millisUntilFinished) { negativeButton.setText(cancel + "(" + millisUntilFinished / 1000 + ")"); } public void onFinish() { cancelAlertDialog(mAlertDialog); } }; mCountDownTimer.start(); }
其中有一个mApkPath我们接下来看在哪赋值。
就是handleStartCopy函数最后在copyApk函数(就是在copy apk到data/app目录下新建的临时目录)之后,mApkPath就是其目录下的base.apk
ret = args.copyApk(mContainerService, true); mApkPath = args.getCodePath() + "/base.apk";
我们再看下有了这个apkPath就可以调用PackageManager的getPackageArchiveInfo来获取PackageInfo,然后解析出ICon和Label等。
PackageInfo info = pm.getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES);
我们来看看这个函数,也是调用PackageParser来解析apk。
public PackageInfo getPackageArchiveInfo(String archiveFilePath, int flags) { final PackageParser parser = new PackageParser(); final File apkFile = new File(archiveFilePath); try { PackageParser.Package pkg = parser.parseMonolithicPackage(apkFile, 0); if ((flags & GET_SIGNATURES) != 0) { parser.collectCertificates(pkg, 0); parser.collectManifestDigest(pkg); } PackageUserState state = new PackageUserState(); return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, state); } catch (PackageParserException e) { return null; } }
我们继续分析handleReturnCode函数,当mContinueToInstall为true就调用processPendingInstall函数继续装载应用,当为false,就调用doPreInstall。联系到之前mContinueToInstall默认为true,因为不是adb安装的话mContinueToInstall就应该默认为true,要不然不能继续装载应用了。
@Override void handleReturnCode() { // If mArgs is null, then MCS couldn't be reached. When it // reconnects, it will try again to install. At that point, this // will succeed. if (mArgs != null) { if (mContinueToInstall) { processPendingInstall(mArgs, mRet); } else { mArgs.doPreInstall(PackageManager.INSTALL_FAILED_MISSING_FEATURE); } } }
我们再来看doPreInstall函数,当状态不是success,就删除一些临时文件。
int doPreInstall(int status) { if (status != PackageManager.INSTALL_SUCCEEDED) { cleanUp(); } return status; }