Apps on sdcard: Add new broadcasts

Add new broadcasts ACTION_MEDIA_RESOURCES_AVAILABLE and
ACTION_MEDIA_RESOURCES_UNAVAILABLE that get broadcast by
PackageManagerService when sdcard gets mounted/unmounted
by MountService so that packages on sdcard get recognized by
various system services as being installed/available or
removed/unavailable by the system.
The broadcasts are sent before the actual package cleanup which includes
mounting/unmounting the packages and we force a gc right after so
that any lingering file references to resources on sdcard get
released.
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index 4801817..238403c 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -63,6 +63,7 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Debug;
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
@@ -2179,21 +2180,32 @@
             parseFlags |= PackageParser.PARSE_FORWARD_LOCK;
         }
 
+        String codePath = null;
+        String resPath = null;
         if ((parseFlags & PackageParser.PARSE_FORWARD_LOCK) != 0) {
             if (ps != null && ps.resourcePathString != null) {
-                pkg.applicationInfo.publicSourceDir = ps.resourcePathString;
+                resPath = ps.resourcePathString;
             } else {
                 // Should not happen at all. Just log an error.
                 Log.e(TAG, "Resource path not set for pkg : " + pkg.packageName);
             }
         } else {
-            pkg.applicationInfo.publicSourceDir = pkg.mScanPath;
+            resPath = pkg.mScanPath;
         }
-        pkg.applicationInfo.sourceDir = pkg.mScanPath;
+        codePath = pkg.mScanPath;
+        // Set application objects path explicitly.
+        setApplicationInfoPaths(pkg, codePath, resPath);
         // Note that we invoke the following method only if we are about to unpack an application
         return scanPackageLI(pkg, parseFlags, scanMode | SCAN_UPDATE_SIGNATURE);
     }
 
+    private static void setApplicationInfoPaths(PackageParser.Package pkg,
+            String destCodePath, String destResPath) {
+        pkg.mPath = pkg.mScanPath = destCodePath;
+        pkg.applicationInfo.sourceDir = destCodePath;
+        pkg.applicationInfo.publicSourceDir = destResPath;
+    }
+
     private static String fixProcessName(String defProcessName,
             String processName, int uid) {
         if (processName == null) {
@@ -4025,7 +4037,7 @@
                     if (res.removedInfo.args != null) {
                         // Remove the replaced package's older resources safely now
                         synchronized (mInstallLock) {
-                            res.removedInfo.args.cleanUpResourcesLI();
+                            res.removedInfo.args.doPostDeleteLI(true);
                         }
                     }
                 }
@@ -4051,13 +4063,14 @@
 
         abstract void createCopyFile();
         abstract int copyApk(IMediaContainerService imcs);
-        abstract void doPreInstall(int status);
+        abstract int doPreInstall(int status);
         abstract boolean doRename(int status, String pkgName, String oldCodePath);
-        abstract void doPostInstall(int status);
+        abstract int doPostInstall(int status);
         abstract String getCodePath();
         abstract String getResourcePath();
         // Need installer lock especially for dex file removal.
         abstract void cleanUpResourcesLI();
+        abstract boolean doPostDeleteLI(boolean delete);
     }
 
     class FileInstallArgs extends InstallArgs {
@@ -4114,10 +4127,11 @@
             return ret;
         }
 
-        void doPreInstall(int status) {
+        int doPreInstall(int status) {
             if (status != PackageManager.INSTALL_SUCCEEDED) {
                 cleanUp();
             }
+            return status;
         }
 
         boolean doRename(int status, final String pkgName, String oldCodePath) {
@@ -4144,10 +4158,11 @@
             }
         }
 
-        void doPostInstall(int status) {
+        int doPostInstall(int status) {
             if (status != PackageManager.INSTALL_SUCCEEDED) {
                 cleanUp();
             }
+            return status;
         }
 
         String getResourcePath() {
@@ -4220,6 +4235,11 @@
             }
             return true;
         }
