本文描述Android11中通过修改系统PackageInstaller应用支持静默安装App,并启动安装的App。
PackageInstaller是系统中专门负责app安装的App,静默安装逻辑添加到此应用中,应用所在路径 /frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/
package com.android.packageinstaller; import android.annotation.SuppressLint; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.PackageParser; import android.content.pm.ResolveInfo; import android.os.AsyncTask; import android.os.Handler; import android.os.Message; import android.util.ArrayMap; import android.util.Log; import androidx.annotation.NonNull; import com.android.internal.content.PackageHelper; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.List; import android.content.pm.IPackageDeleteObserver; final class SilenceInstallManager { private static final String TAG = "SilenceInstallManager"; private static final int MSG_WHAT_INSTALL_FINISH_SUCCESS = 0; private static final int MSG_WHAT_INSTALL_FINISH_FAIL = 1; private static final int MSG_WHAT_UNINSTALL_COMPLETE = 2; private Context mContext; @SuppressLint("NewApi") private ArrayMapInstallAppInfoMap = new ArrayMap<>(); private static volatile SilenceInstallManager INSTANCE; private SilenceInstallManager(Context context) { mContext = context; } public static SilenceInstallManager getInstance(Context context) { if (null == INSTANCE) { synchronized (SilenceInstallManager.class) { if (null == INSTANCE) { INSTANCE = new SilenceInstallManager(context.getApplicationContext()); } } } return INSTANCE; } @SuppressLint("NewApi") private PackageInstaller.SessionCallback mSessionCallback = new PackageInstaller.SessionCallback() { @Override public void onCreated(int sessionId) { Log.d(TAG, "onCreated---->" + sessionId); } @Override public void onBadgingChanged(int sessionId) { // Log.w(TAG, "SilenceInstallReceiver onBadgingChanged---->" + sessionId); } @Override public void onActiveChanged(int sessionId, boolean active) { // Log.w(TAG, "SilenceInstallReceiver onActiveChanged---->" + sessionId + " active--->" + active); } @Override public void onProgressChanged(int sessionId, float progress) { // Log.w(TAG, "SilenceInstallReceiver onProgressChanged---->" + sessionId + " progress--->" + progress); } @Override public void onFinished(int sessionId, boolean success) { Log.d(TAG, "onFinished---->" + sessionId + " success--->" + success); Message msg = Message.obtain(); msg.what = MSG_WHAT_INSTALL_FINISH_SUCCESS; msg.arg1 = sessionId; msg.obj = success; mHandler.sendMessage(msg); } }; @SuppressLint("HandlerLeak") private Handler mHandler = new Handler() { @Override public void dispatchMessage(@NonNull Message msg) { mContext.getPackageManager().getPackageInstaller().unregisterSessionCallback(mSessionCallback); if (msg.what == MSG_WHAT_INSTALL_FINISH_SUCCESS) { boolean result = (boolean) msg.obj; int sessionId = msg.arg1; InstallAppInfo info = InstallAppInfoMap.remove(sessionId); if (result) { Log.d(TAG, "install success"); if (null != info) { if (info.isLaunch && null != info.info && null != info.info.packageName && !"".equals(info.info.packageName)) { launchApp(info.info.packageName); } File f = new File(info.filePath); if (f.exists()) { f.delete(); } } } else { Log.d(TAG, "install fail"); } } else if (msg.what == MSG_WHAT_INSTALL_FINISH_FAIL) { int sessionId = msg.arg1; if (sessionId != -1) { InstallAppInfoMap.remove(sessionId); } Log.d(TAG, "install fail"); } else if (msg.what == MSG_WHAT_UNINSTALL_COMPLETE) { Log.d(TAG, "uninstall complete--->" + msg.arg1); if (msg.arg1 == PackageManager.DELETE_SUCCEEDED) { Log.d(TAG, "delete succeeded"); } else { Log.d(TAG, "delete fail"); } } } }; public void silenceInstall(String appFilePath, boolean launch) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { mContext.getPackageManager().getPackageInstaller().registerSessionCallback(mSessionCallback); PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( PackageInstaller.SessionParams.MODE_FULL_INSTALL); params.setInstallAsInstantApp(false); params.setInstallReason(PackageManager.INSTALL_REASON_USER); File file = new File(appFilePath); if (!file.exists()) { sendFailMsg(-1); return; } try { PackageParser.PackageLite pkg = PackageParser.parsePackageLite(file, 0); params.setAppPackageName(pkg.packageName); params.setInstallLocation(pkg.installLocation); params.setSize( PackageHelper.calculateInstalledSize(pkg, false, params.abiOverride)); } catch (PackageParser.PackageParserException e) { Log.e(TAG, "Cannot parse package " + file + ". Assuming defaults."); Log.e(TAG, "Cannot calculate installed size " + file + ". Try only apk size."); params.setSize(file.length()); } catch (IOException e) { Log.e(TAG, "Cannot calculate installed size " + file + ". Try only apk size."); params.setSize(file.length()); } try { PackageInfo mPkgInfo = PackageUtil.getPackageInfo(mContext, file, PackageManager.GET_PERMISSIONS); int mSessionId = mContext.getPackageManager().getPackageInstaller().createSession(params); InstallAppInfo installAppInfo = new InstallAppInfo(mSessionId, appFilePath, mPkgInfo, launch); InstallAppInfoMap.put(mSessionId, installAppInfo); InstallingAsyncTask mInstallingTask = new InstallingAsyncTask(mContext, appFilePath, mSessionId); mInstallingTask.execute(); } catch (IOException e) { e.printStackTrace(); sendFailMsg(-1); } } } private void sendFailMsg(int sessionId) { Message msg = Message.obtain(); msg.what = MSG_WHAT_INSTALL_FINISH_FAIL; msg.arg1 = sessionId; mHandler.sendMessage(msg); } @SuppressLint("NewApi") private final class InstallingAsyncTask extends AsyncTask { private Context mContext; private String mAppPath; private int mSessionId; public InstallingAsyncTask(Context context, String appPath, int sessionId) { mContext = context; mAppPath = appPath; mSessionId = sessionId; } @Override protected PackageInstaller.Session doInBackground(Void... params) { PackageInstaller.Session session; try { session = mContext.getPackageManager().getPackageInstaller().openSession(mSessionId); } catch (IOException e) { return null; } session.setStagingProgress(0); try { File file = new File(mAppPath); try (InputStream in = new FileInputStream(file)) { long sizeBytes = file.length(); try (OutputStream out = session .openWrite("PackageInstaller", 0, sizeBytes)) { byte[] buffer = new byte[1024 * 1024]; while (true) { int numRead = in.read(buffer); if (numRead == -1) { session.fsync(out); break; } if (isCancelled()) { session.close(); break; } out.write(buffer, 0, numRead); if (sizeBytes > 0) { float fraction = ((float) numRead / (float) sizeBytes); session.addProgress(fraction); } } } } return session; } catch (IOException | SecurityException e) { Log.e(TAG, "Could not write package", e); session.close(); return null; } } @Override protected void onPostExecute(PackageInstaller.Session session) { if (session != null) { Intent broadcastIntent = new Intent(); PendingIntent pendingIntent = PendingIntent.getBroadcast( mContext, 1, broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT); session.commit(pendingIntent.getIntentSender()); session.close(); Log.d(TAG, "send install PendingIntent----->"); } else { mContext.getPackageManager().getPackageInstaller().abandonSession(mSessionId); sendFailMsg(mSessionId); File f = new File(mAppPath); if (f.exists()) { f.delete(); } Log.e(TAG, "copy fail delete file----->"); } mContext = null; mAppPath = ""; mSessionId = -1; } } private class InstallAppInfo { private int sessionId; private String filePath; private PackageInfo info; private boolean isLaunch; public InstallAppInfo(int sessionId, String filePath, PackageInfo info, boolean isLaunch) { this.sessionId = sessionId; this.filePath = filePath; this.info = info; this.isLaunch = isLaunch; } public int getSessionId() { return sessionId; } public String getFilePath() { return filePath; } public PackageInfo getInfo() { return info; } public boolean isLaunch() { return isLaunch; } } private void launchApp(String appPackageName) { Intent mLaunchIntent = mContext.getPackageManager().getLaunchIntentForPackage(appPackageName); if (mLaunchIntent != null) { List list = mContext.getPackageManager().queryIntentActivities(mLaunchIntent, 0); if (list != null && list.size() > 0) { Log.d(TAG, "launch app--->"); mContext.startActivity(mLaunchIntent); } } } public void silenceUninstall(String packageName) { Log.i(TAG, "silenceUninstall--->" + packageName); PackageDeleteObserver observer = new PackageDeleteObserver(); mContext.getPackageManager().deletePackage(packageName, observer, PackageManager.DELETE_ALL_USERS); } private class PackageDeleteObserver extends IPackageDeleteObserver.Stub { public void packageDeleted(String packageName, int returnCode) { Message msg = mHandler.obtainMessage(MSG_WHAT_UNINSTALL_COMPLETE); msg.arg1 = returnCode; msg.obj = packageName; mHandler.sendMessage(msg); } } }
package com.android.packageinstaller; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.net.Uri; public class SilenceInstallReceiver extends BroadcastReceiver { public static final String SILENCE_INSTALL_APP = "com.android.packageinstaller.ACTION_SILENCE_INSTALL"; public static final String SILENCE_INSTALL_KEY = "silence_install"; public static final String IS_LAUNCH_KEY = "is_launch"; public static final String APP_URI_KEY = "app_uri"; @Override public void onReceive(Context context, Intent intent) { if (SILENCE_INSTALL_APP.equals(intent.getAction())) { Uri uri = intent.getParcelableExtra(APP_URI_KEY); boolean isLaunch = intent.getBooleanExtra(IS_LAUNCH_KEY, false); SilenceInstallManager.getInstance(context).silenceInstall(uri.getPath(), isLaunch); } } }
//********省略代码****** protected void onCreate(@Nullable Bundle savedInstanceState) { //********省略代码****** if (isSessionInstall) { nextActivity.setClass(this, PackageInstallerActivity.class); } else { Uri packageUri = intent.getData(); if (packageUri != null && packageUri.getScheme().equals( ContentResolver.SCHEME_CONTENT)) { // [IMPORTANT] This path is deprecated, but should still work. Only necessary // features should be added. // Copy file to prevent it from being changed underneath this process nextActivity.setClass(this, InstallStaging.class); } else if (packageUri != null && packageUri.getScheme().equals( PackageInstallerActivity.SCHEME_PACKAGE)) { nextActivity.setClass(this, PackageInstallerActivity.class); } else { Intent result = new Intent(); result.putExtra(Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_FAILED_INVALID_URI); setResult(RESULT_FIRST_USER, result); nextActivity = null; } } //安装请求中如果带静默安装标识,执行静默安装操作 if (intent.getBooleanExtra(SilenceInstallReceiver.SILENCE_INSTALL_KEY, false)) { StagingAsyncAppTask mStagingTask = new StagingAsyncAppTask(intent.getBooleanExtra(SilenceInstallReceiver.IS_LAUNCH_KEY, false)); mStagingTask.execute(getIntent().getData()); return; } if (nextActivity != null) { startActivity(nextActivity); } finish(); } @SuppressLint("NewApi") private final class StagingAsyncAppTask extends AsyncTask{ private boolean mIsLaunch; public StagingAsyncAppTask(boolean isLaunch){ mIsLaunch = isLaunch; } @Override protected File doInBackground(Uri... params) { Log.d(LOG_TAG, "copy file from user app start"); if (params == null || params.length <= 0) { return null; } Uri packageUri = params[0]; try (InputStream in = getContentResolver().openInputStream(packageUri)) { // Despite the comments in ContentResolver#openInputStream the returned stream can // be null. if (in == null) { return null; } File mStagedFile = TemporaryFileManager.getStagedFile(InstallStart.this); try (OutputStream out = new FileOutputStream(mStagedFile)) { byte[] buffer = new byte[1024 * 1024]; int bytesRead; while ((bytesRead = in.read(buffer)) >= 0) { // Be nice and respond to a cancellation out.write(buffer, 0, bytesRead); } } return mStagedFile; } catch (IOException | SecurityException | IllegalStateException e) { Log.w(LOG_TAG, "Error staging apk from content URI", e); } return null; } @Override protected void onPostExecute(File installFile) { if (null != installFile) { // Now start the installation again from a file Log.d(LOG_TAG, "copy file from user app finish"); Intent installIntent = new Intent(SilenceInstallReceiver.SILENCE_INSTALL_APP); installIntent.putExtra(SilenceInstallReceiver.APP_URI_KEY, Uri.fromFile(installFile)); installIntent.putExtra(SilenceInstallReceiver.IS_LAUNCH_KEY, mIsLaunch); installIntent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); installIntent.setPackage("com.android.packageinstaller"); sendBroadcast(installIntent); Log.d(LOG_TAG, "send to install"); } else { Log.d(LOG_TAG, "copy file from user app fail"); } finish(); } } //********省略代码******
//********省略代码****** public void onCreate(Bundle icicle){ //********省略代码****** //添加静默卸载逻辑 if (intent.getBooleanExtra(SilenceInstallReceiver.SILENCE_INSTALL_KEY, false)) { SilenceInstallManager.getInstance(this).silenceUninstall(mPackageName); return; } showConfirmationDialog(); } //********省略代码******
//********省略代码****** public class PackageInstallerApplication extends Application { @Override public void onCreate() { super.onCreate(); PackageItemInfo.forceSafeLabels(); //添加管理类初始化 SilenceInstallManager.getInstance(this); } } //********省略代码******
//********省略代码****** //添加后台启动activity权限//********省略代码****** //********省略代码******
Background activity start [callingPackage: com.android.packageinstaller; callingUid: 10069; isCallingUidForeground: false; callingUidHasAnyVisibleWindow: false; callingUidProcState: CACHED_EMPTY; isCallingUidPersistentSystemProcess: false; realCallingUid: 10069; isRealCallingUidForeground: false; realCallingUidHasAnyVisibleWindow: false; realCallingUidProcState: CACHED_EMPTY; isRealCallingUidPersistentSystemProcess: false; originatingPendingIntent: null; isBgStartWhitelisted: false; intent: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 pkg=com.qiyi.video cmp=com.qiyi.video/.WelcomeActivity }; callerApp: ProcessRecord{ba2b2ca 2471:com.android.packageinstaller/u0a69}] Abort background activity starts from 10069
当开发的App切换到后台后,就无法进行应用安装请求,ActivityStarter.java中的shouldAbortBackgroundActivityStart()方法在判断是否能够启动activity,在这里添加修改逻辑处理,路径为 /frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java;
//********省略代码****** boolean shouldAbortBackgroundActivityStart(int callingUid, int callingPid, final String callingPackage, int realCallingUid, int realCallingPid, WindowProcessController callerApp, PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart, Intent intent) { //********省略代码****** // don't abort if the callingUid has SYSTEM_ALERT_WINDOW permission if (mService.hasSystemAlertWindowPermission(callingUid, callingPid, callingPackage)) { Slog.w(TAG, "Background activity start for " + callingPackage + " allowed because SYSTEM_ALERT_WINDOW permission is granted."); return false; } //添加判断逻辑,通过allowed_Background标识决定是能够启动 if (intent.getBooleanExtra("allowed_Background", false)) { Slog.w(TAG, "allowed_Background."); return false; } //********省略代码****** } //********省略代码******
private void installTest() { String appPath = getExternalFilesDir(null).getAbsolutePath() + File.separator + "aiqiyi.apk"; File appFile = new File(appPath); if (!appFile.exists()) { showToast("请在" + getExternalFilesDir(null).getAbsolutePath() + File.separator + "目录中放置升级文件"); return; } Uri uri = FileProvider.getUriForFile(getApplicationContext(), getPackageName() + ".file_provider", appFile); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { Intent installApkIntent = new Intent(); installApkIntent.setAction(Intent.ACTION_VIEW); installApkIntent.addCategory(Intent.CATEGORY_DEFAULT); installApkIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); installApkIntent.setDataAndType(uri, "application/vnd.android.package-archive"); installApkIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); //设置静默安装标识 installApkIntent.putExtra("silence_install", true); //设置安装完成是否启动标识 installApkIntent.putExtra("is_launch", true); //设置后台中启动activity标识 installApkIntent.putExtra("allowed_Background", true); if (getPackageManager().queryIntentActivities(installApkIntent, 0).size() > 0) { startActivity(installApkIntent); } } }
public boolean requestSilenceUninstall(Context context, String packageName) { if (null == packageName || "".equals(packageName)) { return false; } try { PackageInfo packinfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS); String[] list = packinfo.requestedPermissions; boolean hasPermission = false; if (null != list) { for (int i = 0; i < list.length; i++) { if (Manifest.permission.QUERY_ALL_PACKAGES.equals(list[i])) { hasPermission = true; break; } } } if (!hasPermission) { throw new RuntimeException("need permission " + Manifest.permission.QUERY_ALL_PACKAGES); } } catch (PackageManager.NameNotFoundException e) { throw new RuntimeException(e); } if (!checkExistForApp(context, packageName)) { return false; } Intent intent = new Intent(Intent.ACTION_DELETE); intent.setData(Uri.parse("package:" + packageName)); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra("allowed_Background", true); intent.putExtra("silence_install", true); context.startActivity(intent); return true; }