Android11.0系统中实现静默安装并启动App以及静默卸载
作者:mmseoamin日期:2024-01-25

Android11.0系统中实现静默安装并启动App以及静默卸载

  • 静默安装并启动App以及静默卸载
    • 修改PackageInstaller
    • 第三方App请求静默安装及卸载

      静默安装并启动App以及静默卸载

      本文描述Android11中通过修改系统PackageInstaller应用支持静默安装App,并启动安装的App。

      修改PackageInstaller

      PackageInstaller是系统中专门负责app安装的App,静默安装逻辑添加到此应用中,应用所在路径 /frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/

      1. 添加SilenceInstallManager,路径为 /frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/SilenceInstallManager.java;
      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 ArrayMap InstallAppInfoMap = 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);
              }
          }
      }
      
      1. 添加SilenceInstallReceiver,路径为 /frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/SilenceInstallReceiver.java;
      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);
              }
          }
      }
      
      1. InstallStart是PackageInstaller程序安装app的入口activity,修改InstallStart,添加静默安装逻辑分支,路径为 /frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java;
      //********省略代码******
        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();
              }
          }
      //********省略代码******
      
      1. UninstallerActivity是PackageInstaller程序卸载app的入口activity,修改UninstallerActivity,添加静默卸载逻辑分支,路径为 /frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java;
      //********省略代码******
      public void onCreate(Bundle icicle){
               //********省略代码******
              
              //添加静默卸载逻辑
              if (intent.getBooleanExtra(SilenceInstallReceiver.SILENCE_INSTALL_KEY, false)) {
                  SilenceInstallManager.getInstance(this).silenceUninstall(mPackageName);
                  return;
              }
              showConfirmationDialog();
      }
      //********省略代码******
      
      1. 修改PackageInstallerApplication,路径为 /frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerApplication.java;
      //********省略代码******
      public class PackageInstallerApplication extends Application {
          @Override
          public void onCreate() {
              super.onCreate();
              PackageItemInfo.forceSafeLabels();
      		//添加管理类初始化
              SilenceInstallManager.getInstance(this);
          }
      }
      //********省略代码******
      
      1. 修改AndroidManifest.xml,路径为 /frameworks/base/packages/PackageInstaller/AndroidManifest.xml;
      //********省略代码******
      //添加后台启动activity权限
      
      //********省略代码******
      
                  
                      
                  
              
      //********省略代码******
      
      1. Android10开始,系统限制在没有用户交互的情况下在后台启动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;
              }
          
             //********省略代码******
      }
      //********省略代码******
      

      第三方App请求静默安装及卸载

      1. 在开发的app中使用下面方法请求安装
          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);
                  }
              }
          }
      
      1. 在开发的app中使用下面方法请求卸载
          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;
          }