Incorporate home metadata into app details UI

Just as we don't expose disable/uninstall of home applications in this UI,
we now also don't allow disable/uninstall of packages that are proxying
for home apps via the android.app.home.alternate meta-data mechanism.

Also, don't display the 'Home' settings top-level category at all when
there is only a single available home app.

Finally, explicitly note the current user when sending broadcasts,
otherwise API sanity checks get suspicious.

Bug 10749961

Change-Id: I13e11032cb571df19798c4e13c91a572d1ee61a7
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index 6b6e704..5c77a97 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -564,7 +564,9 @@
                 int headerIndex = i + 1;
                 i = insertAccountsHeaders(target, headerIndex);
             } else if (id == R.id.home_settings) {
-                updateHomeSettingHeaders(header);
+                if (!updateHomeSettingHeaders(header)) {
+                    target.remove(i);
+                }
             } else if (id == R.id.user_settings) {
                 if (!UserHandle.MU_ENABLED
                         || !UserManager.supportsMultipleUsers()
@@ -669,11 +671,16 @@
         return ((ri.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
     }
 
-    private void updateHomeSettingHeaders(Header header) {
+    private boolean updateHomeSettingHeaders(Header header) {
         final PackageManager pm = getPackageManager();
         final ArrayList<ResolveInfo> homeApps = new ArrayList<ResolveInfo>();
         try {
             ComponentName currentHome = pm.getHomeActivities(homeApps);
+            if (homeApps.size() < 2) {
+                // When there's only one available home app, omit this settings
+                // category entirely at the top level UI.
+                return false;
+            }
             ResolveInfo iconSource = null;
             if (currentHome == null) {
                 // no current default, so find the system home app and use that
@@ -708,6 +715,7 @@
             // Can't look up the home activity; bail on configuring the icon
             Log.w(LOG_TAG, "Problem looking up home activity!", e);
         }
+        return true;
     }
 
     private void getMetaData() {
diff --git a/src/com/android/settings/applications/InstalledAppDetails.java b/src/com/android/settings/applications/InstalledAppDetails.java
index 37c33db..1b3938c 100644
--- a/src/com/android/settings/applications/InstalledAppDetails.java
+++ b/src/com/android/settings/applications/InstalledAppDetails.java
@@ -66,6 +66,7 @@
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 import android.view.LayoutInflater;
 import android.view.Menu;
@@ -143,6 +144,8 @@
 
     private PackageMoveObserver mPackageMoveObserver;
 
+    private final HashSet<String> mHomePackages = new HashSet<String>();
+
     private boolean mDisableAfterUninstall;
 
     private boolean mHaveSizes = false;
@@ -320,29 +323,20 @@
 
     private boolean handleDisableable(Button button) {
         boolean disableable = false;
-        try {
-            // Try to prevent the user from bricking their phone
-            // by not allowing disabling of apps signed with the
-            // system cert and any launcher app in the system.
-            PackageInfo sys = mPm.getPackageInfo("android",
-                    PackageManager.GET_SIGNATURES);
-            Intent intent = new Intent(Intent.ACTION_MAIN);
-            intent.addCategory(Intent.CATEGORY_HOME);
-            intent.setPackage(mAppEntry.info.packageName);
-            List<ResolveInfo> homes = mPm.queryIntentActivities(intent, 0);
-            if ((homes != null && homes.size() > 0) || isThisASystemPackage()) {
-                // Disable button for core system applications.
-                button.setText(R.string.disable_text);
-            } else if (mAppEntry.info.enabled) {
-                button.setText(R.string.disable_text);
-                disableable = true;
-            } else {
-                button.setText(R.string.enable_text);
-                disableable = true;
-            }
-        } catch (PackageManager.NameNotFoundException e) {
-            Log.w(TAG, "Unable to get package info", e);
+        // Try to prevent the user from bricking their phone
+        // by not allowing disabling of apps signed with the
+        // system cert and any launcher app in the system.
+        if (mHomePackages.contains(mAppEntry.info.packageName) || isThisASystemPackage()) {
+            // Disable button for core system applications.
+            button.setText(R.string.disable_text);
+        } else if (mAppEntry.info.enabled) {
+            button.setText(R.string.disable_text);
+            disableable = true;
+        } else {
+            button.setText(R.string.enable_text);
+            disableable = true;
         }
+
         return disableable;
     }
 
@@ -638,6 +632,21 @@
         return packageName;
     }
 
+    private boolean signaturesMatch(String pkg1, String pkg2) {
+        if (pkg1 != null && pkg2 != null) {
+            try {
+                final int match = mPm.checkSignatures(pkg1, pkg2);
+                if (match >= PackageManager.SIGNATURE_MATCH) {
+                    return true;
+                }
+            } catch (Exception e) {
+                // e.g. named alternate package not found during lookup;
+                // this is an expected case sometimes
+            }
+        }
+        return false;
+    }
+
     private boolean refreshUi() {
         if (mMoveInProgress) {
             return true;
@@ -652,6 +661,25 @@
             return false; // onCreate must have failed, make sure to exit
         }
 
+        // Get list of "home" apps and trace through any meta-data references
+        List<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>();
+        mPm.getHomeActivities(homeActivities);
+        mHomePackages.clear();
+        for (int i = 0; i< homeActivities.size(); i++) {
+            ResolveInfo ri = homeActivities.get(i);
+            final String activityPkg = ri.activityInfo.packageName;
+            mHomePackages.add(activityPkg);
+
+            // Also make sure to include anything proxying for the home app
+            final Bundle metadata = ri.activityInfo.metaData;
+            if (metadata != null) {
+                final String metaPkg = metadata.getString(ActivityManager.META_HOME_ALTERNATE);
+                if (signaturesMatch(metaPkg, activityPkg)) {
+                    mHomePackages.add(metaPkg);
+                }
+            }
+        }
+
         // Get list of preferred activities
         List<ComponentName> prefActList = new ArrayList<ComponentName>();
         
@@ -1237,8 +1265,8 @@
             intent.putExtra(Intent.EXTRA_PACKAGES, new String[] { mAppEntry.info.packageName });
             intent.putExtra(Intent.EXTRA_UID, mAppEntry.info.uid);
             intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(mAppEntry.info.uid));
-            getActivity().sendOrderedBroadcast(intent, null, mCheckKillProcessesReceiver, null,
-                    Activity.RESULT_CANCELED, null, null);
+            getActivity().sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null,
+                    mCheckKillProcessesReceiver, null, Activity.RESULT_CANCELED, null, null);
         }
     }