Ephemeral cookie API

Add APIs for an ephemeral app to set a cookie which is a small
peice of data cached longer than the app itself. This is useful
for avoiding the user to login every time they use the ephemeral
app. The cookie is stored after an ephemeral app is uninstalled.
Normal apps or ephemeral apps upgraded to full apps can also use
these APIs with the difference that once they are uninstalled
the cookie is deleted.

The cookie size defaults to 16KB and is configurable by a global
settings which can be adjusted via gservices. Also eviction policy
is time based with a default of one month and is configurable by
a global setting which can be adjusted via gservices. If the cert
of the app cahnges (when ephemeral is installed, uninstalled and
installed again) the cooke is wiped to prevent data leaks.

This cahange also adds an API for apps to know whether they run in
an ephemeral mode since it this mode some APIs will not be available.
Another API exposed by this change is private for the system and
exposes all ephemeral apps - installed and uninstalled. Only the
system can call this API. When an ephemeral app is uninstalled the
system stores its name, icon, and permissions. When the app is
reinstalled or a full version is installed the permissions are
propagated.

Change-Id: Id4a73a7750bfbabda0bfcb9bf9018d2062e94367
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index e500e15..460e68c 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -30,6 +30,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ComponentInfo;
 import android.content.pm.ContainerEncryptionParams;
+import android.content.pm.EphemeralApplicationInfo;
 import android.content.pm.FeatureInfo;
 import android.content.pm.IOnPermissionsChangeListener;
 import android.content.pm.IPackageDataObserver;
@@ -85,9 +86,11 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.UserIcons;
+import libcore.util.EmptyArray;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -98,6 +101,8 @@
     private static final String TAG = "ApplicationPackageManager";
     private final static boolean DEBUG_ICONS = false;
 
+    private static final int DEFAULT_EPHEMERAL_COOKIE_MAX_SIZE_BYTES = 16384; // 16KB
+
     // Default flags to use with PackageManager when no flags are given.
     private final static int sDefaultFlags = PackageManager.GET_SHARED_LIBRARY_FILES;
 
@@ -626,6 +631,80 @@
         }
     }
 
