Merge "Package manager optimizations." into froyo
diff --git a/api/current.xml b/api/current.xml
index b0d9931..aea02e5 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -44268,6 +44268,19 @@
 <parameter name="info" type="android.content.pm.PermissionInfo">
 </parameter>
 </method>
+<method name="addPermissionAsync"
+ return="boolean"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="info" type="android.content.pm.PermissionInfo">
+</parameter>
+</method>
 <method name="addPreferredActivity"
  return="void"
  abstract="true"
@@ -148559,6 +148572,19 @@
 <parameter name="info" type="android.content.pm.PermissionInfo">
 </parameter>
 </method>
+<method name="addPermissionAsync"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="info" type="android.content.pm.PermissionInfo">
+</parameter>
+</method>
 <method name="addPreferredActivity"
  return="void"
  abstract="false"
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 4c2b36f..74cfbfa 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1853,6 +1853,15 @@
         }
 
         @Override
+        public boolean addPermissionAsync(PermissionInfo info) {
+            try {
+                return mPM.addPermissionAsync(info);
+            } catch (RemoteException e) {
+                throw new RuntimeException("Package manager has died", e);
+            }
+        }
+
+        @Override
         public void removePermission(String name) {
             try {
                 mPM.removePermission(name);
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index c638d04..f90ef63 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -314,4 +314,6 @@
     String nextPackageToClean(String lastPackage);
 
     void movePackage(String packageName, IPackageMoveObserver observer, int flags);
+    
+    boolean addPermissionAsync(in PermissionInfo info);
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 0318b6c..2f512ae 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1120,6 +1120,15 @@
     public abstract boolean addPermission(PermissionInfo info);
 
     /**
+     * Like {@link #addPermission(PermissionInfo)} but asynchronously
+     * persists the package manager state after returning from the call,
+     * allowing it to return quicker and batch a series of adds at the
+     * expense of no guarantee the added permission will be retained if
+     * the device is rebooted before it is written.
+     */
+    public abstract boolean addPermissionAsync(PermissionInfo info);
+    
+    /**
      * Removes a permission that was previously added with
      * {@link #addPermission(PermissionInfo)}.  The same ownership rules apply
      * -- you are only allowed to remove permissions that you are allowed
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index 79b012b..7cd058a 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -338,6 +338,9 @@
     static final int MCS_RECONNECT = 10;
     static final int MCS_GIVE_UP = 11;
     static final int UPDATED_MEDIA_STATUS = 12;
+    static final int WRITE_SETTINGS = 13;
+
+    static final int WRITE_SETTINGS_DELAY = 10*1000;  // 10 seconds
 
     // Delay time in millisecs
     static final int BROADCAST_DELAY = 10 * 1000;
@@ -379,24 +382,38 @@
             if (DEBUG_SD_INSTALL) Log.i(TAG, "Trying to bind to" +
                     " DefaultContainerService");
             Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
+            Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
             if (mContext.bindService(service, mDefContainerConn,
                     Context.BIND_AUTO_CREATE)) {
+                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                 mBound = true;
                 return true;
             }
+            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
             return false;
         }
 
         private void disconnectService() {
             mContainerService = null;
             mBound = false;
+            Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
             mContext.unbindService(mDefContainerConn);
+            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
         }
 
         PackageHandler(Looper looper) {
             super(looper);
         }
+        
         public void handleMessage(Message msg) {
+            try {
+                doHandleMessage(msg);
+            } finally {
+                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+            }
+        }
+        
+        void doHandleMessage(Message msg) {
             switch (msg.what) {
                 case INIT_COPY: {
                     if (DEBUG_SD_INSTALL) Log.i(TAG, "init_copy");
@@ -499,6 +516,7 @@
                     ArrayList components[];
                     int size = 0;
                     int uids[];
+                    Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
                     synchronized (mPackages) {
                         if (mPendingBroadcasts == null) {
                             return;
@@ -530,15 +548,18 @@
                         sendPackageChangedBroadcast(packages[i], true,
                                 (ArrayList<String>)components[i], uids[i]);
                     }
+                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                     break;
                 }
                 case START_CLEANING_PACKAGE: {
                     String packageName = (String)msg.obj;
+                    Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
                     synchronized (mPackages) {
                         if (!mSettings.mPackagesToBeCleaned.contains(packageName)) {
                             mSettings.mPackagesToBeCleaned.add(packageName);
                         }
                     }
+                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                     startCleaningPackages();
                 } break;
                 case POST_INSTALL: {
@@ -598,10 +619,24 @@
                         Log.e(TAG, "MountService not running?");
                     }
                 } break;
+                case WRITE_SETTINGS: {
+                    Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
+                    synchronized (mPackages) {
+                        removeMessages(WRITE_SETTINGS);
+                        mSettings.writeLP();
+                    }
+                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+                } break;
             }
         }
     }
 
+    void scheduleWriteSettingsLocked() {
+        if (!mHandler.hasMessages(WRITE_SETTINGS)) {
+            mHandler.sendEmptyMessageDelayed(WRITE_SETTINGS, WRITE_SETTINGS_DELAY);
+        }
+    }
+    
     static boolean installOnSd(int flags) {
         if (((flags & PackageManager.INSTALL_FORWARD_LOCK) != 0) ||
                 ((flags & PackageManager.INSTALL_INTERNAL) != 0)) {
@@ -1633,32 +1668,84 @@
         throw new SecurityException("No permission tree found for " + permName);
     }
 
+    static boolean compareStrings(CharSequence s1, CharSequence s2) {
+        if (s1 == null) {
+            return s2 == null;
+        }
+        if (s2 == null) {
+            return false;
+        }
+        if (s1.getClass() != s2.getClass()) {
+            return false;
+        }
+        return s1.equals(s2);
+    }
+    
+    static boolean comparePermissionInfos(PermissionInfo pi1, PermissionInfo pi2) {
+        if (pi1.icon != pi2.icon) return false;
+        if (pi1.protectionLevel != pi2.protectionLevel) return false;
+        if (!compareStrings(pi1.name, pi2.name)) return false;
+        if (!compareStrings(pi1.nonLocalizedLabel, pi2.nonLocalizedLabel)) return false;
+        // We'll take care of setting this one.
+        if (!compareStrings(pi1.packageName, pi2.packageName)) return false;
+        // These are not currently stored in settings.
+        //if (!compareStrings(pi1.group, pi2.group)) return false;
+        //if (!compareStrings(pi1.nonLocalizedDescription, pi2.nonLocalizedDescription)) return false;
+        //if (pi1.labelRes != pi2.labelRes) return false;
+        //if (pi1.descriptionRes != pi2.descriptionRes) return false;
+        return true;
+    }
+    
+    boolean addPermissionLocked(PermissionInfo info, boolean async) {
+        if (info.labelRes == 0 && info.nonLocalizedLabel == null) {
+            throw new SecurityException("Label must be specified in permission");
+        }
+        BasePermission tree = checkPermissionTreeLP(info.name);
+        BasePermission bp = mSettings.mPermissions.get(info.name);
+        boolean added = bp == null;
+        boolean changed = true;
+        if (added) {
+            bp = new BasePermission(info.name, tree.sourcePackage,
+                    BasePermission.TYPE_DYNAMIC);
+        } else if (bp.type != BasePermission.TYPE_DYNAMIC) {
+            throw new SecurityException(
+                    "Not allowed to modify non-dynamic permission "
+                    + info.name);
+        } else {
+            if (bp.protectionLevel == info.protectionLevel
+                    && bp.perm.owner.equals(tree.perm.owner)
+                    && bp.uid == tree.uid
+                    && comparePermissionInfos(bp.perm.info, info)) {
+                changed = false;
+            }
+        }
+        bp.protectionLevel = info.protectionLevel;
+        bp.perm = new PackageParser.Permission(tree.perm.owner,
+                new PermissionInfo(info));
+        bp.perm.info.packageName = tree.perm.info.packageName;
+        bp.uid = tree.uid;
+        if (added) {
+            mSettings.mPermissions.put(info.name, bp);
+        }
+        if (changed) {
+            if (!async) {
+                mSettings.writeLP();
+            } else {
+                scheduleWriteSettingsLocked();            
+            }
+        }
+        return added;
+    }
+
     public boolean addPermission(PermissionInfo info) {
         synchronized (mPackages) {
-            if (info.labelRes == 0 && info.nonLocalizedLabel == null) {
-                throw new SecurityException("Label must be specified in permission");
-            }
-            BasePermission tree = checkPermissionTreeLP(info.name);
-            BasePermission bp = mSettings.mPermissions.get(info.name);
-            boolean added = bp == null;
-            if (added) {
-                bp = new BasePermission(info.name, tree.sourcePackage,
-                        BasePermission.TYPE_DYNAMIC);
-            } else if (bp.type != BasePermission.TYPE_DYNAMIC) {
-                throw new SecurityException(
-                        "Not allowed to modify non-dynamic permission "
-                        + info.name);
-            }
-            bp.protectionLevel = info.protectionLevel;
-            bp.perm = new PackageParser.Permission(tree.perm.owner,
-                    new PermissionInfo(info));
-            bp.perm.info.packageName = tree.perm.info.packageName;
-            bp.uid = tree.uid;
-            if (added) {
-                mSettings.mPermissions.put(info.name, bp);
-            }
-            mSettings.writeLP();
-            return added;
+            return addPermissionLocked(info, false);
+        }
+    }
+
+    public boolean addPermissionAsync(PermissionInfo info) {
+        synchronized (mPackages) {
+            return addPermissionLocked(info, true);
         }
     }
 
@@ -6448,7 +6535,7 @@
             filter.dump(new LogPrinter(Log.INFO, TAG), "  ");
             mSettings.mPreferredActivities.addFilter(
                     new PreferredActivity(filter, match, set, activity));
-            mSettings.writeLP();
+            scheduleWriteSettingsLocked();            
         }
     }
 
@@ -6519,7 +6606,7 @@
             }
 
             if (clearPackagePreferredActivitiesLP(packageName)) {
-                mSettings.writeLP();
+                scheduleWriteSettingsLocked();            
             }
         }
     }
@@ -6608,18 +6695,28 @@
             }
             if (className == null) {
                 // We're dealing with an application/package level state change
+                if (pkgSetting.enabled == newState) {
+                    // Nothing to do
+                    return;
+                }
                 pkgSetting.enabled = newState;
             } else {
                 // We're dealing with a component level state change
                 switch (newState) {
                 case COMPONENT_ENABLED_STATE_ENABLED:
-                    pkgSetting.enableComponentLP(className);
+                    if (!pkgSetting.enableComponentLP(className)) {
+                        return;
+                    }
                     break;
                 case COMPONENT_ENABLED_STATE_DISABLED:
-                    pkgSetting.disableComponentLP(className);
+                    if (!pkgSetting.disableComponentLP(className)) {
+                        return;
+                    }
                     break;
                 case COMPONENT_ENABLED_STATE_DEFAULT:
-                    pkgSetting.restoreComponentLP(className);
+                    if (!pkgSetting.restoreComponentLP(className)) {
+                        return;
+                    }
                     break;
                 default:
                     Slog.e(TAG, "Invalid new component state: " + newState);
@@ -7614,19 +7711,22 @@
             installStatus = base.installStatus;
         }
 
-        void enableComponentLP(String componentClassName) {
-            disabledComponents.remove(componentClassName);
-            enabledComponents.add(componentClassName);
+        boolean enableComponentLP(String componentClassName) {
+            boolean changed = disabledComponents.remove(componentClassName);
+            changed |= enabledComponents.add(componentClassName);
+            return changed;
         }
 
-        void disableComponentLP(String componentClassName) {
-            enabledComponents.remove(componentClassName);
-            disabledComponents.add(componentClassName);
+        boolean disableComponentLP(String componentClassName) {
+            boolean changed = enabledComponents.remove(componentClassName);
+            changed |= disabledComponents.add(componentClassName);
+            return changed;
         }
 
-        void restoreComponentLP(String componentClassName) {
-            enabledComponents.remove(componentClassName);
-            disabledComponents.remove(componentClassName);
+        boolean restoreComponentLP(String componentClassName) {
+            boolean changed = enabledComponents.remove(componentClassName);
+            changed |= disabledComponents.remove(componentClassName);
+            return changed;
         }
 
         int currentEnabledStateLP(String componentName) {
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index 2ccc9bb..4964f03 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -142,6 +142,11 @@
     }
 
     @Override
+    public boolean addPermissionAsync(PermissionInfo info) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public void removePermission(String name) {
         throw new UnsupportedOperationException();
     }