+
+        boolean doPostDeleteLI(boolean delete) {
+            cleanUpResourcesLI();
+            return true;
+        }
     }
 
     class SdInstallArgs extends InstallArgs {
@@ -4234,7 +4254,7 @@
         }
 
         SdInstallArgs(String fullCodePath, String fullResourcePath) {
-            super(null, null, 0, null);
+            super(null, null, ApplicationInfo.FLAG_ON_SDCARD, null);
             // Extract cid from fullCodePath
             int eidx = fullCodePath.lastIndexOf("/");
             String subStr1 = fullCodePath.substring(0, eidx);
@@ -4243,6 +4263,11 @@
             cachePath = subStr1;
         }
 
+        SdInstallArgs(String cid) {
+            super(null, null,  ApplicationInfo.FLAG_ON_SDCARD, null);
+            this.cid = cid;
+        }
+
         void createCopyFile() {
             cid = getTempContainerId();
         }
@@ -4254,16 +4279,8 @@
                         getEncryptKey(), RES_FILE_NAME);
             } catch (RemoteException e) {
             }
-
-            if (cachePath != null) {
-                // Mount container once its created with system_uid
-                cachePath = mountSdDir(cid, Process.SYSTEM_UID);
-            }
-            if (cachePath == null) {
-                return PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
-            } else {
-                return PackageManager.INSTALL_SUCCEEDED;
-            }
+            return (cachePath == null) ? PackageManager.INSTALL_FAILED_CONTAINER_ERROR :
+                PackageManager.INSTALL_SUCCEEDED;
         }
 
         @Override
@@ -4276,25 +4293,81 @@
             return cachePath + "/" + RES_FILE_NAME;
         }
 