+    /** @hide */
+    @SuppressWarnings("unchecked")
+    @Override
+    public List<EphemeralApplicationInfo> getEphemeralApplications() {
+        try {
+            ParceledListSlice<EphemeralApplicationInfo> slice =
+                    mPM.getEphemeralApplications(mContext.getUserId());
+            if (slice != null) {
+                return slice.getList();
+            }
+            return Collections.emptyList();
+        } catch (RemoteException e) {
+            throw new RuntimeException("Package manager has died", e);
+        }
+    }
+
+    /** @hide */
+    @Override
+    public Drawable getEphemeralApplicationIcon(String packageName) {
+        try {
+            Bitmap bitmap = mPM.getEphemeralApplicationIcon(
+                    packageName, mContext.getUserId());
+            if (bitmap != null) {
+                return new BitmapDrawable(null, bitmap);
+            }
+            return null;
+        } catch (RemoteException e) {
+            throw new RuntimeException("Package manager has died", e);
+        }
+    }
+
+    @Override
+    public boolean isEphemeralApplication() {
+        try {
+            return mPM.isEphemeralApplication(
+                    mContext.getPackageName(), mContext.getUserId());
+        } catch (RemoteException e) {
+            Log.e(TAG, "System server is dead", e);
+        }
+        return false;
+    }
+
+    @Override
+    public int getEphemeralCookieMaxSizeBytes() {
+        return Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.EPHEMERAL_COOKIE_MAX_SIZE_BYTES,
+                DEFAULT_EPHEMERAL_COOKIE_MAX_SIZE_BYTES);
+    }
+
+    @Override
+    public @NonNull byte[] getEphemeralCookie() {
+        try {
+            final byte[] cookie = mPM.getEphemeralApplicationCookie(
+                    mContext.getPackageName(), mContext.getUserId());
+            if (cookie != null) {
+                return cookie;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "System server is dead", e);
+        }
+        return EmptyArray.BYTE;
+    }
+
+    @Override
+    public boolean setEphemeralCookie(@NonNull  byte[] cookie) {
+        try {
+            return mPM.setEphemeralApplicationCookie(
+                    mContext.getPackageName(), cookie, mContext.getUserId());
+        } catch (RemoteException e) {
+            Log.e(TAG, "System server is dead", e);
+        }
+        return false;
+    }
+
     @Override
     public ResolveInfo resolveActivity(Intent intent, int flags) {
         return resolveActivityAsUser(intent, flags, mContext.getUserId());
diff --git a/core/java/android/content/pm/ApplicationInfo.aidl b/core/java/android/content/pm/ApplicationInfo.aidl
index 006d1bd..59b0a89 100644
--- a/core/java/android/content/pm/ApplicationInfo.aidl
+++ b/core/java/android/content/pm/ApplicationInfo.aidl
@@ -1,17 +1,17 @@
-/* //device/java/android/android/view/WindowManager.aidl
+/*
 **
 ** Copyright 2007, The Android Open Source Project
 **
-** Licensed under the Apache License, Version 2.0 (the "License"); 
-** you may not use this file except in compliance with the License. 
-** You may obtain a copy of the License at 
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
 **
-**     http://www.apache.org/licenses/LICENSE-2.0 
+**     http://www.apache.org/licenses/LICENSE-2.0
 **
-** Unless required by applicable law or agreed to in writing, software 
-** distributed under the License is distributed on an "AS IS" BASIS, 
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
-** See the License for the specific language governing permissions and 
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
 ** limitations under the License.
 */
 
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 545478c..8db53d9 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -22,7 +22,6 @@
 import android.os.Environment;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
 import android.text.TextUtils;
@@ -383,13 +382,6 @@
     public static final int FLAG_HARDWARE_ACCELERATED = 1<<29;
 
     /**
-     * Value for {@link #flags}: {@code true} if the application is blocked via restrictions
-     * and for most purposes is considered as not installed.
-     * {@hide}
-     */
-    public static final int FLAG_EPHEMERAL = 1<<30;
-
-    /**
      * Value for {@link #flags}: true if code from this application will need to be
      * loaded into other applications' processes. On devices that support multiple
      * instruction sets, this implies the code might be loaded into a process that's
@@ -462,8 +454,8 @@
     public static final int PRIVATE_FLAG_PRIVILEGED = 1<<3;
 
     /**
-     * Value for {@link #flags}: {@code true} if the application has any IntentFiler with some
-     * data URI using HTTP or HTTPS with an associated VIEW action.
+     * Value for {@link #privateFlags}: {@code true} if the application has any IntentFiler
+     * with some data URI using HTTP or HTTPS with an associated VIEW action.
      *
      * {@hide}
      */
@@ -494,6 +486,13 @@
     public static final int PRIVATE_FLAG_AUTOPLAY = 1 << 7;
 
     /**
+     * Value for {@link #flags}: {@code true} if the application is blocked via restrictions
+     * and for most purposes is considered as not installed.
+     * {@hide}
+     */
+    public static final int PRIVATE_FLAG_EPHEMERAL = 1<<8;
+
+    /**
      * When set, at least one component inside this application is encryption aware.
      *
      * @hide
@@ -1083,6 +1082,13 @@
     /**
      * @hide
      */
+    public boolean isEphemeralApp() {
+        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_EPHEMERAL) != 0;
+    }
+
+    /**
+     * @hide
+     */
     @Override protected ApplicationInfo getApplicationInfo() {
         return this;
     }
