Implement issue #2367442: Please add API for manipulating installer
package names

Change-Id: I0563e896a47ae1a6e77aebc2d624fb1bde52ffbc
diff --git a/api/current.xml b/api/current.xml
index 9422552..2964fb0 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -58662,6 +58662,21 @@
 <parameter name="flags" type="int">
 </parameter>
 </method>
+<method name="setInstallerPackageName"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="targetPackage" type="java.lang.String">
+</parameter>
+<parameter name="installerPackageName" type="java.lang.String">
+</parameter>
+</method>
 <field name="COMPONENT_ENABLED_STATE_DEFAULT"
  type="int"
  transient="false"
@@ -175729,6 +175744,21 @@
 <parameter name="flags" type="int">
 </parameter>
 </method>
+<method name="setInstallerPackageName"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="targetPackage" type="java.lang.String">
+</parameter>
+<parameter name="installerPackageName" type="java.lang.String">
+</parameter>
+</method>
 <method name="setPackageObbPath"
  return="void"
  abstract="false"
@@ -248151,7 +248181,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="arg0" type="T">
+<parameter name="t" type="T">
 </parameter>
 </method>
 </interface>
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index ce9501a..abb26e3 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -914,6 +914,16 @@
     }
 
     @Override
+    public void setInstallerPackageName(String targetPackage,
+            String installerPackageName) {
+        try {
+            mPM.setInstallerPackageName(targetPackage, installerPackageName);
+        } catch (RemoteException e) {
+            // Should never happen!
+        }
+    }
+
+    @Override
     public void movePackage(String packageName, IPackageMoveObserver observer, int flags) {
         try {
             mPM.movePackage(packageName, observer, flags);
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 4cff3bb..d01a68a 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -158,6 +158,8 @@
 
     void finishPackageInstall(int token);
 
+    void setInstallerPackageName(in String targetPackage, in String installerPackageName);
+
     /**
      * Delete a package.
      *
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b5d1653..ac7a95a 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1918,6 +1918,24 @@
             String installerPackageName);
 
     /**
+     * Change the installer associated with a given package.  There are limitations
+     * on how the installer package can be changed; in particular:
+     * <ul>
+     * <li> A SecurityException will be thrown if <var>installerPackageName</var>
+     * is not signed with the same certificate as the calling application.
+     * <li> A SecurityException will be thrown if <var>targetPackage</var> already
+     * has an installer package, and that installer package is not signed with
+     * the same certificate as the calling application.
+     * </ul>
+     *
+     * @param targetPackage The installed package whose installer will be changed.
+     * @param installerPackageName The package name of the new installer.  May be
+     * null to clear the association.
+     */
+    public abstract void setInstallerPackageName(String targetPackage,
+            String installerPackageName);
+
+    /**
      * Attempts to delete a package.  Since this may take a little while, the result will
      * be posted back to the given observer.  A deletion will fail if the calling context
      * lacks the {@link android.Manifest.permission#DELETE_PACKAGES} permission, if the
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index a0a1974..c121808 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -4578,6 +4578,80 @@
         mHandler.sendMessage(msg);
     }
 
+    public void setInstallerPackageName(String targetPackage,
+            String installerPackageName) {
+        PackageSetting pkgSetting;
+        final int uid = Binder.getCallingUid();
+        final int permission = mContext.checkCallingPermission(
+                android.Manifest.permission.INSTALL_PACKAGES);
+        final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED);
+        synchronized (mPackages) {
+            PackageSetting targetPackageSetting = mSettings.mPackages.get(targetPackage);
+            if (targetPackageSetting == null) {
+                throw new IllegalArgumentException("Unknown target package: " + targetPackage);
+            }
+
+            PackageSetting installerPackageSetting;
+            if (installerPackageName != null) {
+                installerPackageSetting = mSettings.mPackages.get(installerPackageName);
+                if (installerPackageSetting == null) {
+                    throw new IllegalArgumentException("Unknown installer package: "
+                            + installerPackageName);
+                }
+            } else {
+                installerPackageSetting = null;
+            }
+
+            Signature[] callerSignature;
+            Object obj = mSettings.getUserIdLP(uid);
+            if (obj != null) {
+                if (obj instanceof SharedUserSetting) {
+                    callerSignature = ((SharedUserSetting)obj).signatures.mSignatures;
+                } else if (obj instanceof PackageSetting) {
+                    callerSignature = ((PackageSetting)obj).signatures.mSignatures;
+                } else {
+                    throw new SecurityException("Bad object " + obj + " for uid " + uid);
+                }
+            } else {
+                throw new SecurityException("Unknown calling uid " + uid);
+            }
+
+            // Verify: can't set installerPackageName to a package that is
+            // not signed with the same cert as the caller.
+            if (installerPackageSetting != null) {
+                if (checkSignaturesLP(callerSignature,
+                        installerPackageSetting.signatures.mSignatures)
+                        != PackageManager.SIGNATURE_MATCH) {
+                    throw new SecurityException(
+                            "Caller does not have same cert as new installer package "
+                            + installerPackageName);
+                }
+            }
+
+            // Verify: if target already has an installer package, it must
+            // be signed with the same cert as the caller.
+            if (targetPackageSetting.installerPackageName != null) {
+                PackageSetting setting = mSettings.mPackages.get(
+                        targetPackageSetting.installerPackageName);
+                // If the currently set package isn't valid, then it's always
+                // okay to change it.
+                if (setting != null) {
+                    if (checkSignaturesLP(callerSignature,
+                            setting.signatures.mSignatures)
+                            != PackageManager.SIGNATURE_MATCH) {
+                        throw new SecurityException(
+                                "Caller does not have same cert as old installer package "
+                                + targetPackageSetting.installerPackageName);
+                    }
+                }
+            }
+
+            // Okay!
+            targetPackageSetting.installerPackageName = installerPackageName;
+            scheduleWriteSettingsLocked();
+        }
+    }
+
     public void setPackageObbPath(String packageName, String path) {
         if (DEBUG_OBB)
             Log.v(TAG, "Setting .obb path for " + packageName + " to: " + path);
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index f0cbaa0..615870b1 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -341,6 +341,12 @@
         throw new UnsupportedOperationException();
     }
 
+    @Override
+    public void setInstallerPackageName(String targetPackage,
+            String installerPackageName) {
+        throw new UnsupportedOperationException();
+    }
+
     /**
      * @hide - to match hiding in superclass
      */