-        void doPreInstall(int status) {
+        int doPreInstall(int status) {
             if (status != PackageManager.INSTALL_SUCCEEDED) {
                 // Destroy container
                 destroySdDir(cid);
+            } else {
+                // STOPSHIP Remove once new api is added in MountService
+                //boolean mounted = isContainerMounted(cid);
+                boolean mounted = false;
+                if (!mounted) {
+                    cachePath = mountSdDir(cid, Process.SYSTEM_UID);
+                    if (cachePath == null) {
+                        return PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
+                    }
+                }
             }
+            return status;
         }
 
         boolean doRename(int status, final String pkgName,
                 String oldCodePath) {
             String newCacheId = getNextCodePath(oldCodePath, pkgName, "/" + RES_FILE_NAME);
+            String newCachePath = null;
+            /*final int RENAME_FAILED = 1;
+            final int MOUNT_FAILED = 2;
+            final int DESTROY_FAILED = 3;
+            final int PASS = 4;
+            int errCode = RENAME_FAILED;
+            if (mounted) {
+                // Unmount the container
+                if (!unMountSdDir(cid)) {
+                    Log.i(TAG, "Failed to unmount " + cid + " before renaming");
+                    return false;
+                }
+                mounted = false;
+            }
+            if (renameSdDir(cid, newCacheId)) {
+                errCode = MOUNT_FAILED;
+                if ((newCachePath = mountSdDir(newCacheId, Process.SYSTEM_UID)) != null) {
+                    errCode = PASS;
+                }
+            }
+            String errMsg = "";
+            switch (errCode) {
+                case RENAME_FAILED:
+                    errMsg = "RENAME_FAILED";
+                    break;
+                case MOUNT_FAILED:
+                    errMsg = "MOUNT_FAILED";
+                    break;
+                case DESTROY_FAILED:
+                    errMsg = "DESTROY_FAILED";
+                    break;
+                default:
+                    errMsg = "PASS";
+                break;
+            }
+            Log.i(TAG, "Status: " + errMsg);
+            if (errCode != PASS) {
+                return false;
+            }
+            Log.i(TAG, "Succesfully renamed " + cid + " to " +newCacheId +
+                    " at path: " + cachePath + " to new path: " + newCachePath);
+            cid = newCacheId;
+            cachePath = newCachePath;
+            return true;
+            */
             // STOPSHIP TEMPORARY HACK FOR RENAME
             // Create new container at newCachePath
             String codePath = getCodePath();
-            String newCachePath = null;
             final int CREATE_FAILED = 1;
             final int COPY_FAILED = 3;
             final int FINALIZE_FAILED = 5;
             final int PASS = 7;
             int errCode = CREATE_FAILED;
+
             if ((newCachePath = createSdDir(new File(codePath), newCacheId)) != null) {
                 errCode = COPY_FAILED;
                 // Copy file from codePath
@@ -4335,13 +4408,18 @@
             return true;
         }
 
-        void doPostInstall(int status) {
+        int doPostInstall(int status) {
             if (status != PackageManager.INSTALL_SUCCEEDED) {
                 cleanUp();
             } else {
-                // Unmount container
-                // Rename and remount based on package name and new uid
+                // STOP SHIP Change this once new api is added.
+                //boolean mounted = isContainerMounted(cid);
+                boolean mounted = false;
+                if (!mounted) {
+                    mountSdDir(cid, Process.SYSTEM_UID);
+                }
             }
+            return status;
         }
 
         private void cleanUp() {
@@ -4363,32 +4441,66 @@
             }
             cleanUp();
         }
+
+        boolean matchContainer(String app) {
+            if (cid.startsWith(app)) {
+                return true;
+            }
+            return false;
+        }
+
+        String getPackageName() {
+            int idx = cid.lastIndexOf("-");
+            if (idx == -1) {
+                return cid;
+            }
+            return cid.substring(0, idx);
+        }
+
+        boolean doPostDeleteLI(boolean delete) {
+            boolean ret = false;
+            boolean mounted = isContainerMounted(cid);
+            if (mounted) {
+                // Unmount first
+                ret = unMountSdDir(cid);
+            }
+            if (ret && delete) {
+                cleanUpResourcesLI();
+            }
+            return ret;
+        }
     };
 
     // Utility method used to create code paths based on package name and available index.
     private static String getNextCodePath(String oldCodePath, String prefix, String suffix) {
         String idxStr = "";
         int idx = 1;
+        // Fall back to default value of idx=1 if prefix is not
+        // part of oldCodePath
         if (oldCodePath != null) {
             String subStr = oldCodePath;
-            if (subStr.startsWith(prefix)) {
-                subStr = subStr.substring(prefix.length());
-            }
+            // Drop the suffix right away
             if (subStr.endsWith(suffix)) {
                 subStr = subStr.substring(0, subStr.length() - suffix.length());
             }
-            if (subStr != null) {
-                if (subStr.startsWith("-")) {
-                    subStr = subStr.substring(1);
-                }
-                try {
-                    idx = Integer.parseInt(subStr);
-                    if (idx <= 1) {
-                        idx++;
-                    } else {
-                        idx--;
+            // If oldCodePath already contains prefix find out the
+            // ending index to either increment or decrement.
+            int sidx = subStr.lastIndexOf(prefix);
+            if (sidx != -1) {
+                subStr = subStr.substring(sidx + prefix.length());
+                if (subStr != null) {
+                    if (subStr.startsWith("-")) {
+                        subStr = subStr.substring(1);
                     }
-                } catch(NumberFormatException e) {
+                    try {
+                        idx = Integer.parseInt(subStr);
+                        if (idx <= 1) {
+                            idx++;
+                        } else {
+                            idx--;
+                        }
+                    } catch(NumberFormatException e) {
+                    }
                 }
             }
         }
@@ -4753,6 +4865,9 @@
                 if ((pFlags&PackageManager.INSTALL_REPLACE_EXISTING) != 0
                         && mPackages.containsKey(pkgName)) {
                     replacingExistingPackage = true;
+                }
+                PackageSetting ps = mSettings.mPackages.get(pkgName);
+                if (ps != null) {
                     oldCodePath = mSettings.mPackages.get(pkgName).codePathString;
                 }
             }
@@ -4761,9 +4876,8 @@
                 res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
                 break main_flow;
             }
-            // TODO rename  pkg.mScanPath In scanPackageLI let it just set values based on mScanPath
-            pkg.applicationInfo.sourceDir = pkg.mScanPath= pkg.mPath = args.getCodePath();
-            pkg.applicationInfo.publicSourceDir = args.getResourcePath();
+            // Set application objects path explicitly after the rename
+            setApplicationInfoPaths(pkg, args.getCodePath(), args.getResourcePath());
             if(replacingExistingPackage) {
                 replacePackageLI(pkg, parseFlags, scanMode,
                         installerPackageName, res);
@@ -4977,9 +5091,9 @@
         }
         // Delete the resources here after sending the broadcast to let
         // other processes clean up before deleting resources.
-        synchronized (mInstallLock) {
-            if (info.args != null) {
-                info.args.cleanUpResourcesLI();
+        if (info.args != null) {
+            synchronized (mInstallLock) {
+                info.args.doPostDeleteLI(deleteCodeAndResources);
             }
         }
         return res;
@@ -6948,6 +7062,17 @@
             }
         }
 
+        private Set<String> findPackagesWithFlag(int flag) {
+            Set<String> ret = new HashSet<String>();
+            for (PackageSetting ps : mPackages.values()) {
+                // Has to match atleast all the flag bits set on flag
+                if ((ps.pkgFlags & flag) == flag) {
+                    ret.add(ps.name);
+                }
+            }
+            return ret;
+        }
+
         private void removeUserIdLP(int uid) {
             if (uid >= FIRST_APPLICATION_UID) {
                 int N = mUserIds.size();
@@ -7992,9 +8117,20 @@
        return true;
    }
 
-    private String getSdDir(String pkgName) {
-        return getMountService().getSecureContainerPath(pkgName);
-    }
+   private boolean renameSdDir(String oldId, String newId) {
+       try {
+           getMountService().renameSecureContainer(oldId, newId);
+           return true;
+       } catch (IllegalStateException e) {
+           Log.i(TAG, "Failed ot rename  " + oldId + " to " + newId +
+                   " with exception : " + e);
+       }
+       return false;
+   }
+
+   private String getSdDir(String pkgName) {
+       return getMountService().getSecureContainerPath(pkgName);
+   }
 
     private boolean finalizeSdDir(String pkgName) {
         int rc = getMountService().finalizeSecureContainer(pkgName);
@@ -8019,6 +8155,16 @@
         return list.length == 0 ? null : list;
     }
 
+   static boolean isContainerMounted(String cid) {
+       // STOPSHIP
+       // New api from MountService
+       try {
+           return (getMountService().getSecureContainerPath(cid) != null);
+       } catch (IllegalStateException e) {
+       }
+       return false;
+   }
+
    static String getTempContainerId() {
        String prefix = "smdl1tmp";
        int tmpIdx = 1;
@@ -8063,6 +8209,8 @@
    }
 
    public void updateExternalMediaStatus(final boolean mediaStatus) {
+       final boolean DEBUG = true;
+       if (DEBUG) Log.i(TAG, "updateExterMediaStatus::");
        if (mediaStatus == mMediaMounted) {
            return;
        }
@@ -8071,54 +8219,155 @@
        mHandler.post(new Runnable() {
            public void run() {
                mHandler.removeCallbacks(this);
-               final String list[] = getSecureContainerList();
-               if (list == null || list.length == 0) {
-                   return;
+               updateExternalMediaStatusInner(mediaStatus);
+           }
+       });
+   }
+
+   void updateExternalMediaStatusInner(boolean mediaStatus) {
+       final String list[] = getSecureContainerList();
+       if (list == null || list.length == 0) {
+           return;
+       }
+       HashMap<SdInstallArgs, String> processCids = new HashMap<SdInstallArgs, String>();
+       int uidList[] = new int[list.length];
+       int num = 0;
+       for (int i = 0; i < uidList.length; i++) {
+           uidList[i] = Process.LAST_APPLICATION_UID;
+       }
+       synchronized (mPackages) {
+           Set<String> appList = mSettings.findPackagesWithFlag(ApplicationInfo.FLAG_ON_SDCARD);
+           for (String cid : list) {
+               SdInstallArgs args = new SdInstallArgs(cid);
+               String removeEntry = null;
+               for (String app : appList) {
+                   if (args.matchContainer(app)) {
+                       removeEntry = app;
+                       break;
+                   }
                }
-               for (int i = 0; i < list.length; i++) {
-                   String mountPkg = list[i];
-                   // TODO compare with default package
-                   synchronized (mPackages) {
-                       PackageSetting ps = mSettings.mPackages.get(mountPkg);
-                       if (ps != null && (ps.pkgFlags & ApplicationInfo.FLAG_ON_SDCARD) != 0) {
-                           if (mediaStatus) {
-                               String pkgPath = getSdDir(mountPkg);
-                               if (pkgPath == null) {
-                                   continue;
-                               }
-                               pkgPath = ps.codePathString;
-                               int parseFlags = PackageParser.PARSE_CHATTY |
-                               PackageParser.PARSE_ON_SDCARD | mDefParseFlags;
-                               PackageParser pp = new PackageParser(pkgPath);
-                               pp.setSeparateProcesses(mSeparateProcesses);
-                               final PackageParser.Package pkg = pp.parsePackage(new File(pkgPath),
-                                       null, mMetrics, parseFlags);
-                               if (pkg == null) {
-                                   Log.w(TAG, "Failed to install package : " + mountPkg + " from sd card");
-                                   continue;
-                               }
-                               int scanMode = SCAN_MONITOR;
-                               // Scan the package
-                               if (scanPackageLI(pkg, parseFlags, scanMode) != null) {
-                                   // Grant permissions
-                                   grantPermissionsLP(pkg, false);
-                                   // Persist settings
-                                   mSettings.writeLP();
-                               } else {
-                                   Log.i(TAG, "Failed to install package: " + mountPkg + " from sdcard");
-                               }
-                           } else {
-                               // Delete package
-                               PackageRemovedInfo outInfo = new PackageRemovedInfo();
-                               boolean res = deletePackageLI(mountPkg, false, PackageManager.DONT_DELETE_DATA, outInfo);
-                               if (!res) {
-                                   Log.e(TAG, "Failed to delete pkg  from sdcard : " + mountPkg);
-                               }
-                           }
-                       }
+               if (removeEntry == null) {
+                   // No matching app on device. Skip entry or may be cleanup?
+                   // Ignore default package
+                   continue;
+               }
+               appList.remove(removeEntry);
+               PackageSetting ps = mSettings.mPackages.get(removeEntry);
+               processCids.put(args, ps.codePathString);
+               int uid = ps.userId;
+               if (uid != -1) {
+                   int idx = Arrays.binarySearch(uidList, uid);
+                   if (idx < 0) {
+                       uidList[-idx] = uid;
+                       num++;
                    }
                }
            }
-       });
+       }
+       int uidArr[] = uidList;
+       if ((num > 0) && (num < uidList.length)) {
+           uidArr = new int[num];
+           for (int i = 0; i < num; i++) {
+               uidArr[i] = uidList[i];
+           }
+       }
+       if (mediaStatus) {
+           loadMediaPackages(processCids, uidArr);
+       } else {
+           unloadMediaPackages(processCids, uidArr);
+       }
+   }
+
+   private void sendResourcesChangedBroadcast(boolean mediaStatus,
+           ArrayList<String> pkgList, int uidArr[]) {
+       int size = pkgList.size();
+       if (size > 0) {
+           // Send broadcasts here
+           Bundle extras = new Bundle();
+           extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST,
+                   pkgList.toArray(new String[size]));
+           extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidArr);
+           String action = mediaStatus ? Intent.ACTION_MEDIA_RESOURCES_AVAILABLE
+                   : Intent.ACTION_MEDIA_RESOURCES_UNAVAILABLE;
+           sendPackageBroadcast(action, null, extras);
+       }
+   }
+
+   void loadMediaPackages(HashMap<SdInstallArgs, String> processCids, int uidArr[]) {
+       ArrayList<String> pkgList = new ArrayList<String>();
+       Set<SdInstallArgs> keys = processCids.keySet();
+       for (SdInstallArgs args : keys) {
+           String cid = args.cid;
+           String codePath = processCids.get(args);
+           if (args.doPreInstall(PackageManager.INSTALL_SUCCEEDED) != PackageManager.INSTALL_SUCCEEDED) {
+               Log.i(TAG, "Failed to install package: " + codePath + " from sdcard");
+               continue;
+           }
+           // Parse package
+           int parseFlags = PackageParser.PARSE_CHATTY |
+           PackageParser.PARSE_ON_SDCARD | mDefParseFlags;
+           PackageParser pp = new PackageParser(codePath);
+           pp.setSeparateProcesses(mSeparateProcesses);
+           final PackageParser.Package pkg = pp.parsePackage(new File(codePath),
+                   codePath, mMetrics, parseFlags);
+           if (pkg == null) {
+               Log.w(TAG, "Failed to install package : " + cid + " from sd card");
+               continue;
+           }
+           setApplicationInfoPaths(pkg, codePath, codePath);
+           int retCode = PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
+           synchronized (mInstallLock) {
+               // Scan the package
+               if (scanPackageLI(pkg, parseFlags, SCAN_MONITOR) != null) {
+                   synchronized (mPackages) {
+                       // Grant permissions
+                       grantPermissionsLP(pkg, false);
+                       // Persist settings
+                       mSettings.writeLP();
+                       retCode = PackageManager.INSTALL_SUCCEEDED;
+                       pkgList.add(pkg.packageName);
+                   }
+               } else {
+                   Log.i(TAG, "Failed to install package: " + pkg.packageName + " from sdcard");
+               }
+           }
+           args.doPostInstall(retCode);
+           pkgList.add(pkg.packageName);
+       }
+       // Send broadcasts first
+       sendResourcesChangedBroadcast(true, pkgList, uidArr);
+       Runtime.getRuntime().gc();
+       // If something failed do we clean up here or next install?
+   }
+
+   void unloadMediaPackages(HashMap<SdInstallArgs, String> processCids, int uidArr[]) {
+       ArrayList<String> pkgList = new ArrayList<String>();
+       Set<SdInstallArgs> keys = processCids.keySet();
+       for (SdInstallArgs args : keys) {
+           String cid = args.cid;
+           String pkgName = args.getPackageName();
+           // STOPSHIP Send broadcast to apps to remove references
+           // STOPSHIP Unmount package
+           // Delete package internally
+           PackageRemovedInfo outInfo = new PackageRemovedInfo();
+           synchronized (mInstallLock) {
+               boolean res = deletePackageLI(pkgName, false,
+                       PackageManager.DONT_DELETE_DATA, outInfo);
+               if (res) {
+                   pkgList.add(pkgName);
+               } else {
+                   Log.e(TAG, "Failed to delete pkg  from sdcard : " + pkgName);
+               }
+           }
+       }
+       // Send broadcasts
+       sendResourcesChangedBroadcast(false, pkgList, uidArr);
+       Runtime.getRuntime().gc();
+       // Do clean up. Just unmount
+       for (SdInstallArgs args : keys) {
+           synchronized (mInstallLock) {
+               args.doPostDeleteLI(false);
+           }
+       }
    }
 }