diff --git a/core/java/android/content/pm/EphemeralApplicationInfo.aidl b/core/java/android/content/pm/EphemeralApplicationInfo.aidl
new file mode 100644
index 0000000..5aaae78
--- /dev/null
+++ b/core/java/android/content/pm/EphemeralApplicationInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+parcelable EphemeralApplicationInfo;
diff --git a/core/java/android/content/pm/EphemeralApplicationInfo.java b/core/java/android/content/pm/EphemeralApplicationInfo.java
new file mode 100644
index 0000000..87663f1
--- /dev/null
+++ b/core/java/android/content/pm/EphemeralApplicationInfo.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class represents the state of an ephemeral app.
+ *
+ * @hide
+ */
+public final class EphemeralApplicationInfo implements Parcelable {
+    private final ApplicationInfo mApplicationInfo;
+
+    private final String mPackageName;
+    private final CharSequence mLabelText;
+
+    private final String[] mRequestedPermissions;
+    private final String[] mGrantedPermissions;
+
+    public EphemeralApplicationInfo(ApplicationInfo appInfo,
+            String[] requestedPermissions, String[] grantedPermissions) {
+        mApplicationInfo = appInfo;
+        mPackageName = null;
+        mLabelText = null;
+        mRequestedPermissions = requestedPermissions;
+        mGrantedPermissions = grantedPermissions;
+    }
+
+    public EphemeralApplicationInfo(String packageName, CharSequence label,
+            String[] requestedPermissions, String[] grantedPermissions) {
+        mApplicationInfo = null;
+        mPackageName = packageName;
+        mLabelText = label;
+        mRequestedPermissions = requestedPermissions;
+        mGrantedPermissions = grantedPermissions;
+    }
+
+    private EphemeralApplicationInfo(Parcel parcel) {
+        mPackageName = parcel.readString();
+        mLabelText = parcel.readCharSequence();
+        mRequestedPermissions = parcel.readStringArray();
+        mGrantedPermissions = parcel.createStringArray();
+        mApplicationInfo = parcel.readParcelable(null);
+    }
+
+    public @NonNull String getPackageName() {
+        if (mApplicationInfo != null) {
+            return mApplicationInfo.packageName;
+        }
+        return mPackageName;
+    }
+
+    public @NonNull CharSequence loadLabel(@NonNull PackageManager packageManager) {
+        if (mApplicationInfo != null) {
+            return mApplicationInfo.loadLabel(packageManager);
+        }
+        return mLabelText;
+    }
+
+    public @NonNull Drawable loadIcon(@NonNull PackageManager packageManager) {
+        if (mApplicationInfo != null) {
+            return mApplicationInfo.loadIcon(packageManager);
+        }
+        return packageManager.getEphemeralApplicationIcon(mPackageName);
+    }
+
+    public @Nullable String[] getRequestedPermissions() {
+        return mRequestedPermissions;
+    }
+
+    public @Nullable String[] getGrantedPermissions() {
+        return mGrantedPermissions;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeString(mPackageName);
+        parcel.writeCharSequence(mLabelText);
+        parcel.writeStringArray(mRequestedPermissions);
+        parcel.writeStringArray(mGrantedPermissions);
+        parcel.writeParcelable(mApplicationInfo, flags);
+    }
+
+    public static final Creator<EphemeralApplicationInfo> CREATOR =
+            new Creator<EphemeralApplicationInfo>() {
+        @Override
+        public EphemeralApplicationInfo createFromParcel(Parcel parcel) {
+            return new EphemeralApplicationInfo(parcel);
+        }
+
+        @Override
+        public EphemeralApplicationInfo[] newArray(int size) {
+            return new EphemeralApplicationInfo[0];
+        }
+    };
+}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index b9a42eb..b947a2b 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -23,6 +23,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ContainerEncryptionParams;
+import android.content.pm.EphemeralApplicationInfo;
 import android.content.pm.FeatureInfo;
 import android.content.pm.IPackageInstallObserver2;
 import android.content.pm.IPackageInstaller;
@@ -47,6 +48,7 @@
 import android.content.pm.UserInfo;
 import android.content.pm.VerificationParams;
 import android.content.pm.VerifierDeviceIdentity;
+import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
@@ -513,4 +515,10 @@
     boolean isPermissionRevokedByPolicy(String permission, String packageName, int userId);
 
     String getPermissionControllerPackageName();
+
+    ParceledListSlice getEphemeralApplications(int userId);
+    byte[] getEphemeralApplicationCookie(String packageName, int userId);
+    boolean setEphemeralApplicationCookie(String packageName, in byte[] cookie, int userId);
+    Bitmap getEphemeralApplicationIcon(String packageName, int userId);
+    boolean isEphemeralApplication(String packageName, int userId);
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 9b80a16..a822150 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -47,8 +47,10 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.storage.VolumeInfo;
+import android.provider.Settings;
 import android.util.AndroidException;
 
+import android.util.Log;
 import com.android.internal.util.ArrayUtils;
 
 import java.io.File;
