【項目】Android 預置第三方應用可卸載功能的實現

原生Android 的狀況是:

手機會預置一些第三方APP ,用戶不可刪除。

現在實現用戶可刪除的預置應用的功能

1.修改預置應用安裝路徑:

1.1 /system 下創建/third_app 文件夾

       1.把預留應用放在system/third-app下;

       2.第一次開機 ,PKMS初始化掃描data/app之前,這些應用源文件從 /system/third-app  copy到 data/app下;

       恢復出廠設置僅僅就格式化/data 分區,不會格式化/system 分區,回覆出廠設置後第一次就直接copy到/system/app下,因此恢復出廠設置後仍然有效

 1.1.1 修改預置第三放APP Android.mk

include $(CLEAR_VARS)
LOCAL_MODULE := SogouInput
LOCAL_SRC_FILES := $(LOCAL_MODULE).apk
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
LOCAL_CERTIFICATE := PRESIGNED
LOCAL_DEX_PREOPT := false
LOCAL_MODULE_PATH := $(TARGET_OUT)/third_app
include $(BUILD_PREBUILT)

  1.1.2 添加copy 代碼

    //install the third apps when system is first boot
    private void installThirdApps(){
        //the source directory not exists
        File storeDir = new File("/system/third_app");
        if(!storeDir.exists()){
            Log.e(TAG,"/system/third_app is not exist");
            return;
        }
 
        //get the apk files in /system/third_app
        String apkFilesNames[] = storeDir.list();
        if(apkFilesNames == null){
            Log.e(TAG,"apk file name is null");
            return;
        }
 
        //copy the apk files to /data/app
        boolean installSucc = false;
        for(int i = 0; i < apkFilesNames.length; i++){
            //Uri srcFileUri = Uri.parse(storeDir+"/"+apkFilesNames[i]);
            File srcFile = new File("/system/third_app",apkFilesNames[i]);
            Log.e(TAG,"srcFile="+srcFile);
            File destFile = new File("/data/app",apkFilesNames[i]);
            Log.e(TAG,"destFile="+destFile.toString());
            boolean installResult = copyThirdApps(srcFile,destFile);
            if(!installResult){
                Log.d(TAG,"install failed");
                return;
            }
        }
    }
 
    /**
     * File copy function.
     * It will be used when installThirdApps
     * @param srcFile  just like '/system/third_app/***.apk'
     * @param destDir  just like '/data/app/***.apk'
     * @return
     */
    private boolean copyThirdApps(File srcFile, File destDir) {
        //do some check actions
        if (srcFile == null || destDir == null || !srcFile.exists()) {
            Log.e(TAG, "invalid arguments for movePreinstallApkFile()");
            Log.e(TAG, "move " + srcFile + " to " + destDir + " failed");
            return false;
        }
 
        //create new file
        try{
            destDir.createNewFile();
        }catch(Exception e){
            Log.e(TAG, "create file faild! due to:" + e);
            return false;
        }
 
        //set permission
        try{
            Runtime.getRuntime().exec("chmod 644 "+destDir.getAbsolutePath());
        }catch(Exception e){
            Log.e(TAG, "chmod file faild! due to:"+e);
        }
 
        //do copy
        try{
            boolean ret = FileUtils.copyFile(srcFile,destDir);
            if(!ret){
                Log.e(TAG,"copy file faild!");
                return false;
            }
        }catch(Exception e){
            Log.e(TAG, "copy file faild! due to:"+e);
        }
 
        return true && destDir.exists();
    }

1.1.3 PKMS 中插入copy代碼

            if (!mOnlyCore) {
                EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
                        SystemClock.uptimeMillis());
                if(isFirstBoot()){//判斷第一次開機
                    Log.i(TAG, "It's first boot, install the third apps");
                    installThirdApps();//安裝三方應用(copy到data/app下)
                }                        
                scanDirLI(mAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0);
 
                scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK,
                        scanFlags | SCAN_REQUIRE_KNOWN, 0);

