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/api/current.txt b/api/current.txt
index 2e2a604..6e4b292 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9421,6 +9421,8 @@
method public abstract int getComponentEnabledSetting(android.content.ComponentName);
method public abstract android.graphics.drawable.Drawable getDefaultActivityIcon();
method public abstract android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo);
+ method public abstract byte[] getEphemeralCookie();
+ method public abstract int getEphemeralCookieMaxSizeBytes();
method public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
method public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
method public abstract java.lang.String getInstallerPackageName(java.lang.String);
@@ -9452,6 +9454,7 @@
method public abstract java.lang.CharSequence getUserBadgedLabel(java.lang.CharSequence, android.os.UserHandle);
method public abstract android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo);
method public abstract boolean hasSystemFeature(java.lang.String);
+ method public abstract boolean isEphemeralApplication();
method public abstract boolean isPermissionRevokedByPolicy(java.lang.String, java.lang.String);
method public abstract boolean isSafeMode();
method public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
@@ -9469,6 +9472,7 @@
method public abstract android.content.pm.ResolveInfo resolveService(android.content.Intent, int);
method public abstract void setApplicationEnabledSetting(java.lang.String, int, int);
method public abstract void setComponentEnabledSetting(android.content.ComponentName, int, int);
+ method public abstract boolean setEphemeralCookie(byte[]);
method public abstract void setInstallerPackageName(java.lang.String, java.lang.String);
method public abstract void verifyPendingInstall(int, int);
field public static final int COMPONENT_ENABLED_STATE_DEFAULT = 0; // 0x0
@@ -36407,6 +36411,8 @@
method public android.graphics.drawable.Drawable getDefaultActivityIcon();
method public java.lang.String getDefaultBrowserPackageName(int);
method public android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo);
+ method public byte[] getEphemeralCookie();
+ method public int getEphemeralCookieMaxSizeBytes();
method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
method public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
method public java.lang.String getInstallerPackageName(java.lang.String);
@@ -36437,6 +36443,7 @@
method public java.lang.CharSequence getUserBadgedLabel(java.lang.CharSequence, android.os.UserHandle);
method public android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo);
method public boolean hasSystemFeature(java.lang.String);
+ method public boolean isEphemeralApplication();
method public boolean isPermissionRevokedByPolicy(java.lang.String, java.lang.String);
method public boolean isSafeMode();
method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
@@ -36455,6 +36462,7 @@
method public void setApplicationEnabledSetting(java.lang.String, int, int);
method public void setComponentEnabledSetting(android.content.ComponentName, int, int);
method public boolean setDefaultBrowserPackageName(java.lang.String, int);
+ method public boolean setEphemeralCookie(byte[]);
method public void setInstallerPackageName(java.lang.String, java.lang.String);
method public void verifyPendingInstall(int, int);
}
diff --git a/api/system-current.txt b/api/system-current.txt
index 30be4c7..5360dca 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -9713,6 +9713,8 @@
method public abstract int getComponentEnabledSetting(android.content.ComponentName);
method public abstract android.graphics.drawable.Drawable getDefaultActivityIcon();
method public abstract android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo);
+ method public abstract byte[] getEphemeralCookie();
+ method public abstract int getEphemeralCookieMaxSizeBytes();
method public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
method public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
method public abstract java.lang.String getInstallerPackageName(java.lang.String);
@@ -9746,6 +9748,7 @@
method public abstract android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo);
method public abstract void grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
method public abstract boolean hasSystemFeature(java.lang.String);
+ method public abstract boolean isEphemeralApplication();
method public abstract boolean isPermissionRevokedByPolicy(java.lang.String, java.lang.String);
method public abstract boolean isSafeMode();
method public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
@@ -9765,6 +9768,7 @@
method public abstract void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
method public abstract void setApplicationEnabledSetting(java.lang.String, int, int);
method public abstract void setComponentEnabledSetting(android.content.ComponentName, int, int);
+ method public abstract boolean setEphemeralCookie(byte[]);
method public abstract void setInstallerPackageName(java.lang.String, java.lang.String);
method public abstract void updatePermissionFlags(java.lang.String, java.lang.String, int, int, android.os.UserHandle);
method public abstract void verifyIntentFilter(int, int, java.util.List<java.lang.String>);
@@ -38737,6 +38741,8 @@
method public android.graphics.drawable.Drawable getDefaultActivityIcon();
method public java.lang.String getDefaultBrowserPackageName(int);
method public android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo);
+ method public byte[] getEphemeralCookie();
+ method public int getEphemeralCookieMaxSizeBytes();
method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
method public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
method public java.lang.String getInstallerPackageName(java.lang.String);
@@ -38769,6 +38775,7 @@
method public android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo);
method public void grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
method public boolean hasSystemFeature(java.lang.String);
+ method public boolean isEphemeralApplication();
method public boolean isPermissionRevokedByPolicy(java.lang.String, java.lang.String);
method public boolean isSafeMode();
method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
@@ -38789,6 +38796,7 @@
method public void setApplicationEnabledSetting(java.lang.String, int, int);
method public void setComponentEnabledSetting(android.content.ComponentName, int, int);
method public boolean setDefaultBrowserPackageName(java.lang.String, int);
+ method public boolean setEphemeralCookie(byte[]);
method public void setInstallerPackageName(java.lang.String, java.lang.String);
method public void updatePermissionFlags(java.lang.String, java.lang.String, int, int, android.os.UserHandle);
method public void verifyIntentFilter(int, int, java.util.List<java.lang.String>);
diff --git a/api/test-current.txt b/api/test-current.txt
index 2a34a6c..5f08345 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -9421,6 +9421,8 @@
method public abstract int getComponentEnabledSetting(android.content.ComponentName);
method public abstract android.graphics.drawable.Drawable getDefaultActivityIcon();
method public abstract android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo);
+ method public abstract byte[] getEphemeralCookie();
+ method public abstract int getEphemeralCookieMaxSizeBytes();
method public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
method public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
method public abstract java.lang.String getInstallerPackageName(java.lang.String);
@@ -9452,6 +9454,7 @@
method public abstract java.lang.CharSequence getUserBadgedLabel(java.lang.CharSequence, android.os.UserHandle);
method public abstract android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo);
method public abstract boolean hasSystemFeature(java.lang.String);
+ method public abstract boolean isEphemeralApplication();
method public abstract boolean isPermissionRevokedByPolicy(java.lang.String, java.lang.String);
method public abstract boolean isSafeMode();
method public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
@@ -9469,6 +9472,7 @@
method public abstract android.content.pm.ResolveInfo resolveService(android.content.Intent, int);
method public abstract void setApplicationEnabledSetting(java.lang.String, int, int);
method public abstract void setComponentEnabledSetting(android.content.ComponentName, int, int);
+ method public abstract boolean setEphemeralCookie(byte[]);
method public abstract void setInstallerPackageName(java.lang.String, java.lang.String);
method public abstract void verifyPendingInstall(int, int);
field public static final int COMPONENT_ENABLED_STATE_DEFAULT = 0; // 0x0
@@ -36409,6 +36413,8 @@
method public android.graphics.drawable.Drawable getDefaultActivityIcon();
method public java.lang.String getDefaultBrowserPackageName(int);
method public android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo);
+ method public byte[] getEphemeralCookie();
+ method public int getEphemeralCookieMaxSizeBytes();
method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
method public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
method public java.lang.String getInstallerPackageName(java.lang.String);
@@ -36439,6 +36445,7 @@
method public java.lang.CharSequence getUserBadgedLabel(java.lang.CharSequence, android.os.UserHandle);
method public android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo);
method public boolean hasSystemFeature(java.lang.String);
+ method public boolean isEphemeralApplication();
method public boolean isPermissionRevokedByPolicy(java.lang.String, java.lang.String);
method public boolean isSafeMode();
method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
@@ -36457,6 +36464,7 @@
method public void setApplicationEnabledSetting(java.lang.String, int, int);
method public void setComponentEnabledSetting(android.content.ComponentName, int, int);
method public boolean setDefaultBrowserPackageName(java.lang.String, int);
+ method public boolean setEphemeralCookie(byte[]);
method public void setInstallerPackageName(java.lang.String, java.lang.String);
method public void verifyPendingInstall(int, int);
}
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.
*
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4fda222..5251b20 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2777,6 +2777,12 @@
confirmation UI for full backup/restore -->
<uses-permission android:name="android.permission.CONFIRM_FULL_BACKUP"/>
+
+ <!-- Allows the holder to access the ephemeral applications on the device.
+ @hide -->
+ <permission android:name="android.permission.ACCESS_EPHEMERAL_APPS"
+ android:protectionLevel="signature" />
+
<application android:process="system"
android:persistent="true"
android:hasCode="false"
diff --git a/services/core/java/com/android/server/pm/EphemeralApplicationRegistry.java b/services/core/java/com/android/server/pm/EphemeralApplicationRegistry.java
new file mode 100644
index 0000000..8786350
--- /dev/null
+++ b/services/core/java/com/android/server/pm/EphemeralApplicationRegistry.java
@@ -0,0 +1,698 @@
+/*
+ * 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 com.android.server.pm;
+
+import android.content.Context;
+import android.content.pm.EphemeralApplicationInfo;
+import android.content.pm.PackageParser;
+import android.content.pm.PackageUserState;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Binder;
+import android.os.Environment;
+import android.provider.Settings;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.Xml;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.XmlUtils;
+import libcore.io.IoUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * This class is a part of the package manager service that is responsible
+ * for managing data associated with ephemeral apps such as cached uninstalled
+ * ephemeral apps and ephemeral apps' cookies.
+ */
+class EphemeralApplicationRegistry {
+ private static final boolean DEBUG = false;
+
+ private static final String LOG_TAG = "EphemeralAppRegistry";
+
+ private static final long DEFAULT_UNINSTALLED_EPHEMERAL_APP_CACHE_DURATION_MILLIS =
+ DEBUG ? 60 * 1000L /* one min */ : 30 * 24 * 60 * 60 * 1000L; /* one month */
+
+ private final static char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
+
+ private static final String EPHEMERAL_APPS_FOLDER = "ephemeral";
+ private static final String EPHEMERAL_APP_ICON_FILE = "icon.png";
+ private static final String EPHEMERAL_APP_COOKIE_FILE_PREFIX = "cookie_";
+ private static final String EPHEMERAL_APP_COOKIE_FILE_SIFFIX = ".dat";
+ private static final String EPHEMERAL_APP_METADATA_FILE = "metadata.xml";
+
+ private static final String TAG_PACKAGE = "package";
+ private static final String TAG_PERMS = "perms";
+ private static final String TAG_PERM = "perm";
+
+ private static final String ATTR_LABEL = "label";
+ private static final String ATTR_NAME = "name";
+ private static final String ATTR_GRANTED = "granted";
+
+ private final PackageManagerService mService;
+
+ @GuardedBy("mService.mPackages")
+ private SparseArray<List<UninstalledEphemeralAppState>> mUninstalledEphemeralApps;
+
+ public EphemeralApplicationRegistry(PackageManagerService service) {
+ mService = service;
+ }
+
+ public byte[] getEphemeralApplicationCookieLPw(String packageName, int userId) {
+ pruneUninstalledEphemeralAppsLPw(userId);
+
+ File cookieFile = peekEphemeralCookieFile(packageName, userId);
+ if (cookieFile != null && cookieFile.exists()) {
+ try {
+ return IoUtils.readFileAsByteArray(cookieFile.toString());
+ } catch (IOException e) {
+ Slog.w(LOG_TAG, "Error reading cookie file: " + cookieFile);
+ }
+ }
+ return null;
+ }
+
+ public boolean setEphemeralApplicationCookieLPw(String packageName,
+ byte[] cookie, int userId) {
+ pruneUninstalledEphemeralAppsLPw(userId);
+
+ PackageParser.Package pkg = mService.mPackages.get(packageName);
+ if (pkg == null) {
+ return false;
+ }
+
+ if (!isValidCookie(mService.mContext, cookie)) {
+ return false;
+ }
+
+ File appDir = getEphemeralApplicationDir(pkg.packageName, userId);
+ if (!appDir.exists() && !appDir.mkdirs()) {
+ return false;
+ }
+
+ File cookieFile = computeEphemeralCookieFile(pkg, userId);
+ if (cookieFile.exists() && !cookieFile.delete()) {
+ return false;
+ }
+
+ try (FileOutputStream fos = new FileOutputStream(cookieFile)) {
+ fos.write(cookie, 0, cookie.length);
+ } catch (IOException e) {
+ Slog.w(LOG_TAG, "Error writing cookie file: " + cookieFile);
+ return false;
+ }
+ return true;
+ }
+
+ public Bitmap getEphemeralApplicationIconLPw(String packageName, int userId) {
+ pruneUninstalledEphemeralAppsLPw(userId);
+
+ File iconFile = new File(getEphemeralApplicationDir(packageName, userId),
+ EPHEMERAL_APP_ICON_FILE);
+ if (iconFile.exists()) {
+ return BitmapFactory.decodeFile(iconFile.toString());
+ }
+ return null;
+ }
+
+ public List<EphemeralApplicationInfo> getEphemeralApplicationsLPw(int userId) {
+ pruneUninstalledEphemeralAppsLPw(userId);
+
+ List<EphemeralApplicationInfo> result = getInstalledEphemeralApplicationsLPr(userId);
+ result.addAll(getUninstalledEphemeralApplicationsLPr(userId));
+ return result;
+ }
+
+ public void onPackageInstalledLPw(PackageParser.Package pkg) {
+ PackageSetting ps = (PackageSetting) pkg.mExtras;
+ if (ps == null) {
+ return;
+ }
+ for (int userId : UserManagerService.getInstance().getUserIds()) {
+ pruneUninstalledEphemeralAppsLPw(userId);
+
+ // Ignore not installed apps
+ if (mService.mPackages.get(pkg.packageName) == null || !ps.getInstalled(userId)) {
+ continue;
+ }
+
+ // Propagate permissions before removing any state
+ propagateEphemeralAppPermissionsIfNeeded(pkg, userId);
+
+ // Remove the in-memory state
+ if (mUninstalledEphemeralApps != null) {
+ List<UninstalledEphemeralAppState> uninstalledAppStates =
+ mUninstalledEphemeralApps.get(userId);
+ if (uninstalledAppStates != null) {
+ final int appCount = uninstalledAppStates.size();
+ for (int i = 0; i < appCount; i++) {
+ UninstalledEphemeralAppState uninstalledAppState =
+ uninstalledAppStates.get(i);
+ if (uninstalledAppState.mEphemeralApplicationInfo
+ .getPackageName().equals(pkg.packageName)) {
+ uninstalledAppStates.remove(i);
+ break;
+ }
+ }
+ }
+ }
+
+ // Remove the on-disk state except the cookie
+ File ephemeralAppDir = getEphemeralApplicationDir(pkg.packageName, userId);
+ new File(ephemeralAppDir, EPHEMERAL_APP_METADATA_FILE).delete();
+ new File(ephemeralAppDir, EPHEMERAL_APP_ICON_FILE).delete();
+
+ // If app signature changed - wipe the cookie
+ File currentCookieFile = peekEphemeralCookieFile(pkg.packageName, userId);
+ if (currentCookieFile == null) {
+ continue;
+ }
+ File expectedCookeFile = computeEphemeralCookieFile(pkg, userId);
+ if (!currentCookieFile.equals(expectedCookeFile)) {
+ Slog.i(LOG_TAG, "Signature for package " + pkg.packageName
+ + " changed - dropping cookie");
+ currentCookieFile.delete();
+ }
+ }
+ }
+
+ public void onPackageUninstalledLPw(PackageParser.Package pkg) {
+ PackageSetting ps = (PackageSetting) pkg.mExtras;
+ if (ps == null) {
+ return;
+ }
+ for (int userId : UserManagerService.getInstance().getUserIds()) {
+ pruneUninstalledEphemeralAppsLPw(userId);
+
+ if (mService.mPackages.get(pkg.packageName) != null && ps.getInstalled(userId)) {
+ continue;
+ }
+
+ if (pkg.applicationInfo.isEphemeralApp()) {
+ // Add a record for an uninstalled ephemeral app
+ addUninstalledEphemeralAppLPw(pkg, userId);
+ } else {
+ // Deleting an app prunes all ephemeral state such as cookie
+ deleteDir(getEphemeralApplicationDir(pkg.packageName, userId));
+ }
+ }
+ }
+
+ public void onUserRemovedLPw(int userId) {
+ if (mUninstalledEphemeralApps != null) {
+ mUninstalledEphemeralApps.remove(userId);
+ }
+ deleteDir(getEphemeralApplicationsDir(userId));
+ }
+
+ private void addUninstalledEphemeralAppLPw(PackageParser.Package pkg, int userId) {
+ EphemeralApplicationInfo uninstalledApp = createEphemeralAppInfoForPackage(pkg, userId);
+ if (uninstalledApp == null) {
+ return;
+ }
+ if (mUninstalledEphemeralApps == null) {
+ mUninstalledEphemeralApps = new SparseArray<>();
+ }
+ List<UninstalledEphemeralAppState> uninstalledAppStates =
+ mUninstalledEphemeralApps.get(userId);
+ if (uninstalledAppStates == null) {
+ uninstalledAppStates = new ArrayList<>();
+ mUninstalledEphemeralApps.put(userId, uninstalledAppStates);
+ }
+ UninstalledEphemeralAppState uninstalledAppState = new UninstalledEphemeralAppState(
+ uninstalledApp, System.currentTimeMillis());
+ uninstalledAppStates.add(uninstalledAppState);
+
+ writeUninstalledEphemeralAppMetadata(uninstalledApp, userId);
+ writeEphemeralApplicationIconLPw(pkg, userId);
+ }
+
+ private void writeEphemeralApplicationIconLPw(PackageParser.Package pkg, int userId) {
+ File appDir = getEphemeralApplicationDir(pkg.packageName, userId);
+ if (!appDir.exists()) {
+ return;
+ }
+
+ Drawable icon = pkg.applicationInfo.loadIcon(mService.mContext.getPackageManager());
+
+ final Bitmap bitmap;
+ if (icon instanceof BitmapDrawable) {
+ bitmap = ((BitmapDrawable) icon).getBitmap();
+ } else {
+ bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(),
+ icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ icon.draw(canvas);
+ }
+
+ File iconFile = new File(getEphemeralApplicationDir(pkg.packageName, userId),
+ EPHEMERAL_APP_ICON_FILE);
+
+ try (FileOutputStream out = new FileOutputStream(iconFile)) {
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
+ } catch (Exception e) {
+ Slog.e(LOG_TAG, "Error writing ephemeral app icon", e);
+ }
+ }
+
+ private void pruneUninstalledEphemeralAppsLPw(int userId) {
+ final long maxCacheDurationMillis = Settings.Global.getLong(
+ mService.mContext.getContentResolver(),
+ Settings.Global.UNINSTALLED_EPHEMERAL_APP_CACHE_DURATION_MILLIS,
+ DEFAULT_UNINSTALLED_EPHEMERAL_APP_CACHE_DURATION_MILLIS);
+
+ // Prune in-memory state
+ if (mUninstalledEphemeralApps != null) {
+ List<UninstalledEphemeralAppState> uninstalledAppStates =
+ mUninstalledEphemeralApps.get(userId);
+ if (uninstalledAppStates != null) {
+ final int appCount = uninstalledAppStates.size();
+ for (int j = appCount - 1; j >= 0; j--) {
+ UninstalledEphemeralAppState uninstalledAppState = uninstalledAppStates.get(j);
+ final long elapsedCachingMillis = System.currentTimeMillis()
+ - uninstalledAppState.mTimestamp;
+ if (elapsedCachingMillis > maxCacheDurationMillis) {
+ uninstalledAppStates.remove(j);
+ }
+ }
+ if (uninstalledAppStates.isEmpty()) {
+ mUninstalledEphemeralApps.remove(userId);
+ }
+ }
+ }
+
+ // Prune on-disk state
+ File ephemeralAppsDir = getEphemeralApplicationsDir(userId);
+ if (!ephemeralAppsDir.exists()) {
+ return;
+ }
+ File[] files = ephemeralAppsDir.listFiles();
+ if (files == null) {
+ return;
+ }
+ for (File ephemeralDir : files) {
+ if (!ephemeralDir.isDirectory()) {
+ continue;
+ }
+
+ File metadataFile = new File(ephemeralDir, EPHEMERAL_APP_METADATA_FILE);
+ if (!metadataFile.exists()) {
+ continue;
+ }
+
+ final long elapsedCachingMillis = System.currentTimeMillis()
+ - metadataFile.lastModified();
+ if (elapsedCachingMillis > maxCacheDurationMillis) {
+ deleteDir(ephemeralDir);
+ }
+ }
+ }
+
+ private List<EphemeralApplicationInfo> getInstalledEphemeralApplicationsLPr(int userId) {
+ List<EphemeralApplicationInfo> result = null;
+
+ final int packageCount = mService.mPackages.size();
+ for (int i = 0; i < packageCount; i++) {
+ PackageParser.Package pkg = mService.mPackages.valueAt(i);
+ if (!pkg.applicationInfo.isEphemeralApp()) {
+ continue;
+ }
+ EphemeralApplicationInfo info = createEphemeralAppInfoForPackage(pkg, userId);
+ if (info == null) {
+ continue;
+ }
+ if (result == null) {
+ result = new ArrayList<>();
+ }
+ result.add(info);
+ }
+
+ return result;
+ }
+
+ private EphemeralApplicationInfo createEphemeralAppInfoForPackage(
+ PackageParser.Package pkg, int userId) {
+ PackageSetting ps = (PackageSetting) pkg.mExtras;
+ if (ps == null) {
+ return null;
+ }
+ PackageUserState userState = ps.readUserState(userId);
+ if (userState == null || !userState.installed || userState.hidden) {
+ return null;
+ }
+
+ String[] requestedPermissions = new String[pkg.requestedPermissions.size()];
+ pkg.requestedPermissions.toArray(requestedPermissions);
+
+ Set<String> permissions = ps.getPermissionsState().getPermissions(userId);
+ String[] grantedPermissions = new String[permissions.size()];
+ permissions.toArray(grantedPermissions);
+
+ return new EphemeralApplicationInfo(pkg.applicationInfo,
+ requestedPermissions, grantedPermissions);
+ }
+
+ private List<EphemeralApplicationInfo> getUninstalledEphemeralApplicationsLPr(int userId) {
+ List<UninstalledEphemeralAppState> uninstalledAppStates =
+ getUninstalledEphemeralAppStatesLPr(userId);
+ if (uninstalledAppStates == null || uninstalledAppStates.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ List<EphemeralApplicationInfo> uninstalledApps = new ArrayList<>();
+ final int stateCount = uninstalledAppStates.size();
+ for (int i = 0; i < stateCount; i++) {
+ UninstalledEphemeralAppState uninstalledAppState = uninstalledAppStates.get(i);
+ uninstalledApps.add(uninstalledAppState.mEphemeralApplicationInfo);
+ }
+ return uninstalledApps;
+ }
+
+ private void propagateEphemeralAppPermissionsIfNeeded(PackageParser.Package pkg, int userId) {
+ EphemeralApplicationInfo appInfo = getOrParseUninstalledEphemeralAppInfo(pkg.packageName, userId);
+ if (appInfo == null) {
+ return;
+ }
+ if (ArrayUtils.isEmpty(appInfo.getGrantedPermissions())) {
+ return;
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ for (String grantedPermission : appInfo.getGrantedPermissions()) {
+ mService.grantRuntimePermission(pkg.packageName, grantedPermission, userId);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ private EphemeralApplicationInfo getOrParseUninstalledEphemeralAppInfo(String packageName,
+ int userId) {
+ if (mUninstalledEphemeralApps != null) {
+ List<UninstalledEphemeralAppState> uninstalledAppStates =
+ mUninstalledEphemeralApps.get(userId);
+ if (uninstalledAppStates != null) {
+ final int appCount = uninstalledAppStates.size();
+ for (int i = 0; i < appCount; i++) {
+ UninstalledEphemeralAppState uninstalledAppState = uninstalledAppStates.get(i);
+ if (uninstalledAppState.mEphemeralApplicationInfo
+ .getPackageName().equals(packageName)) {
+ return uninstalledAppState.mEphemeralApplicationInfo;
+ }
+ }
+ }
+ }
+
+ File metadataFile = new File(getEphemeralApplicationDir(packageName, userId),
+ EPHEMERAL_APP_METADATA_FILE);
+ UninstalledEphemeralAppState uninstalledAppState = parseMetadataFile(metadataFile);
+ if (uninstalledAppState == null) {
+ return null;
+ }
+
+ return uninstalledAppState.mEphemeralApplicationInfo;
+ }
+
+ private List<UninstalledEphemeralAppState> getUninstalledEphemeralAppStatesLPr(int userId) {
+ List<UninstalledEphemeralAppState> uninstalledAppStates = null;
+ if (mUninstalledEphemeralApps != null) {
+ uninstalledAppStates = mUninstalledEphemeralApps.get(userId);
+ if (uninstalledAppStates != null) {
+ return uninstalledAppStates;
+ }
+ }
+
+ File ephemeralAppsDir = getEphemeralApplicationsDir(userId);
+ if (ephemeralAppsDir.exists()) {
+ File[] files = ephemeralAppsDir.listFiles();
+ if (files != null) {
+ for (File ephemeralDir : files) {
+ if (!ephemeralDir.isDirectory()) {
+ continue;
+ }
+ File metadataFile = new File(ephemeralDir,
+ EPHEMERAL_APP_METADATA_FILE);
+ UninstalledEphemeralAppState uninstalledAppState =
+ parseMetadataFile(metadataFile);
+ if (uninstalledAppState == null) {
+ continue;
+ }
+ if (uninstalledAppStates == null) {
+ uninstalledAppStates = new ArrayList<>();
+ }
+ uninstalledAppStates.add(uninstalledAppState);
+ }
+ }
+ }
+
+ if (uninstalledAppStates != null) {
+ if (mUninstalledEphemeralApps == null) {
+ mUninstalledEphemeralApps = new SparseArray<>();
+ }
+ mUninstalledEphemeralApps.put(userId, uninstalledAppStates);
+ }
+
+ return uninstalledAppStates;
+ }
+
+ private static boolean isValidCookie(Context context, byte[] cookie) {
+ if (ArrayUtils.isEmpty(cookie)) {
+ return true;
+ }
+ return cookie.length <= context.getPackageManager().getEphemeralCookieMaxSizeBytes();
+ }
+
+ private static UninstalledEphemeralAppState parseMetadataFile(File metadataFile) {
+ if (!metadataFile.exists()) {
+ return null;
+ }
+ FileInputStream in;
+ try {
+ in = new AtomicFile(metadataFile).openRead();
+ } catch (FileNotFoundException fnfe) {
+ Slog.i(LOG_TAG, "No ephemeral metadata file");
+ return null;
+ }
+
+ final File ephemeralDir = metadataFile.getParentFile();
+ final long timestamp = metadataFile.lastModified();
+ final String packageName = ephemeralDir.getName();
+
+ try {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(in, StandardCharsets.UTF_8.name());
+ return new UninstalledEphemeralAppState(
+ parseMetadata(parser, packageName), timestamp);
+ } catch (XmlPullParserException | IOException e) {
+ throw new IllegalStateException("Failed parsing ephemeral"
+ + " metadata file: " + metadataFile, e);
+ } finally {
+ IoUtils.closeQuietly(in);
+ }
+ }
+
+ private static File computeEphemeralCookieFile(PackageParser.Package pkg, int userId) {
+ File appDir = getEphemeralApplicationDir(pkg.packageName, userId);
+ String cookieFile = EPHEMERAL_APP_COOKIE_FILE_PREFIX + computePackageCertDigest(pkg)
+ + EPHEMERAL_APP_COOKIE_FILE_SIFFIX;
+ return new File(appDir, cookieFile);
+ }
+
+ private static File peekEphemeralCookieFile(String packageName, int userId) {
+ File appDir = getEphemeralApplicationDir(packageName, userId);
+ if (!appDir.exists()) {
+ return null;
+ }
+ for (File file : appDir.listFiles()) {
+ if (!file.isDirectory()
+ && file.getName().startsWith(EPHEMERAL_APP_COOKIE_FILE_PREFIX)
+ && file.getName().endsWith(EPHEMERAL_APP_COOKIE_FILE_SIFFIX)) {
+ return file;
+ }
+ }
+ return null;
+ }
+
+ private static EphemeralApplicationInfo parseMetadata(XmlPullParser parser, String packageName)
+ throws IOException, XmlPullParserException {
+ final int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ if (TAG_PACKAGE.equals(parser.getName())) {
+ return parsePackage(parser, packageName);
+ }
+ }
+ return null;
+ }
+
+ private static EphemeralApplicationInfo parsePackage(XmlPullParser parser, String packageName)
+ throws IOException, XmlPullParserException {
+ String label = parser.getAttributeValue(null, ATTR_LABEL);
+
+ List<String> outRequestedPermissions = new ArrayList<>();
+ List<String> outGrantedPermissions = new ArrayList<>();
+
+ final int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ if (TAG_PERMS.equals(parser.getName())) {
+ parsePermissions(parser, outRequestedPermissions, outGrantedPermissions);
+ }
+ }
+
+ String[] requestedPermissions = new String[outRequestedPermissions.size()];
+ outRequestedPermissions.toArray(requestedPermissions);
+
+ String[] grantedPermissions = new String[outGrantedPermissions.size()];
+ outGrantedPermissions.toArray(grantedPermissions);
+
+ return new EphemeralApplicationInfo(packageName, label,
+ requestedPermissions, grantedPermissions);
+ }
+
+ private static void parsePermissions(XmlPullParser parser, List<String> outRequestedPermissions,
+ List<String> outGrantedPermissions) throws IOException, XmlPullParserException {
+ final int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser,outerDepth)) {
+ if (TAG_PERM.equals(parser.getName())) {
+ String permission = XmlUtils.readStringAttribute(parser, ATTR_NAME);
+ outRequestedPermissions.add(permission);
+ if (XmlUtils.readBooleanAttribute(parser, ATTR_GRANTED)) {
+ outGrantedPermissions.add(permission);
+ }
+ }
+ }
+ }
+
+ private void writeUninstalledEphemeralAppMetadata(
+ EphemeralApplicationInfo ephemeralApp, int userId) {
+ File appDir = getEphemeralApplicationDir(ephemeralApp.getPackageName(), userId);
+ if (!appDir.exists() && !appDir.mkdirs()) {
+ return;
+ }
+
+ File metadataFile = new File(appDir, EPHEMERAL_APP_METADATA_FILE);
+
+ AtomicFile destination = new AtomicFile(metadataFile);
+ FileOutputStream out = null;
+ try {
+ out = destination.startWrite();
+
+ XmlSerializer serializer = Xml.newSerializer();
+ serializer.setOutput(out, StandardCharsets.UTF_8.name());
+ serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+
+ serializer.startDocument(null, true);
+
+ serializer.startTag(null, TAG_PACKAGE);
+ serializer.attribute(null, ATTR_LABEL, ephemeralApp.loadLabel(
+ mService.mContext.getPackageManager()).toString());
+
+ serializer.startTag(null, TAG_PERMS);
+ for (String permission : ephemeralApp.getRequestedPermissions()) {
+ serializer.startTag(null, TAG_PERM);
+ serializer.attribute(null, ATTR_NAME, permission);
+ if (ArrayUtils.contains(ephemeralApp.getGrantedPermissions(), permission)) {
+ serializer.attribute(null, ATTR_GRANTED, String.valueOf(true));
+ }
+ serializer.endTag(null, TAG_PERM);
+ }
+ serializer.endTag(null, TAG_PERMS);
+
+ serializer.endTag(null, TAG_PACKAGE);
+
+ serializer.endDocument();
+ destination.finishWrite(out);
+ } catch (Throwable t) {
+ Slog.wtf(LOG_TAG, "Failed to write ephemeral state, restoring backup", t);
+ destination.failWrite(out);
+ } finally {
+ IoUtils.closeQuietly(out);
+ }
+ }
+
+ private static String computePackageCertDigest(PackageParser.Package pkg) {
+ MessageDigest messageDigest;
+ try {
+ messageDigest = MessageDigest.getInstance("SHA256");
+ } catch (NoSuchAlgorithmException e) {
+ /* can't happen */
+ return null;
+ }
+
+ messageDigest.update(pkg.mSignatures[0].toByteArray());
+
+ final byte[] digest = messageDigest.digest();
+ final int digestLength = digest.length;
+ final int charCount = 2 * digestLength;
+
+ final char[] chars = new char[charCount];
+ for (int i = 0; i < digestLength; i++) {
+ final int byteHex = digest[i] & 0xFF;
+ chars[i * 2] = HEX_ARRAY[byteHex >>> 4];
+ chars[i * 2 + 1] = HEX_ARRAY[byteHex & 0x0F];
+ }
+ return new String(chars);
+ }
+
+ private static File getEphemeralApplicationsDir(int userId) {
+ return new File(Environment.getUserSystemDirectory(userId),
+ EPHEMERAL_APPS_FOLDER);
+ }
+
+ private static File getEphemeralApplicationDir(String packageName, int userId) {
+ return new File (getEphemeralApplicationsDir(userId), packageName);
+ }
+
+ private static void deleteDir(File dir) {
+ File[] files = dir.listFiles();
+ if (files != null) {
+ for (File file : dir.listFiles()) {
+ deleteDir(file);
+ }
+ }
+ dir.delete();
+ }
+
+ private static final class UninstalledEphemeralAppState {
+ final EphemeralApplicationInfo mEphemeralApplicationInfo;
+ final long mTimestamp;
+
+ public UninstalledEphemeralAppState(EphemeralApplicationInfo ephemeralApp,
+ long timestamp) {
+ mEphemeralApplicationInfo = ephemeralApp;
+ mTimestamp = timestamp;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ab8e5a7..2f8157e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -105,6 +105,7 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.AppsQueryHelper;
+import android.content.pm.EphemeralApplicationInfo;
import android.content.pm.FeatureInfo;
import android.content.pm.IOnPermissionsChangeListener;
import android.content.pm.IPackageDataObserver;
@@ -144,6 +145,7 @@
import android.content.pm.VerifierDeviceIdentity;
import android.content.pm.VerifierInfo;
import android.content.res.Resources;
+import android.graphics.Bitmap;
import android.hardware.display.DisplayManager;
import android.net.Uri;
import android.os.Debug;
@@ -200,6 +202,7 @@
import android.util.Xml;
import android.view.Display;
+import com.android.internal.annotations.GuardedBy;
import dalvik.system.DexFile;
import dalvik.system.VMRuntime;
@@ -207,7 +210,6 @@
import libcore.util.EmptyArray;
import com.android.internal.R;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.EphemeralResolveInfo;
import com.android.internal.app.IMediaContainerService;
import com.android.internal.app.ResolverActivity;
@@ -512,6 +514,8 @@
// If a recursive restorecon of /data/data/<pkg> is needed.
private boolean mShouldRestoreconData = SELinuxMMAC.shouldRestorecon();
+ private final EphemeralApplicationRegistry mEphemeralApplicationRegistry;
+
public static final class SharedLibraryEntry {
public final String path;
public final String apk;
@@ -1396,6 +1400,10 @@
args.installGrantPermissions);
}
+ synchronized (mPackages) {
+ mEphemeralApplicationRegistry.onPackageInstalledLPw(res.pkg);
+ }
+
// Determine the set of users who are adding this
// package for the first time vs. those who are seeing
// an update.
@@ -2405,6 +2413,8 @@
mEphemeralInstallerComponent = null;
mEphemeralResolverConnection = null;
}
+
+ mEphemeralApplicationRegistry = new EphemeralApplicationRegistry(this);
} // synchronized (mPackages)
} // synchronized (mInstallLock)
@@ -5784,6 +5794,82 @@
}
}
+ @Override
+ public ParceledListSlice<EphemeralApplicationInfo> getEphemeralApplications(int userId) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_EPHEMERAL_APPS,
+ "getEphemeralApplications");
+ enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false,
+ "getEphemeralApplications");
+ synchronized (mPackages) {
+ List<EphemeralApplicationInfo> ephemeralApps = mEphemeralApplicationRegistry
+ .getEphemeralApplicationsLPw(userId);
+ if (ephemeralApps != null) {
+ return new ParceledListSlice<>(ephemeralApps);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean isEphemeralApplication(String packageName, int userId) {
+ enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false,
+ "isEphemeral");
+ if (!isCallerSameApp(packageName)) {
+ return false;
+ }
+ synchronized (mPackages) {
+ PackageParser.Package pkg = mPackages.get(packageName);
+ if (pkg != null) {
+ return pkg.applicationInfo.isEphemeralApp();
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public byte[] getEphemeralApplicationCookie(String packageName, int userId) {
+ enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false,
+ "getCookie");
+ if (!isCallerSameApp(packageName)) {
+ return null;
+ }
+ synchronized (mPackages) {
+ return mEphemeralApplicationRegistry.getEphemeralApplicationCookieLPw(
+ packageName, userId);
+ }
+ }
+
+ @Override
+ public boolean setEphemeralApplicationCookie(String packageName, byte[] cookie, int userId) {
+ enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false,
+ "setCookie");
+ if (!isCallerSameApp(packageName)) {
+ return false;
+ }
+ synchronized (mPackages) {
+ return mEphemeralApplicationRegistry.setEphemeralApplicationCookieLPw(
+ packageName, cookie, userId);
+ }
+ }
+
+ @Override
+ public Bitmap getEphemeralApplicationIcon(String packageName, int userId) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_EPHEMERAL_APPS,
+ "getEphemeralApplicationIcon");
+ enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false,
+ "getEphemeralApplicationIcon");
+ synchronized (mPackages) {
+ return mEphemeralApplicationRegistry.getEphemeralApplicationIconLPw(
+ packageName, userId);
+ }
+ }
+
+ private boolean isCallerSameApp(String packageName) {
+ PackageParser.Package pkg = mPackages.get(packageName);
+ return pkg != null
+ && UserHandle.getAppId(Binder.getCallingUid()) == pkg.applicationInfo.uid;
+ }
+
public List<ApplicationInfo> getPersistentApplications(int flags) {
final ArrayList<ApplicationInfo> finalList = new ArrayList<ApplicationInfo>();
@@ -12270,8 +12356,7 @@
// First find the old package info and check signatures
synchronized(mPackages) {
oldPackage = mPackages.get(pkgName);
- final boolean oldIsEphemeral
- = ((oldPackage.applicationInfo.flags & ApplicationInfo.FLAG_EPHEMERAL) != 0);
+ final boolean oldIsEphemeral = oldPackage.applicationInfo.isEphemeralApp();
if (isEphemeral && !oldIsEphemeral) {
// can't downgrade from full to ephemeral
Slog.w(TAG, "Can't replace app with ephemeral: " + pkgName);
@@ -13090,11 +13175,11 @@
}
private static boolean isEphemeral(PackageParser.Package pkg) {
- return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_EPHEMERAL) != 0;
+ return pkg.applicationInfo.isEphemeralApp();
}
private static boolean isEphemeral(PackageSetting ps) {
- return (ps.pkgFlags & ApplicationInfo.FLAG_EPHEMERAL) != 0;
+ return ps.pkg != null && isEphemeral(ps.pkg);
}
private static boolean isSystemApp(PackageParser.Package pkg) {
@@ -13295,11 +13380,14 @@
boolean removedForAllUsers = false;
boolean systemUpdate = false;
+ PackageParser.Package uninstalledPkg;
+
// for the uninstall-updates case and restricted profiles, remember the per-
// userhandle installed state
int[] allUsers;
boolean[] perUserInstalled;
synchronized (mPackages) {
+ uninstalledPkg = mPackages.get(packageName);
PackageSetting ps = mSettings.mPackages.get(packageName);
allUsers = sUserManager.getUserIds();
perUserInstalled = new boolean[allUsers.length];
@@ -13314,8 +13402,13 @@
true, allUsers, perUserInstalled,
flags | REMOVE_CHATTY, info, true);
systemUpdate = info.isRemovedPackageSystemUpdate;
- if (res && !systemUpdate && mPackages.get(packageName) == null) {
- removedForAllUsers = true;
+ synchronized (mPackages) {
+ if (res) {
+ if (!systemUpdate && mPackages.get(packageName) == null) {
+ removedForAllUsers = true;
+ }
+ mEphemeralApplicationRegistry.onPackageUninstalledLPw(uninstalledPkg);
+ }
}
if (DEBUG_REMOVE) Slog.d(TAG, "delete res: systemUpdate=" + systemUpdate
+ " removedForAllUsers=" + removedForAllUsers);
@@ -16837,6 +16930,7 @@
mUserNeedsBadging.delete(userHandle);
mSettings.removeUserLPw(userHandle);
mPendingBroadcasts.remove(userHandle);
+ mEphemeralApplicationRegistry.onUserRemovedLPw(userHandle);
}
synchronized (mInstallLock) {
final StorageManager storage = mContext.getSystemService(StorageManager.class);
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index 17c24af..f7c63d1 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -16,6 +16,7 @@
package android.test.mock;
+import android.annotation.NonNull;
import android.app.PackageInstallObserver;
import android.content.ComponentName;
import android.content.Intent;
@@ -24,6 +25,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.IPackageDataObserver;
import android.content.pm.IPackageDeleteObserver;
@@ -286,6 +288,38 @@
throw new UnsupportedOperationException();
}
+ /** @hide */
+ @Override
+ public List<EphemeralApplicationInfo> getEphemeralApplications() {
+ throw new UnsupportedOperationException();
+ }
+
+ /** @hide */
+ @Override
+ public Drawable getEphemeralApplicationIcon(String packageName) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public byte[] getEphemeralCookie() {
+ return new byte[0];
+ }
+
+ @Override
+ public boolean isEphemeralApplication() {
+ return false;
+ }
+
+ @Override
+ public int getEphemeralCookieMaxSizeBytes() {
+ return 0;
+ }
+
+ @Override
+ public boolean setEphemeralCookie(@NonNull byte[] cookie) {
+ return false;
+ }
+
@Override
public ResolveInfo resolveActivity(Intent intent, int flags) {
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 e3a19e7..bab25c0 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
@@ -16,6 +16,7 @@
package com.android.layoutlib.bridge.android;
+import android.annotation.NonNull;
import android.app.PackageInstallObserver;
import android.content.ComponentName;
import android.content.Intent;
@@ -24,6 +25,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.IPackageDataObserver;
import android.content.pm.IPackageDeleteObserver;
@@ -251,6 +253,36 @@
}
@Override
+ public List<EphemeralApplicationInfo> getEphemeralApplications() {
+ return null;
+ }
+
+ @Override
+ public Drawable getEphemeralApplicationIcon(String packageName) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public byte[] getEphemeralCookie() {
+ return new byte[0];
+ }
+
+ @Override
+ public boolean isEphemeralApplication() {
+ return false;
+ }
+
+ @Override
+ public int getEphemeralCookieMaxSizeBytes() {
+ return 0;
+ }
+
+ @Override
+ public boolean setEphemeralCookie(@NonNull byte[] cookie) {
+ return false;
+ }
+
+ @Override
public String[] getSystemSharedLibraryNames() {
return new String[0];
}