@@ -2860,6 +2862,83 @@
     public abstract List<ApplicationInfo> getInstalledApplications(int flags);
 
     /**
+     * Gets the ephemeral applications the user recently used. Requires
+     * holding "android.permission.ACCESS_EPHEMERAL_APPS".
+     *
+     * @return The ephemeral app list.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.ACCESS_EPHEMERAL_APPS)
+    public abstract List<EphemeralApplicationInfo> getEphemeralApplications();
+
+    /**
+     * Gets the icon for an ephemeral application.
+     *
+     * @param packageName The app package name.
+     *
+     * @hide
+     */
+    public abstract Drawable getEphemeralApplicationIcon(String packageName);
+
+    /**
+     * Gets whether the caller is an ephemeral app.
+     *
+     * @return Whether caller is an ephemeral app.
+     *
+     * @see #setEphemeralCookie(byte[])
+     * @see #getEphemeralCookie()
+     * @see #getEphemeralCookieMaxSizeBytes()
+     */
+    public abstract boolean isEphemeralApplication();
+
+    /**
+     * Gets the maximum size in bytes of the cookie data an ephemeral app
+     * can store on the device.
+     *
+     * @return The max cookie size in bytes.
+     *
+     * @see #isEphemeralApplication()
+     * @see #setEphemeralCookie(byte[])
+     * @see #getEphemeralCookie()
+     */
+    public abstract int getEphemeralCookieMaxSizeBytes();
+
+    /**
+     * Gets the ephemeral application cookie for this app. Non
+     * ephemeral apps and apps that were ephemeral but were upgraded
+     * to non-ephemeral can still access this API. For ephemeral apps
+     * this cooke is cached for some time after uninstall while for
+     * normal apps the cookie is deleted after the app is uninstalled.
+     * The cookie is always present while the app is installed.
+     *
+     * @return The cookie.
+     *
+     * @see #isEphemeralApplication()
+     * @see #setEphemeralCookie(byte[])
+     * @see #getEphemeralCookieMaxSizeBytes()
+     */
+    public abstract @NonNull byte[] getEphemeralCookie();
+
+    /**
+     * Sets the ephemeral application cookie for the calling app. Non
+     * ephemeral apps and apps that were ephemeral but were upgraded
+     * to non-ephemeral can still access this API. For ephemeral apps
+     * this cooke is cached for some time after uninstall while for
+     * normal apps the cookie is deleted after the app is uninstalled.
+     * The cookie is always present while the app is installed. The
+     * cookie size is limited by {@link #getEphemeralCookieMaxSizeBytes()}.
+     *
+     * @param cookie The cookie data.
+     * @return True if the cookie was set.
+     *
+     * @see #isEphemeralApplication()
+     * @see #getEphemeralCookieMaxSizeBytes()
+     * @see #getEphemeralCookie();
+     */
+    public abstract boolean setEphemeralCookie(@NonNull  byte[] cookie);
+
+    /**
      * Get a list of shared libraries that are available on the
      * system.
      *
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 0307108..b79b6b6 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1470,6 +1470,10 @@
             pkg.applicationInfo.flags |= ApplicationInfo.FLAG_EXTERNAL_STORAGE;
         }
 
+        if ((flags & PARSE_IS_EPHEMERAL) != 0) {
+            pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_EPHEMERAL;
+        }
+
         // Resource boolean are -1, so 1 means we don't know the value.
         int supportsSmallScreens = 1;
         int supportsNormalScreens = 1;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index dad3c67..521aa3c 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7792,6 +7792,24 @@
         public static final String LTE_SERVICE_FORCED = "lte_service_forced";
 
         /**
+         * Ephemeral app cookie max size in bytes.
+         * <p>
+         * Type: int
+         * @hide
+         */
+        public static final String EPHEMERAL_COOKIE_MAX_SIZE_BYTES =
+                "ephemeral_cookie_max_size_bytes";
+
+        /**
+         * The duration for caching uninstalled ephemeral apps.
+         * <p>
+         * Type: long
+         * @hide
+         */
+        public static final String UNINSTALLED_EPHEMERAL_APP_CACHE_DURATION_MILLIS =
+                "uninstalled_ephemeral_app_cache_duration_millis";
+
+        /**
          * Settings to backup. This is here so that it's in the same place as the settings
          * keys and easy to update.
          *