在PKMS的構造函數,開始處理非系統應用的時候,但是一定要在掃描data/app之前,這樣才能後面掃描到data/app這些複製進去的app,纔會第一次開機安裝成功

 

 

=====================功能優化=========================

以上方法會有如下問題:

1.調用接口安裝的,可能Launcher啓動後還沒安裝完。

2.而copy到data/app下又會有兩份apk問題。

現在提新的方法:
     1.就是放在system/third_app下,開機的時候直接掃描這個目錄;

     2.我們在data/system下建一個xml文件,當應用卸載的時候,我們再xml上記錄該應用被卸載了,當再次開機的時候,掃描到該應用就直接跳過;

     3.恢復出廠設置時data目錄重置,xml文件被刪除。system/third_app又會被重新掃描;

     4.所有的apk就全部安裝上了,而當我們卸載時,因爲system/third_app的權限問題,PKMS刪除不了,正好恢復出廠設置的時候可以重新恢復;
 

 

     簡單的邏輯就是把/system/third_app 下的APP專門由PKMS.mVendorSettings.mVendorPackages 進行管理(與PKMS.mSettings.mPackages 對應),但是,PKMS.mSettings.mPackages仍然包含PKMS.mVendorSettings.mVendorPackages中的PKG

 

1.增加“services/core/java/com/android/server/pm/VendorSettings.java”



package com.android.server.pm;

... ...
final class VendorSettings {
... ...
    private final File mSystemDir;                         //"/data/system"
    private final File mVendorSettingsFilename;            //"/data/system/custom-packages.xml"
    private final File mVendorBackupSettingsFilename;      //“/data/system/custom-packages-backup.xml”
    final HashMap<String, VendorPackageSettings> mVendorPackages =
            new HashMap<String, VendorPackageSettings>();

    VendorSettings() {
        this(Environment.getDataDirectory());
    }



    VendorSettings(File dataDir) {
 
        mSystemDir = new File(dataDir, "system");;
        mSystemDir.mkdirs();

        FileUtils.setPermissions(mSystemDir.toString(),
                FileUtils.S_IRWXU|FileUtils.S_IRWXG
                |FileUtils.S_IROTH|FileUtils.S_IXOTH,
                -1, -1);
        //=1= : 創建"/data/system/custom-packages.xml  和 其備份文件,類似/data/system/package-list.xml"
        mVendorSettingsFilename = new File(mSystemDir, "custom-packages.xml");
        mVendorBackupSettingsFilename = new File(mSystemDir, "custom-packages-backup.xml");
    }


    // =2= : mVendorPackages 中增加新的pkg
    void insertPackage(String packageName, boolean installStatus) {

        VendorPackageSettings vps = mVendorPackages.get(packageName);
        if (vps != null) {
            vps.setIntallStatus(installStatus);
        } else {
            vps = new VendorPackageSettings(packageName, installStatus);
            mVendorPackages.put(packageName, vps);
        }
    }



    // =3= : 
    void setPackageStatus(String packageName, boolean installStatus) {
        VendorPackageSettings vps = mVendorPackages.get(packageName);
    ... ...
            vps.setIntallStatus(installStatus);
    ... ...
    }




    // =4= : mVendorPackages 中減去的pkg
    void removePackage(String packageName) {
    ... ...
            mVendorPackages.remove(packageName);
    ... ...
    }


    void readLPw() {

      // 1.讀取/system/etc/custom-packages.xml ,將其中的VendorPackageSettings 信息 讀取在PKMS.mVendorSettings.mVendorPackages數組中.

    }

    void writeLPr() {

      // 1.將PKMS.mVendorSettings.mVendorPackages 同步到/system/etc/custom-packages.xml 中.

    }

}

 

2.增加/services/core/java/com/android/server/pm/VendorPackageSettings.java


