Add the {get,set}PackageObbPaths calls to API

Add getPackageObbPaths() and setPackageObbPaths() to the public API.

Bug: 3214719
Change-Id: Icb9f2f92f8c59bb3d31317f609854e81abbd1449
diff --git a/api/current.xml b/api/current.xml
index 85b367d..c0dcfe6 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -45952,6 +45952,19 @@
 <exception name="PackageManager.NameNotFoundException" type="android.content.pm.PackageManager.NameNotFoundException">
 </exception>
 </method>
+<method name="getPackageObbPaths"
+ return="java.lang.String[]"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="packageName" type="java.lang.String">
+</parameter>
+</method>
 <method name="getPackagesForUid"
  return="java.lang.String[]"
  abstract="true"
@@ -46423,6 +46436,21 @@
 <parameter name="flags" type="int">
 </parameter>
 </method>
+<method name="setPackageObbPaths"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="packageName" type="java.lang.String">
+</parameter>
+<parameter name="paths" type="java.lang.String[]">
+</parameter>
+</method>
 <field name="COMPONENT_ENABLED_STATE_DEFAULT"
  type="int"
  transient="false"
@@ -159105,6 +159133,19 @@
 <exception name="PackageManager.NameNotFoundException" type="android.content.pm.PackageManager.NameNotFoundException">
 </exception>
 </method>
+<method name="getPackageObbPaths"
+ return="java.lang.String[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="packageName" type="java.lang.String">
+</parameter>
+</method>
 <method name="getPackagesForUid"
  return="java.lang.String[]"
  abstract="false"
@@ -159589,6 +159630,21 @@
 <parameter name="path" type="java.lang.String">
 </parameter>
 </method>
+<method name="setPackageObbPaths"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="packageName" type="java.lang.String">
+</parameter>
+<parameter name="paths" type="java.lang.String[]">
+</parameter>
+</method>
 </class>
 <class name="MockResources"
  extends="android.content.res.Resources"
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 7e7cd7a..18ab478 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2700,14 +2700,24 @@
         }
 
         @Override
-        public void setPackageObbPath(String packageName, String path) {
+        public void setPackageObbPaths(String packageName, String[] paths) {
             try {
-                mPM.setPackageObbPath(packageName, path);
+                mPM.setPackageObbPaths(packageName, paths);
             } catch (RemoteException e) {
                 // Should never happen!
             }
         }
 
+        @Override
+        public String[] getPackageObbPaths(String packageName) {
+            try {
+                return mPM.getPackageObbPaths(packageName);
+            } catch (RemoteException e) {
+                // Should never happen!
+            }
+            return null;
+        }
+
         private final ContextImpl mContext;
         private final IPackageManager mPM;
 
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 4cff3bb..44b0c96 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -322,5 +322,6 @@
     boolean setInstallLocation(int loc);
     int getInstallLocation();
 
-    void setPackageObbPath(String packageName, String path);
+    void setPackageObbPaths(in String packageName, in String[] paths);
+    String[] getPackageObbPaths(in String packageName);
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b5d1653..a1c29f7 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2273,15 +2273,30 @@
             String packageName, IPackageMoveObserver observer, int flags);
 
     /**
-     * Sets the Opaque Binary Blob (OBB) file location.
+     * Sets the Opaque Binary Blob (OBB) file path associated with a package
+     * name. The caller must have the
+     * {@link android.Manifest.permission#INSTALL_PACKAGES} permission.
      * <p>
      * NOTE: The existence or format of this file is not currently checked, but
      * it may be in the future.
      * 
      * @param packageName Name of the package with which to associate the .obb
-     *            file
-     * @param path Path on the filesystem to the .obb file
-     * @hide
+     *            file.
+     * @param paths Arrays of paths on the filesystem to the .obb files
+     *            associated with the package.
+     * @see #getPackageObbPaths(String)
      */
