Change ANDROID_ID for Instant Apps

ANDROID_ID for Instant Apps now has the following properties:
1) per-app scoped
2) reset if the user clears the Instant App
3) remains the same if the Instant App gets upgraded to an installed
app.

Note that if the user goes instant -> installed_1 -> uninstall ->
installed_2 the ANDROID_ID at installed_1 will not be the same as
installed_2. This was deemed better than the id changing on the upgrade
step.

Test: manual
Change-Id: I532975c50049c94ff80902a897e001dd35a69f9f
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 027ddf5..2ce02df 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -2630,4 +2630,13 @@
             throw e.rethrowAsRuntimeException();
         }
     }
+
+    @Override
+    public String getInstantAppAndroidId(String packageName, UserHandle user) {
+        try {
+            return mPM.getInstantAppAndroidId(packageName, user.getIdentifier());
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
 }
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index bbc942a..bc7a612 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -634,4 +634,6 @@
     ComponentName getInstantAppResolverSettingsComponent();
 
     ComponentName getInstantAppInstallerComponent();
+
+    String getInstantAppAndroidId(String packageName, int userId);
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 178b967..af0c3b1 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -6293,4 +6293,12 @@
      */
     @SystemApi
     public abstract ComponentName getInstantAppInstallerComponent();
+
+    /**
+     * Return the Android Id for a given Instant App.
+     *
+     * @see {@link android.provider.Settings.Secure#ANDROID_ID}
+     * @hide
+     */
+    public abstract String getInstantAppAndroidId(String packageName, @NonNull UserHandle user);
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f32f163..22de27f 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5082,6 +5082,10 @@
          * (available on certain devices running Android 4.2 or higher), each user appears as a
          * completely separate device, so the {@code ANDROID_ID} value is unique to each
          * user.</p>
+         *
+         * <p class="note"><strong>Note:</strong> If the caller is an Instant App the id is scoped
+         * to the Instant App, it is generated when the Instant App is first installed and reset if
+         * the user clears the Instant App.
          */
         public static final String ANDROID_ID = "android_id";
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 169a034..48429e8 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -1109,6 +1109,33 @@
         // Retrieve the ssaid from the table if present.
         final Setting ssaid = mSettingsRegistry.getSettingLocked(SETTINGS_TYPE_SSAID, owningUserId,
                 name);
+        // If the app is an Instant App use its stored SSAID instead of our own.
+        final String instantSsaid;
+        final long token = Binder.clearCallingIdentity();
+        try {
+            instantSsaid = mPackageManager.getInstantAppAndroidId(callingPkg.packageName,
+                    owningUserId);
+        } catch (RemoteException e) {
+            Slog.e(LOG_TAG, "Failed to get Instant App Android ID", e);
+            return null;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        if (instantSsaid != null) {
+            // Use the stored value if it is still valid.
+            if (ssaid != null && instantSsaid.equals(ssaid.getValue())) {
+                return ssaid;
+            }
+            // The value has changed, update the stored value.
+            final SettingsState ssaidSettings = mSettingsRegistry.getSettingsLocked(
+                    SETTINGS_TYPE_SSAID, owningUserId);
+            final boolean success = ssaidSettings.insertSettingLocked(name, instantSsaid, null,
+                    true, callingPkg.packageName);
+            if (!success) {
+                throw new IllegalStateException("Failed to update instant app android id");
+            }
+            return mSettingsRegistry.getSettingLocked(SETTINGS_TYPE_SSAID, owningUserId, name);
+        }
 
         // Lazy initialize ssaid if not yet present in ssaid table.
         if (ssaid == null || ssaid.isNull() || ssaid.getValue() == null) {
diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java
index 89a303d..46bc69b 100644
--- a/services/core/java/com/android/server/pm/InstantAppRegistry.java
+++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java
@@ -35,6 +35,7 @@
 import android.provider.Settings;
 import android.util.ArrayMap;
 import android.util.AtomicFile;
+import android.util.ByteStringUtils;
 import android.util.PackageUtils;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -56,8 +57,10 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
+import java.security.SecureRandom;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Locale;
 import java.util.Set;
 import java.util.function.Predicate;
 
@@ -81,6 +84,7 @@
     private static final String INSTANT_APP_COOKIE_FILE_PREFIX = "cookie_";
     private static final String INSTANT_APP_COOKIE_FILE_SIFFIX = ".dat";
     private static final String INSTANT_APP_METADATA_FILE = "metadata.xml";
+    private static final String INSTANT_APP_ANDROID_ID_FILE = "android_id";
 
     private static final String TAG_PACKAGE = "package";
     private static final String TAG_PERMISSIONS = "permissions";
@@ -195,6 +199,36 @@
         return null;
     }
 
+    public String getInstantAppAndroidIdLPw(@NonNull String packageName,
+                                            @UserIdInt int userId) {
+        File idFile = new File(getInstantApplicationDir(packageName, userId),
+                INSTANT_APP_ANDROID_ID_FILE);
+        if (idFile.exists()) {
+            try {
+                return IoUtils.readFileAsString(idFile.getAbsolutePath());
+            } catch (IOException e) {
+                Slog.e(LOG_TAG, "Failed to read instant app android id file: " + idFile, e);
+            }
+        }
+        return generateInstantAppAndroidIdLPw(packageName, userId);
+    }
+
+    private String generateInstantAppAndroidIdLPw(@NonNull String packageName,
+                                                @UserIdInt int userId) {
+        byte[] randomBytes = new byte[8];
+        new SecureRandom().nextBytes(randomBytes);
+        String id = ByteStringUtils.toHexString(randomBytes).toLowerCase(Locale.US);
+        File idFile = new File(getInstantApplicationDir(packageName, userId),
+                INSTANT_APP_ANDROID_ID_FILE);
+        try (FileOutputStream fos = new FileOutputStream(idFile)) {
+            fos.write(id.getBytes());
+        } catch (IOException e) {
+            Slog.e(LOG_TAG, "Error writing instant app android id file: " + idFile, e);
+        }
+        return id;
+
+    }
+
     public @Nullable List<InstantAppInfo> getInstantAppsLPr(@UserIdInt int userId) {
         List<InstantAppInfo> installedApps = getInstalledInstantApplicationsLPr(userId);
         List<InstantAppInfo> uninstalledApps = getUninstalledInstantApplicationsLPr(userId);
@@ -462,6 +496,7 @@
         File instantAppDir = getInstantApplicationDir(packageName, userId);
         new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete();
         new File(instantAppDir, INSTANT_APP_ICON_FILE).delete();
+        new File(instantAppDir, INSTANT_APP_ANDROID_ID_FILE).delete();
         File cookie = peekInstantCookieFile(packageName, userId);
         if (cookie != null) {
             cookie.delete();
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 5535d17..74acacc 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -23654,6 +23654,22 @@
         return mInstantAppInstallerActivity == null
                 ? null : mInstantAppInstallerActivity.getComponentName();
     }
+
+    @Override
+    public String getInstantAppAndroidId(String packageName, int userId) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_INSTANT_APPS,
+                "getInstantAppAndroidId");
+        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+                true /* requireFullPermission */, false /* checkShell */,
+                "getInstantAppAndroidId");
+        // Make sure the target is an Instant App.
+        if (!isInstantApp(packageName, userId)) {
+            return null;
+        }
+        synchronized (mPackages) {
+            return mInstantAppRegistry.getInstantAppAndroidIdLPw(packageName, userId);
+        }
+    }
 }
 
 interface PackageSender {
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index 20392e7..589dd0c 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -1134,4 +1134,11 @@
     public ComponentName getInstantAppInstallerComponent() {
         throw new UnsupportedOperationException();
     }
+
+    /**
+     * @hide
+     */
+    public String getInstantAppAndroidId(String packageName, UserHandle user) {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
index ad26bc8..93319a3 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
@@ -928,4 +928,9 @@
     public ComponentName getInstantAppInstallerComponent() {
         return null;
     }
+
+    @Override
+    public String getInstantAppAndroidId(String packageName, UserHandle user) {
+        return null;
+    }
 }