package com.android.server.pm;

final class VendorPackageSettings {

    final String mPackageName;
    boolean mIntallStatus = true;

    VendorPackageSettings(String packageName) {
        this.mPackageName = packageName;
    }

    VendorPackageSettings(String packageName, boolean intallStatus) {
        this.mPackageName = packageName;
        this.mIntallStatus = intallStatus;
    }

    boolean getIntallStatus() {
        return mIntallStatus;
    }

    void setIntallStatus(boolean mIntallStatus) {
        this.mIntallStatus = mIntallStatus;
    }

    String getPackageName() {
        return mPackageName;
    }
}

3.PackageManagerService 中的修改:

    3.1.  增加PKMS.mVendorSettings 成員變量 和PKMS.mVendorPackages 成員變量

public class PackageManagerService extends IPackageManager.Stub {

... ...

+    final HashMap<String, PackageParser.Package> mVendorPackages =
+        new HashMap<String, PackageParser.Package>();
... ...
     final Settings mSettings;
... ...
+    final VendorSettings mVendorSettings;
     boolean mRestoredSettings;

   3.2.  PKMS 構造函數

 public class PackageManagerService extends IPackageManager.Stub {
 ... ...
      mSettings = new Settings(mPackages);      
      
      //(1): 初始化PKMS.mVendorSettings  
 +    mVendorSettings = new VendorSettings();
 ... ...
      
      mRestoredSettings = mSettings.readLPw(this, sUserManager.getUsers(false),
                    mSdkVersion, mOnlyCore);
      
      //(2): 調用VendorSettings.readLPw,初始化mVendorSettings.mVendorPackages
 +    mVendorSettings.readLPw();


 ... ...
 ... ...
      scanDirLI( "/vendor/overlay" , ...);
      scanDirLI( "/system/framework" , ...);
      scanDirLI( "/system/priv-app" , ...);
      scanDirLI( "/system/app" , ...);

      //(3): PKMS 開始掃描/system/app等APP 時,同樣調用scanDirLI掃描/system/third_app下的app
+     final File operatorAppDir = new File("/system/third_app");
+
+     //Add PARSE_IS_VENDOR for operator apps
+     final File[] operatorAppFiles = operatorAppDir.listFiles();
+     for (File file : operatorAppFiles) {
+          scanDirLI(file, PackageParser.PARSE_IS_VENDOR, scanFlags, 0);
+     }

 ... ...

      //(4):1. PKMS.mVendorSettings.mVendorPackages包含解析custom-packages.xml 中記錄的所有third_app信息 ;
      //(4):2. PKMS.mVendorPackages 包含所有scanDirLI() 解析/system/third_app 新出來的PackageParser.Package 對象
      //(4):3. PKMS.mVendorPackages如果沒有PKMS.mVendorSettings.mVendorPackages中的記錄,說明現實中的third_app 比 custom-packages.xml 中的少,需要跟新custom-packages.xml
  
  +            Iterator<VendorPackageSettings> vpsit = mVendorSettings.mVendorPackages.values().iterator();
+            while (vpsit.hasNext()) {
+                VendorPackageSettings vps = vpsit.next();
+                final PackageParser.Package scannedVendorPkg = mVendorPackages.get(vps.getPackageName());
+                if (scannedVendorPkg == null) {
+                    vpsit.remove();
+                    Slog.w(TAG, "Vendor package: " + vps.getPackageName()
+                        + " has been removed from system");
+                }
+            }
  
 ... ...

        mSettings.writeLPr();
       //(5): 1.PKMS 初始化完成,調用 mSettings 和 mVendorSettings 的writeLPr()函數,分別將mSettings.mPackages 同步到package-list.xml  和 將mVendorSettings.mVendorPackages 同步到custom-packages.xml
+      mVendorSettings.writeLPr();


 }

4. 掃描/system/third_app 相關的修改