-    public abstract void setPackageObbPath(String packageName, String path);
+    public abstract void setPackageObbPaths(String packageName, String[] paths);
+
+    /**
+     * Gets the Opaque Binary Blob (OBB) file path associated with the package.
+     * The caller must be the owner of the package queried or have the
+     * {@link android.Manifest.permission#INSTALL_PACKAGES} permission.
+     * 
+     * @param packageName Name of the package with which to associate the .obb
+     *            file.
+     * @return array of paths to .obb files associated with the package
+     * @see #setPackageObbPaths(String, String[])
+     */
+    public abstract String[] getPackageObbPaths(String packageName);
 }
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
index d5f385b..c8a4593 100755
--- a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
@@ -47,6 +47,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Arrays;
 
 public class PackageManagerTests extends AndroidTestCase {
     private static final boolean localLOGV = true;
@@ -2838,6 +2839,164 @@
         installFromRawResource("install.apk", rapk2, PackageManager.INSTALL_REPLACE_EXISTING, true,
                 fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
     }
+
+    @LargeTest
+    public void testPackageObbPaths_Nonexistent() {
+        try {
+            final PackageManager pm = getPm();
+
+            // Invalid Java package name.
+            pm.getPackageObbPaths("=non-existent");
+
+            fail("Should not be able to get package OBB paths for non-existent package");
+        } catch (IllegalArgumentException e) {
+            // pass
+        }
+    }
+
+    @LargeTest
+    public void testPackageObbPaths_Initial() {
+        InstallParams ip = sampleInstallFromRawResource(PackageManager.INSTALL_INTERNAL, false);
+
+        try {
+            final PackageManager pm = getPm();
+
+            assertEquals("Initial obb paths should be null",
+                    null, pm.getPackageObbPaths(ip.pkg.packageName));
+        } finally {
+            cleanUpInstall(ip);
+        }
+    }
+
+    @LargeTest
+    public void testPackageObbPaths_Null() {
+        InstallParams ip = sampleInstallFromRawResource(PackageManager.INSTALL_INTERNAL, false);
+
+        try {
+            final PackageManager pm = getPm();
+
+            pm.setPackageObbPaths(ip.pkg.packageName, null);
+
+            assertEquals("Returned paths should be null",
+                    null, pm.getPackageObbPaths(ip.pkg.packageName));
+        } finally {
+            cleanUpInstall(ip);
+        }
+    }
+
+    @LargeTest
+    public void testPackageObbPaths_Empty() {
+        InstallParams ip = sampleInstallFromRawResource(PackageManager.INSTALL_INTERNAL, false);
+
+        try {
+            final PackageManager pm = getPm();
+
+            final String[] paths = new String[0];
+
+            pm.setPackageObbPaths(ip.pkg.packageName, paths);
+
+            assertEquals("Empty list should be interpreted as null",
+                    null, pm.getPackageObbPaths(ip.pkg.packageName));
+        } finally {
+            cleanUpInstall(ip);
+        }
+    }
+
+    @LargeTest
+    public void testPackageObbPaths_Single() {
+        InstallParams ip = sampleInstallFromRawResource(PackageManager.INSTALL_INTERNAL, false);
+
+        try {
+            final PackageManager pm = getPm();
+
+            final String[] paths = new String[] {
+                "/example/test",
+            };
+
+            pm.setPackageObbPaths(ip.pkg.packageName, paths.clone());
+
+            assertTrue("Previously set paths should be the same as the returned paths.",
+                    Arrays.equals(paths, pm.getPackageObbPaths(ip.pkg.packageName)));
+        } finally {
+            cleanUpInstall(ip);
+        }
+    }
+
+    @LargeTest
+    public void testPackageObbPaths_Multiple() {
+        InstallParams ip = sampleInstallFromRawResource(PackageManager.INSTALL_INTERNAL, false);
+
+        try {
+            final PackageManager pm = getPm();
+
+            final String[] paths = new String[] {
+                    "/example/test1",
+                    "/example/test2",
+            };
+
+            pm.setPackageObbPaths(ip.pkg.packageName, paths.clone());
+
+            assertTrue("Previously set paths should be the same as the returned paths.",
+                    Arrays.equals(paths, pm.getPackageObbPaths(ip.pkg.packageName)));
+        } finally {
+            cleanUpInstall(ip);
+        }
+    }
+
+    @LargeTest
+    public void testPackageObbPaths_Twice() {
+        InstallParams ip = sampleInstallFromRawResource(PackageManager.INSTALL_INTERNAL, false);
+
+        try {
+            final PackageManager pm = getPm();
+
+            final String[] paths = new String[] {
+                    "/example/test1",
+                    "/example/test2",
+            };
+
+            pm.setPackageObbPaths(ip.pkg.packageName, paths.clone());
+
+            assertTrue("Previously set paths should be the same as the returned paths.",
+                    Arrays.equals(paths, pm.getPackageObbPaths(ip.pkg.packageName)));
+
+            paths[0] = "/example/test3";
+            pm.setPackageObbPaths(ip.pkg.packageName, paths.clone());
+
+            assertTrue("Previously set paths should be the same as the returned paths.",
+                    Arrays.equals(paths, pm.getPackageObbPaths(ip.pkg.packageName)));
+        } finally {
+            cleanUpInstall(ip);
+        }
+    }
+
+    @LargeTest
+    public void testPackageObbPaths_ReplacePackage() {
+        InstallParams ip = sampleInstallFromRawResource(PackageManager.INSTALL_INTERNAL, false);
+
+        try {
+            final PackageManager pm = getPm();
+
+            final String[] paths = new String[] {
+                    "/example/test1",
+                    "/example/test2",
+            };
+
+            pm.setPackageObbPaths(ip.pkg.packageName, paths.clone());
+
+            Log.i(TAG, "Creating replaceReceiver");
+            final GenericReceiver receiver = new ReplaceReceiver(ip.pkg.packageName);
+
+            final int flags = PackageManager.INSTALL_REPLACE_EXISTING;
+            invokeInstallPackage(ip.packageURI, flags, receiver);
+            assertInstall(ip.pkg, flags, ip.pkg.installLocation);
+
+            assertTrue("Previously set paths should be the same as the returned paths.",
+                    Arrays.equals(paths, pm.getPackageObbPaths(ip.pkg.packageName)));
+        } finally {
+            cleanUpInstall(ip);
+        }
+    }
     /*---------- Recommended install location tests ----*/
     /*
      * TODO's
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index b0f3a23..ead9f22 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -102,6 +102,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintWriter;
+import java.lang.reflect.Array;
 import java.security.NoSuchAlgorithmException;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -4577,16 +4578,52 @@
         mHandler.sendMessage(msg);
     }
 
-    public void setPackageObbPath(String packageName, String path) {
+    public void setPackageObbPaths(String packageName, String[] paths) {
         if (DEBUG_OBB)
-            Log.v(TAG, "Setting .obb path for " + packageName + " to: " + path);
-        PackageSetting pkgSetting;
+            Log.v(TAG, "Setting .obb paths for " + packageName + " to: " + Arrays.toString(paths));
+        final int uid = Binder.getCallingUid();
+        final int permission = mContext.checkCallingPermission(
+                android.Manifest.permission.INSTALL_PACKAGES);
+        final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED);
+        if (!allowedByPermission) {
+            throw new SecurityException("Permission denial: attempt to set .obb file from pid="
+                    + Binder.getCallingPid());
+        }
+        synchronized (mPackages) {
+            final PackageSetting pkgSetting = mSettings.mPackages.get(packageName);
+            if (pkgSetting == null) {
+                throw new IllegalArgumentException("Unknown package: " + packageName);
+            }
+
+            if (paths != null) {
+                if (paths.length == 0) {
+                    // Don't bother storing an empty array.
+                    paths = null;
+                } else {
+                    // Don't allow the caller to manipulate our copy of the
+                    // list.
+                    paths = paths.clone();
+                }
+            }
+
+            // Only write settings file if the new and old settings are not the
+            // same.
+            if (!Arrays.equals(paths, pkgSetting.obbPathStrings)) {
+                pkgSetting.obbPathStrings = paths;
+                mSettings.writeLP();
+            }
+        }
+    }
+
+    public String[] getPackageObbPaths(String packageName) {
+        if (DEBUG_OBB)
+            Log.v(TAG, "Getting .obb paths for " + packageName);
         final int uid = Binder.getCallingUid();
         final int permission = mContext.checkCallingPermission(
                 android.Manifest.permission.INSTALL_PACKAGES);
         final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED);
         synchronized (mPackages) {
-            pkgSetting = mSettings.mPackages.get(packageName);
+            final PackageSetting pkgSetting = mSettings.mPackages.get(packageName);
             if (pkgSetting == null) {
                 throw new IllegalArgumentException("Unknown package: " + packageName);
             }
@@ -4595,8 +4632,7 @@
                         + Binder.getCallingPid() + ", uid=" + uid + ", package uid="
                         + pkgSetting.userId);
             }
-            pkgSetting.obbPathString = path;
-            mSettings.writeLP();
+            return pkgSetting.obbPathStrings;
         }
     }
 
@@ -7165,7 +7201,7 @@
                     pw.print("    codePath="); pw.println(ps.codePathString);
                     pw.print("    resourcePath="); pw.println(ps.resourcePathString);
                     pw.print("    nativeLibraryPath="); pw.println(ps.nativeLibraryPathString);
-                    pw.print("    obbPath="); pw.println(ps.obbPathString);
+                    pw.print("    obbPaths="); pw.println(Arrays.toString(ps.obbPathStrings));
                     pw.print("    versionCode="); pw.println(ps.versionCode);
                     if (ps.pkg != null) {
                         pw.print("    versionName="); pw.println(ps.pkg.mVersionName);
@@ -7728,7 +7764,7 @@
         File resourcePath;
         String resourcePathString;
         String nativeLibraryPathString;
-        String obbPathString;
+        String[] obbPathStrings;
         long timeStamp;
         long firstInstallTime;
         long lastUpdateTime;
@@ -8749,8 +8785,15 @@
             if (pkg.installerPackageName != null) {
                 serializer.attribute(null, "installer", pkg.installerPackageName);
             }
-            if (pkg.obbPathString != null) {
-                serializer.attribute(null, "obbPath", pkg.obbPathString);
+            if (pkg.obbPathStrings != null && pkg.obbPathStrings.length > 0) {
+                int N = pkg.obbPathStrings.length;
+                serializer.startTag(null, "obbs");
+                for (int i = 0; i < N; i++) {
+                    serializer.startTag(null, "obb");
+                    serializer.attribute(null, "path", pkg.obbPathStrings[i]);
+                    serializer.endTag(null, "obb");
+                }
+                serializer.endTag(null, "obbs");
             }
             pkg.signatures.writeXml(serializer, "sigs", mPastSignatures);
             if ((pkg.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) {
@@ -9154,7 +9197,6 @@
             String codePathStr = null;
             String resourcePathStr = null;
             String nativeLibraryPathStr = null;
-            String obbPathStr = null;
             String systemStr = null;
             String installerPackageName = null;
             String uidError = null;
@@ -9174,7 +9216,6 @@
                 codePathStr = parser.getAttributeValue(null, "codePath");
                 resourcePathStr = parser.getAttributeValue(null, "resourcePath");
                 nativeLibraryPathStr = parser.getAttributeValue(null, "nativeLibraryPath");
-                obbPathStr = parser.getAttributeValue(null, "obbPath");
                 version = parser.getAttributeValue(null, "version");
                 if (version != null) {
                     try {
@@ -9299,7 +9340,6 @@
                 packageSetting.uidError = "true".equals(uidError);
                 packageSetting.installerPackageName = installerPackageName;
                 packageSetting.nativeLibraryPathString = nativeLibraryPathStr;
-                packageSetting.obbPathString = obbPathStr;
                 final String enabledStr = parser.getAttributeValue(null, "enabled");
                 if (enabledStr != null) {
                     if (enabledStr.equalsIgnoreCase("true")) {
@@ -9347,6 +9387,8 @@
                         readGrantedPermissionsLP(parser,
                                 packageSetting.grantedPermissions);
                         packageSetting.permissionsFixed = true;
+                    } else if (tagName.equals("obbs")) {
+                        readObbPathsLP(packageSetting, parser);
                     } else {
                         reportSettingsProblem(Log.WARN,
                                 "Unknown element under <package>: "
@@ -9551,6 +9593,34 @@
             }
         }
 
+        private void readObbPathsLP(PackageSettingBase packageSetting, XmlPullParser parser)
+                throws XmlPullParserException, IOException {
+            final List<String> obbPaths = new ArrayList<String>();
+            final int outerDepth = parser.getDepth();
+            int type;
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                    continue;
+                }
+
+                final String tagName = parser.getName();
+                if (tagName.equals("obb")) {
+                    final String path = parser.getAttributeValue(null, "path");
+                    obbPaths.add(path);
+                } else {
+                    reportSettingsProblem(Log.WARN, "Unknown element under <obbs>: "
+                            + parser.getName());
+                }
+                XmlUtils.skipCurrentTag(parser);
+            }
+            if (obbPaths.size() == 0) {
+                return;
+            } else {
+                packageSetting.obbPathStrings = obbPaths.toArray(new String[obbPaths.size()]);
+            }
+        }
+
         // Returns -1 if we could not find an available UserId to assign
         private int newUserIdLP(Object obj) {
             // Let's be stupidly inefficient for now...
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index f0cbaa0..4a18b3e 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -490,8 +490,17 @@
         throw new UnsupportedOperationException();
     }
 
-    @Override
     public void setPackageObbPath(String packageName, String path) {
         throw new UnsupportedOperationException();
     }
+
+    @Override
+    public void setPackageObbPaths(String packageName, String[] paths) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String[] getPackageObbPaths(String packageName) {
+        throw new UnsupportedOperationException();
+    }
 }