我们再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;
}