  4.1 增加PackageParser.PARSE_IS_VENDOR 這個flags

    public final static int PARSE_IS_VENDOR = 1<<10;

   代表 pkg  是  /system/third_app 

  4.2 scanPackageLI ()  的修改

 

 private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,long currentTime, UserHandle user) throws PackageManagerException {
 
  ... ...
  PackageParser pp = new PackageParser();
  ... ...

+  //If the newly installed package is vendor app,
+  //add or update it in vendor settings
+  if ((parseFlags & PackageParser.PARSE_IS_VENDOR) != 0) {
+      mVendorPackages.put(pkg.packageName,true);
+  }
+

+  //Check whether we should skip the scan of current package
+  //We should only check vendor packages
+  if ((parseFlags & PackageParser.PARSE_IS_VENDOR) != 0) {
+      VendorPackageSettings vps = + 
+         mVendorSettings.mVendorPackages.get(pkg.packageName);
+      if (vps != null) {
+          if (!vps.getIntallStatus()) {
+              //Skip the vendor package that was uninstalled by user
+              Log.i(TAG, "Package "  + vps.getPackageName()+ " skipped due to + + 
+  uninstalled");
+              return null;
+           }
+   
+       }
+   }

4.2.1 在scanDirLI ()  中,將有 PackageParser.PARSE_IS_VENDOR 標誌位的放入PKMS.mVendorSettings.mVendorPakages中;

4.2.2 scanDirLI 正在掃描的third_pkg 也在mVendorPackages中,但是“uninstalled”狀態,就返回return,結束安裝 (/system/third_app 下的資源文件不可能被刪除,當一個third_app 被刪除時,VendorPackageSettings.mIntallStatus = false 代表被已經刪除,放在 (VendorPackageSettings) PKMS.mVendorSettings.mPackages[i].mIntallStatus :

   1.scanDirLI 在掃描/system/third_app/deleted_third_app.apk 時,就直接return null ,此時,而PKMS.mPackage 的更新是在scanPackageDirtyLI中,所以PKMS.mPackages<PackageParser.package> 中是沒有deleted_third_app.apk對應的PackageParser.package對象的,所以package-list.xml 中不會有記錄;

   2.

4.3 scanPackageDirtyLI () 

private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, int parseFlags, ... ) {

... ...


 synchronized (mPackages) {

 // (1) : scanPackageDirtyLI () 中開始進行PKMS.mSettings.mPackage<PackageSettings> 的更新
 // Add the new setting to mSettings
 mSettings.insertPackageSettingLPw(pkgSetting, pkg);

 //(2) : PKMS.mPackages 的更新
 // Add the new setting to mPackages
 mPackages.put(pkg.applicationInfo.packageName, pkg);

 //(3) : 清楚PKMS.mSettings.mPackages 多餘項
 // Make sure we don't accidentally delete its data.
 final Iterator<PackageCleanItem> iter = mSettings.mPackagesToBeCleaned.iterator();
     while (iter.hasNext()) {
         PackageCleanItem item = iter.next();
         if (pkgName.equals(item.packageName)) {
              iter.remove();
         }
      }
   }

... ...
 //(4) : 更新PKMS.mVendorSettings.mVendorPackage[i].mInstallStatus = true ,表示已經安裝
 +   //If the newly installed package is vendor app,
 +   //add or update it in vendor settings
 +   if ((parseFlags & PackageParser.PARSE_IS_VENDOR) != 0) {
 +       mVendorSettings.insertPackage(pkg.packageName,true);
 +   }

... ...

}

 在PKMS.mPackages  和 PKMS.mSettings.mPackages  進行同步後,更新PKMS.mVendorSettings.mVendorPackage[i].mInstallStatus = true ,表示已經安裝狀態

5. 刪除third_app 的處理:

  在removePackageDataLI()  中處理:

 private void removePackageDataLI(PackageSetting ps,
            int[] allUserHandles, boolean[] perUserInstalled,
            PackageRemovedInfo outInfo, int flags, boolean writeSettings) {
 ... ...
     final PackageSetting deletedPs;
 +   final VendorPackageSettings delVps;
 ... ...

     deletedPs = mSettings.mPackages.get(packageName);
 +   delVps = mVendorSettings.mVendorPackages.get(packageName);

 ... ...

     mHandler.post(new Runnable() {
         @Override
         public void run() {
             // This has to happen with no lock held.
             killApplication(deletedPs.name, deletedPs.appId,
                 KILL_APP_REASON_GIDS_CHANGED);
         }
     });

 ... ...

+    if (delVps != null) {
+        //If the deleted package is vendor package
+        //remove it from vendor settins
+        mVendorSettings.setPackageStatus(packageName, false);
+        mVendorSettings.writeLPr();
+     }
}

 注意:PKMS.mPackages中本身是包含需要刪除的deleting_third_app.apk 的,所以沒有必要專門爲PKMS.mVendorPackages 做特殊的刪除流程!

           在刪除流程中,僅僅只需要設置PKMS.mVendorPackages[i].mInstalStatus = false;

=====================================================

***知識點***:

1. scanDirLI(file, PackageParser.PARSE_IS_VENDOR, scanFlags, 0) :

    pkg被scanDirLI掃描時 加上的PackageParser.PARSE_IS_VENDOR的flag,system 的app 也是這個時候加上的flag;

    注意:scanDirLI是掃描PKG 最開始的函數,scanDirLI (... , PackageParser.PARSE_IS_VENDOR, ...) 此時添加了PackageParser.PARSE_IS_VENDOR 這個flag ,那麼後續掃描都有在PackageParser.PARSE_IS_VENDOR 這個flag 的基礎上繼續添加新的flag。

2.這幾個路徑下是system_app

            //  /vendor/overlay
            scanDirLI(vendorOverlayDir, PackageParser.PARSE_IS_SYSTEM
                    | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags | SCAN_TRUSTED_OVERLAY, 0);

            //  /system/framework
            scanDirLI(frameworkDir, PackageParser.PARSE_IS_SYSTEM
                    | PackageParser.PARSE_IS_SYSTEM_DIR
                    | PackageParser.PARSE_IS_PRIVILEGED,
                    scanFlags | SCAN_NO_DEX, 0);

            //  /system/priv-app
            scanDirLI(privilegedAppDir, PackageParser.PARSE_IS_SYSTEM
                    | PackageParser.PARSE_IS_SYSTEM_DIR
                    | PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0);

            //  /system/app
            scanDirLI(systemAppDir, PackageParser.PARSE_IS_SYSTEM
                    | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);

            //   /vendor/app
            scanDirLI(vendorAppDir, PackageParser.PARSE_IS_SYSTEM
                    | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);


            scanDirLI(oemAppDir, PackageParser.PARSE_IS_SYSTEM
                    | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);

1./vendor/overlay

2./system/framework

3./system/priv-app

4./system/app

5./vendor/app

以上目錄下的app 都是flags = system_app

3. 幾個存儲信息的成員變量的比較:

     3.1   PKMS.mPackages<PackageParser.package>

             PKMS.mVendorPackages<PackageParser.package>

     3.2   PKMS.mSettings.mPackages<PackageSettings>

             PKMS.mVendorSettings.mVendorPackages<VendorPackageSettings>

     PKMS.mPackage是scanDirLI ()  掃描了XXXX.apk 後生成的 成員是PackageParser.package 的數組,是當前系統中app 的信息;

    PKMS.mSettings.mPackages是Settings 調用readPlw 讀取/data/system/package-list.xml 後生成的文件解析信息;

    PKMS 初始化最後會將PKMS.mPackage 同步到PKMS.mSettings.mPackages 中並最後寫進package-list.xml 文件中;

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章