diff options
31 files changed, 1881 insertions, 492 deletions
diff --git a/api/current.txt b/api/current.txt index 0329f93..d38d80f 100644 --- a/api/current.txt +++ b/api/current.txt @@ -3431,6 +3431,7 @@ package android.app { method public boolean onPreparePanel(int, android.view.View, android.view.Menu); method public void onProvideAssistContent(android.app.AssistContent); method public void onProvideAssistData(android.os.Bundle); + method public void onRequestPermissionsResult(int, java.lang.String[], int[]); method protected void onRestart(); method protected void onRestoreInstanceState(android.os.Bundle); method public void onRestoreInstanceState(android.os.Bundle, android.os.PersistableBundle); @@ -3461,6 +3462,7 @@ package android.app { method public boolean releaseInstance(); method public final deprecated void removeDialog(int); method public void reportFullyDrawn(); + method public final void requestPermissions(java.lang.String[], int); method public boolean requestVisibleBehind(boolean); method public final boolean requestWindowFeature(int); method public final void runOnUiThread(java.lang.Runnable); @@ -4315,6 +4317,7 @@ package android.app { method public void onOptionsMenuClosed(android.view.Menu); method public void onPause(); method public void onPrepareOptionsMenu(android.view.Menu); + method public void onRequestPermissionsResult(int, java.lang.String[], int[]); method public void onResume(); method public void onSaveInstanceState(android.os.Bundle); method public void onStart(); @@ -4323,6 +4326,7 @@ package android.app { method public void onViewCreated(android.view.View, android.os.Bundle); method public void onViewStateRestored(android.os.Bundle); method public void registerForContextMenu(android.view.View); + method public final void requestPermissions(java.lang.String[], int); method public void setAllowEnterTransitionOverlap(boolean); method public void setAllowReturnTransitionOverlap(boolean); method public void setArguments(android.os.Bundle); @@ -7409,6 +7413,7 @@ package android.content { method public abstract int checkCallingPermission(java.lang.String); method public abstract int checkCallingUriPermission(android.net.Uri, int); method public abstract int checkPermission(java.lang.String, int, int); + method public abstract int checkSelfPermission(java.lang.String); method public abstract int checkUriPermission(android.net.Uri, int, int, int); method public abstract int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int); method public abstract deprecated void clearWallpaper() throws java.io.IOException; @@ -7586,6 +7591,7 @@ package android.content { method public int checkCallingPermission(java.lang.String); method public int checkCallingUriPermission(android.net.Uri, int); method public int checkPermission(java.lang.String, int, int); + method public int checkSelfPermission(java.lang.String); method public int checkUriPermission(android.net.Uri, int, int, int); method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int); method public deprecated void clearWallpaper() throws java.io.IOException; @@ -8838,7 +8844,6 @@ package android.content.pm { field public static final int INSTALL_LOCATION_INTERNAL_ONLY = 1; // 0x1 field public static final int INSTALL_LOCATION_PREFER_EXTERNAL = 2; // 0x2 field public static final int REQUESTED_PERMISSION_GRANTED = 2; // 0x2 - field public static final int REQUESTED_PERMISSION_REQUIRED = 1; // 0x1 field public android.content.pm.ActivityInfo[] activities; field public android.content.pm.ApplicationInfo applicationInfo; field public int baseRevisionCode; @@ -30192,6 +30197,7 @@ package android.test.mock { method public int checkCallingPermission(java.lang.String); method public int checkCallingUriPermission(android.net.Uri, int); method public int checkPermission(java.lang.String, int, int); + method public int checkSelfPermission(java.lang.String); method public int checkUriPermission(android.net.Uri, int, int, int); method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int); method public void clearWallpaper(); diff --git a/api/removed.txt b/api/removed.txt index 1b209a9..c2b9d3e 100644 --- a/api/removed.txt +++ b/api/removed.txt @@ -1,3 +1,11 @@ +package android.content.pm { + + public class PackageInfo implements android.os.Parcelable { + field public static final int REQUESTED_PERMISSION_REQUIRED = 1; // 0x1 + } + +} + package android.media { public class AudioFormat { diff --git a/api/system-current.txt b/api/system-current.txt index aeff38b..0fc3a90 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -3514,6 +3514,7 @@ package android.app { method public boolean onPreparePanel(int, android.view.View, android.view.Menu); method public void onProvideAssistContent(android.app.AssistContent); method public void onProvideAssistData(android.os.Bundle); + method public void onRequestPermissionsResult(int, java.lang.String[], int[]); method protected void onRestart(); method protected void onRestoreInstanceState(android.os.Bundle); method public void onRestoreInstanceState(android.os.Bundle, android.os.PersistableBundle); @@ -3544,6 +3545,7 @@ package android.app { method public boolean releaseInstance(); method public final deprecated void removeDialog(int); method public void reportFullyDrawn(); + method public final void requestPermissions(java.lang.String[], int); method public boolean requestVisibleBehind(boolean); method public final boolean requestWindowFeature(int); method public final void runOnUiThread(java.lang.Runnable); @@ -4405,6 +4407,7 @@ package android.app { method public void onOptionsMenuClosed(android.view.Menu); method public void onPause(); method public void onPrepareOptionsMenu(android.view.Menu); + method public void onRequestPermissionsResult(int, java.lang.String[], int[]); method public void onResume(); method public void onSaveInstanceState(android.os.Bundle); method public void onStart(); @@ -4413,6 +4416,7 @@ package android.app { method public void onViewCreated(android.view.View, android.os.Bundle); method public void onViewStateRestored(android.os.Bundle); method public void registerForContextMenu(android.view.View); + method public final void requestPermissions(java.lang.String[], int); method public void setAllowEnterTransitionOverlap(boolean); method public void setAllowReturnTransitionOverlap(boolean); method public void setArguments(android.os.Bundle); @@ -7615,6 +7619,7 @@ package android.content { method public abstract int checkCallingPermission(java.lang.String); method public abstract int checkCallingUriPermission(android.net.Uri, int); method public abstract int checkPermission(java.lang.String, int, int); + method public abstract int checkSelfPermission(java.lang.String); method public abstract int checkUriPermission(android.net.Uri, int, int, int); method public abstract int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int); method public abstract deprecated void clearWallpaper() throws java.io.IOException; @@ -7798,6 +7803,7 @@ package android.content { method public int checkCallingPermission(java.lang.String); method public int checkCallingUriPermission(android.net.Uri, int); method public int checkPermission(java.lang.String, int, int); + method public int checkSelfPermission(java.lang.String); method public int checkUriPermission(android.net.Uri, int, int, int); method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int); method public deprecated void clearWallpaper() throws java.io.IOException; @@ -9078,7 +9084,6 @@ package android.content.pm { field public static final int INSTALL_LOCATION_INTERNAL_ONLY = 1; // 0x1 field public static final int INSTALL_LOCATION_PREFER_EXTERNAL = 2; // 0x2 field public static final int REQUESTED_PERMISSION_GRANTED = 2; // 0x2 - field public static final int REQUESTED_PERMISSION_REQUIRED = 1; // 0x1 field public android.content.pm.ActivityInfo[] activities; field public android.content.pm.ApplicationInfo applicationInfo; field public int baseRevisionCode; @@ -9278,6 +9283,7 @@ package android.content.pm { method public abstract android.graphics.drawable.Drawable getUserBadgedIcon(android.graphics.drawable.Drawable, android.os.UserHandle); 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 void grantPermission(java.lang.String, java.lang.String, android.os.UserHandle); method public abstract boolean hasSystemFeature(java.lang.String); method public abstract boolean isSafeMode(); method public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int); @@ -9293,16 +9299,20 @@ package android.content.pm { method public abstract android.content.pm.ResolveInfo resolveActivity(android.content.Intent, int); method public abstract android.content.pm.ProviderInfo resolveContentProvider(java.lang.String, int); method public abstract android.content.pm.ResolveInfo resolveService(android.content.Intent, int); + method public abstract void revokePermission(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 void setInstallerPackageName(java.lang.String, java.lang.String); method public abstract void verifyPendingInstall(int, int); + field public static final java.lang.String ACTION_REQUEST_PERMISSIONS = "android.content.pm.action.REQUEST_PERMISSIONS"; field public static final int COMPONENT_ENABLED_STATE_DEFAULT = 0; // 0x0 field public static final int COMPONENT_ENABLED_STATE_DISABLED = 2; // 0x2 field public static final int COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED = 4; // 0x4 field public static final int COMPONENT_ENABLED_STATE_DISABLED_USER = 3; // 0x3 field public static final int COMPONENT_ENABLED_STATE_ENABLED = 1; // 0x1 field public static final int DONT_KILL_APP = 1; // 0x1 + field public static final java.lang.String EXTRA_REQUEST_PERMISSIONS_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES"; + field public static final java.lang.String EXTRA_REQUEST_PERMISSIONS_RESULTS = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS"; field public static final java.lang.String EXTRA_VERIFICATION_ID = "android.content.pm.extra.VERIFICATION_ID"; field public static final java.lang.String EXTRA_VERIFICATION_RESULT = "android.content.pm.extra.VERIFICATION_RESULT"; field public static final java.lang.String FEATURE_APP_WIDGETS = "android.software.app_widgets"; @@ -32551,6 +32561,7 @@ package android.test.mock { method public int checkCallingPermission(java.lang.String); method public int checkCallingUriPermission(android.net.Uri, int); method public int checkPermission(java.lang.String, int, int); + method public int checkSelfPermission(java.lang.String); method public int checkUriPermission(android.net.Uri, int, int, int); method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int); method public void clearWallpaper(); diff --git a/api/system-removed.txt b/api/system-removed.txt index 1b209a9..c2b9d3e 100644 --- a/api/system-removed.txt +++ b/api/system-removed.txt @@ -1,3 +1,11 @@ +package android.content.pm { + + public class PackageInfo implements android.os.Parcelable { + field public static final int REQUESTED_PERMISSION_REQUIRED = 1; // 0x1 + } + +} + package android.media { public class AudioFormat { diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java index c48a618..f38b9e7 100644 --- a/cmds/pm/src/com/android/commands/pm/Pm.java +++ b/cmds/pm/src/com/android/commands/pm/Pm.java @@ -1517,6 +1517,15 @@ public final class Pm { } private int runGrantRevokePermission(boolean grant) { + int userId = UserHandle.USER_CURRENT; + + String opt = null; + while ((opt = nextOption()) != null) { + if (opt.equals("--user")) { + userId = Integer.parseInt(nextArg()); + } + } + String pkg = nextArg(); if (pkg == null) { System.err.println("Error: no package specified"); @@ -1529,11 +1538,12 @@ public final class Pm { showUsage(); return 1; } + try { if (grant) { - mPm.grantPermission(pkg, perm); + mPm.grantPermission(pkg, perm, userId); } else { - mPm.revokePermission(pkg, perm); + mPm.revokePermission(pkg, perm, userId); } return 0; } catch (RemoteException e) { @@ -1815,8 +1825,8 @@ public final class Pm { System.err.println(" pm disable-until-used [--user USER_ID] PACKAGE_OR_COMPONENT"); System.err.println(" pm hide [--user USER_ID] PACKAGE_OR_COMPONENT"); System.err.println(" pm unhide [--user USER_ID] PACKAGE_OR_COMPONENT"); - System.err.println(" pm grant PACKAGE PERMISSION"); - System.err.println(" pm revoke PACKAGE PERMISSION"); + System.err.println(" pm grant [--user USER_ID] PACKAGE PERMISSION"); + System.err.println(" pm revoke [--user USER_ID] PACKAGE PERMISSION"); System.err.println(" pm set-install-location [0/auto] [1/internal] [2/external]"); System.err.println(" pm get-install-location"); System.err.println(" pm set-permission-enforced PERMISSION [true|false]"); @@ -1889,8 +1899,9 @@ public final class Pm { System.err.println(" as \"package/class\")."); System.err.println(""); System.err.println("pm grant, revoke: these commands either grant or revoke permissions"); - System.err.println(" to applications. Only optional permissions the application has"); - System.err.println(" declared can be granted or revoked."); + System.err.println(" to apps. The permissions must be declared as used in the app's"); + System.err.println(" manifest, be runtime permissions (protection level dangerous),"); + System.err.println(" and the app targeting SDK greater than Lollipop MR1."); System.err.println(""); System.err.println("pm get-install-location: returns the current install location."); System.err.println(" 0 [auto]: Let system decide the best location"); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 7fcbe35..b5817df 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -3726,6 +3726,95 @@ public class Activity extends ContextThemeWrapper } /** + * Requests permissions to be granted to this application. These permissions + * must be requested in your manifest, they should not be granted to your app, + * and they should have protection level {@link android.content.pm.PermissionInfo + * #PROTECTION_DANGEROUS dangerous}, regardless whether they are declared by + * the platform or a third-party app. + * <p> + * Normal permissions {@link android.content.pm.PermissionInfo#PROTECTION_NORMAL} + * are granted at install time if requested in the manifest. Signature permissions + * {@link android.content.pm.PermissionInfo#PROTECTION_SIGNATURE} are granted at + * install time if requested in the manifest and the signature of your app matches + * the signature of the app declaring the permissions. + * </p> + * <p> + * If your app does not have the requested permissions the user will be presented + * with UI for accepting them. After the user has accepted or rejected the + * requested permissions you will receive a callback on {@link + * #onRequestPermissionsResult(int, String[], int[])} reporting whether the + * permissions were granted or not. + * </p> + * <p> + * Note that requesting a permission does not guarantee it will be granted and + * your app should be able to run without having this permission. + * </p> + * <p> + * This method may start an activity allowing the user to choose which permissions + * to grant and which to reject. Hence, you should be prepared that your activity + * may be paused and resumed. Further, granting some permissions may require + * a restart of you application. In such a case, the system will recreate the + * activity stack before delivering the result to {@link + * #onRequestPermissionsResult(int, String[], int[])}. + * </p> + * <p> + * When checking whether you have a permission you should use {@link + * #checkSelfPermission(String)}. + * </p> + * <p> + * A sample permissions request looks like this: + * </p> + * <code><pre><p> + * private void showContacts() { + * if (checkSelfPermission(Manifest.permission.READ_CONTACTS) + * != PackageManager.PERMISSION_GRANTED) { + * requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, + * PERMISSIONS_REQUEST_READ_CONTACTS); + * } else { + * doShowContacts(); + * } + * } + * + * {@literal @}Override + * public void onRequestPermissionsResult(int requestCode, String[] permissions, + * int[] grantResults) { + * if (requestCode == PERMISSIONS_REQUEST_READ_CONTACTS + * && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + * showContacts(); + * } + * } + * </code></pre></p> + * + * @param permissions The requested permissions. + * @param requestCode Application specific request code to match with a result + * reported to {@link #onRequestPermissionsResult(int, String[], int[])}. + * + * @see #onRequestPermissionsResult(int, String[], int[]) + * @see #checkSelfPermission(String) + */ + public final void requestPermissions(@NonNull String[] permissions, int requestCode) { + Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions); + startActivityForResult(intent, requestCode); + } + + /** + * Callback for the result from requesting permissions. This method + * is invoked for every call on {@link #requestPermissions(String[], int)}. + * + * @param requestCode The request code passed in {@link #requestPermissions(String[], int)}. + * @param permissions The requested permissions. Never null. + * @param grantResults The grant results for the corresponding permissions + * which is either {@link android.content.pm.PackageManager#PERMISSION_GRANTED} + * or {@link android.content.pm.PackageManager#PERMISSION_DENIED}. Never null. + * + * @see #requestPermissions(String[], int) + */ + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { + /* callback - no nothing */ + } + + /** * Same as calling {@link #startActivityForResult(Intent, int, Bundle)} * with no options. * @@ -6269,11 +6358,19 @@ public class Activity extends ContextThemeWrapper + ", resCode=" + resultCode + ", data=" + data); mFragments.noteStateNotSaved(); if (who == null) { - onActivityResult(requestCode, resultCode, data); + if (isRequestPermissionResult(data)) { + dispatchRequestPermissionsResult(requestCode, data); + } else { + onActivityResult(requestCode, resultCode, data); + } } else { Fragment frag = mFragments.findFragmentByWho(who); if (frag != null) { - frag.onActivityResult(requestCode, resultCode, data); + if (isRequestPermissionResult(data)) { + dispatchRequestPermissionsResultToFragment(requestCode, data, frag); + } else { + frag.onActivityResult(requestCode, resultCode, data); + } } } } @@ -6343,4 +6440,26 @@ public class Activity extends ContextThemeWrapper */ public void onTranslucentConversionComplete(boolean drawComplete); } + + private void dispatchRequestPermissionsResult(int requestCode, Intent data) { + String[] permissions = data.getStringArrayExtra( + PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES); + final int[] grantResults = data.getIntArrayExtra( + PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS); + onRequestPermissionsResult(requestCode, permissions, grantResults); + } + + private void dispatchRequestPermissionsResultToFragment(int requestCode, Intent data, + Fragment fragement) { + String[] permissions = data.getStringArrayExtra( + PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES); + final int[] grantResults = data.getIntArrayExtra( + PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS); + fragement.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + + private static boolean isRequestPermissionResult(Intent intent) { + return intent != null + && PackageManager.ACTION_REQUEST_PERMISSIONS.equals(intent.getAction()); + } } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 29b024ac..d143f8b 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -2493,7 +2493,8 @@ public class ActivityManager { public static int checkComponentPermission(String permission, int uid, int owningUid, boolean exported) { // Root, system server get to do everything. - if (uid == 0 || uid == Process.SYSTEM_UID) { + final int appId = UserHandle.getAppId(uid); + if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) { return PackageManager.PERMISSION_GRANTED; } // Isolated processes don't get any permissions. diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 9f81670..6d74905 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -81,7 +81,6 @@ import java.util.List; /*package*/ final class ApplicationPackageManager extends PackageManager { private static final String TAG = "ApplicationPackageManager"; - private final static boolean DEBUG = false; private final static boolean DEBUG_ICONS = false; // Default flags to use with PackageManager when no flags are given. @@ -186,8 +185,8 @@ final class ApplicationPackageManager extends PackageManager { public int[] getPackageGids(String packageName) throws NameNotFoundException { try { - int[] gids = mPM.getPackageGids(packageName); - if (gids == null || gids.length > 0) { + int[] gids = mPM.getPackageGids(packageName, mContext.getUserId()); + if (gids != null) { return gids; } } catch (RemoteException e) { @@ -398,7 +397,7 @@ final class ApplicationPackageManager extends PackageManager { @Override public int checkPermission(String permName, String pkgName) { try { - return mPM.checkPermission(permName, pkgName); + return mPM.checkPermission(permName, pkgName, mContext.getUserId()); } catch (RemoteException e) { throw new RuntimeException("Package manager has died", e); } @@ -432,18 +431,18 @@ final class ApplicationPackageManager extends PackageManager { } @Override - public void grantPermission(String packageName, String permissionName) { + public void grantPermission(String packageName, String permissionName, UserHandle user) { try { - mPM.grantPermission(packageName, permissionName); + mPM.grantPermission(packageName, permissionName, user.getIdentifier()); } catch (RemoteException e) { throw new RuntimeException("Package manager has died", e); } } @Override - public void revokePermission(String packageName, String permissionName) { + public void revokePermission(String packageName, String permissionName, UserHandle user) { try { - mPM.revokePermission(packageName, permissionName); + mPM.revokePermission(packageName, permissionName, user.getIdentifier()); } catch (RemoteException e) { throw new RuntimeException("Package manager has died", e); } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index eb27830..4ccd69f 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1323,6 +1323,15 @@ class ContextImpl extends Context { Binder.getCallingUid()); } + @Override + public int checkSelfPermission(String permission) { + if (permission == null) { + throw new IllegalArgumentException("permission is null"); + } + + return checkPermission(permission, Process.myPid(), Process.myUid()); + } + private void enforce( String permission, int resultOfCheck, boolean selfToo, int uid, String message) { diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index bdcc312..4fdae7f 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -17,6 +17,7 @@ package android.app; import android.animation.Animator; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringRes; import android.content.ComponentCallbacks2; @@ -1092,13 +1093,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene if (mActivity == null) { throw new IllegalStateException("Fragment " + this + " not attached to Activity"); } - if (options != null) { - mActivity.startActivityFromFragment(this, intent, requestCode, options); - } else { - // Note we want to go through this call for compatibility with - // applications that may have overridden the method. - mActivity.startActivityFromFragment(this, intent, requestCode, options); - } + mActivity.startActivityFromFragment(this, intent, requestCode, options); } /** @@ -1119,6 +1114,98 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene } /** + * Requests permissions to be granted to this application. These permissions + * must be requested in your manifest, they should not be granted to your app, + * and they should have protection level {@link android.content.pm.PermissionInfo + * #PROTECTION_DANGEROUS dangerous}, regardless whether they are declared by + * the platform or a third-party app. + * <p> + * Normal permissions {@link android.content.pm.PermissionInfo#PROTECTION_NORMAL} + * are granted at install time if requested in the manifest. Signature permissions + * {@link android.content.pm.PermissionInfo#PROTECTION_SIGNATURE} are granted at + * install time if requested in the manifest and the signature of your app matches + * the signature of the app declaring the permissions. + * </p> + * <p> + * If your app does not have the requested permissions the user will be presented + * with UI for accepting them. After the user has accepted or rejected the + * requested permissions you will receive a callback on {@link + * #onRequestPermissionsResult(int, String[], int[])} reporting whether the + * permissions were granted or not. + * </p> + * <p> + * Note that requesting a permission does not guarantee it will be granted and + * your app should be able to run without having this permission. + * </p> + * <p> + * This method may start an activity allowing the user to choose which permissions + * to grant and which to reject. Hence, you should be prepared that your activity + * may be paused and resumed. Further, granting some permissions may require + * a restart of you application. In such a case, the system will recreate the + * activity stack before delivering the result to {@link + * #onRequestPermissionsResult(int, String[], int[])}. + * </p> + * <p> + * When checking whether you have a permission you should use {@link + * android.content.Context#checkSelfPermission(String)}. + * </p> + * <p> + * A sample permissions request looks like this: + * </p> + * <code><pre><p> + * private void showContacts() { + * if (getActivity().checkSelfPermission(Manifest.permission.READ_CONTACTS) + * != PackageManager.PERMISSION_GRANTED) { + * requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, + * PERMISSIONS_REQUEST_READ_CONTACTS); + * } else { + * doShowContacts(); + * } + * } + * + * {@literal @}Override + * public void onRequestPermissionsResult(int requestCode, String[] permissions, + * int[] grantResults) { + * if (requestCode == PERMISSIONS_REQUEST_READ_CONTACTS + * && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + * doShowContacts(); + * } + * } + * </code></pre></p> + * + * @param permissions The requested permissions. + * @param requestCode Application specific request code to match with a result + * reported to {@link #onRequestPermissionsResult(int, String[], int[])}. + * + * @see #onRequestPermissionsResult(int, String[], int[]) + * @see android.content.Context#checkSelfPermission(String) + */ + public final void requestPermissions(@NonNull String[] permissions, int requestCode) { + if (mActivity == null) { + throw new IllegalStateException("Fragment " + this + " not attached to Activity"); + } + Intent intent = mActivity.getPackageManager().buildRequestPermissionsIntent(permissions); + mActivity.startActivityFromFragment(this, intent, requestCode, null); + } + + /** + * Callback for the result from requesting permissions. This method + * is invoked for every call on {@link #requestPermissions(String[], int)}. + * + * @param requestCode The request code passed in {@link #requestPermissions(String[], int)}. + * @param permissions The requested permissions. Never null. + * @param grantResults The grant results for the corresponding permissions + * which is either {@link android.content.pm.PackageManager#PERMISSION_GRANTED} + * or {@link android.content.pm.PackageManager#PERMISSION_DENIED}. Never null. + * + * @see #requestPermissions(String[], int) + */ + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { + /* callback - do nothing */ + } + + /** * @hide Hack so that DialogFragment can make its Dialog before creating * its views, and the view construction can use the dialog's context for * inflation. Maybe this should become a public API. Note sure. diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 80b5e0b..39a70be 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3109,6 +3109,20 @@ public abstract class Context { public abstract int checkCallingOrSelfPermission(@NonNull String permission); /** + * Determine whether <em>you</em> have been granted a particular permission. + * + * @param permission The name of the permission being checked. + * + * @return {@link PackageManager#PERMISSION_GRANTED} if you have the + * permission, or {@link PackageManager#PERMISSION_DENIED} if not. + * + * @see PackageManager#checkPermission(String, String) + * @see #checkCallingPermission(String) + */ + @PackageManager.PermissionResult + public abstract int checkSelfPermission(@NonNull String permission); + + /** * If the given permission is not allowed for a particular process * and user ID running in the system, throw a {@link SecurityException}. * diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 6e8b7c1..8c5a87c 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -602,6 +602,11 @@ public class ContextWrapper extends Context { } @Override + public int checkSelfPermission(String permission) { + return mBase.checkSelfPermission(permission); + } + + @Override public void enforcePermission( String permission, int pid, int uid, String message) { mBase.enforcePermission(permission, pid, uid, message); diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 3e5d362..c6d97f1 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -46,32 +46,34 @@ import android.content.pm.UserInfo; import android.content.pm.VerificationParams; import android.content.pm.VerifierDeviceIdentity; import android.net.Uri; +import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.content.IntentSender; +import com.android.internal.os.IResultReceiver; /** * See {@link PackageManager} for documentation on most of the APIs * here. - * + * * {@hide} */ interface IPackageManager { boolean isPackageAvailable(String packageName, int userId); PackageInfo getPackageInfo(String packageName, int flags, int userId); int getPackageUid(String packageName, int userId); - int[] getPackageGids(String packageName); - + int[] getPackageGids(String packageName, int userId); + String[] currentToCanonicalPackageNames(in String[] names); String[] canonicalToCurrentPackageNames(in String[] names); PermissionInfo getPermissionInfo(String name, int flags); - + List<PermissionInfo> queryPermissionsByGroup(String group, int flags); - + PermissionGroupInfo getPermissionGroupInfo(String name, int flags); - + List<PermissionGroupInfo> getAllPermissionGroups(int flags); - + ApplicationInfo getApplicationInfo(String packageName, int flags ,int userId); ActivityInfo getActivityInfo(in ComponentName className, int flags, int userId); @@ -85,28 +87,28 @@ interface IPackageManager { ProviderInfo getProviderInfo(in ComponentName className, int flags, int userId); - int checkPermission(String permName, String pkgName); - + int checkPermission(String permName, String pkgName, int userId); + int checkUidPermission(String permName, int uid); - + boolean addPermission(in PermissionInfo info); - + void removePermission(String name); - void grantPermission(String packageName, String permissionName); + boolean grantPermission(String packageName, String permissionName, int userId); - void revokePermission(String packageName, String permissionName); + boolean revokePermission(String packageName, String permissionName, int userId); boolean isProtectedBroadcast(String actionName); - + int checkSignatures(String pkg1, String pkg2); - + int checkUidSignatures(int uid1, int uid2); - + String[] getPackagesForUid(int uid); - + String getNameForUid(int uid); - + int getUidForSharedUser(String sharedUserName); int getFlagsForUid(int uid); @@ -121,7 +123,7 @@ interface IPackageManager { boolean canForwardTo(in Intent intent, String resolvedType, int sourceUserId, int targetUserId); - List<ResolveInfo> queryIntentActivities(in Intent intent, + List<ResolveInfo> queryIntentActivities(in Intent intent, String resolvedType, int flags, int userId); List<ResolveInfo> queryIntentActivityOptions( @@ -168,7 +170,7 @@ interface IPackageManager { /** * Retrieve all applications that are marked as persistent. - * + * * @return A List<applicationInfo> containing one entry for each persistent * application. */ @@ -178,7 +180,7 @@ interface IPackageManager { /** * Retrieve sync information for all content providers. - * + * * @param outNames Filled in with a list of the root names of the content * providers that can sync. * @param outInfo Filled in with a list of the ProviderInfo for each diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java index 9223269..9e6c6b5 100644 --- a/core/java/android/content/pm/PackageInfo.java +++ b/core/java/android/content/pm/PackageInfo.java @@ -167,8 +167,7 @@ public class PackageInfo implements Parcelable { * or null if there were none. This is only filled in if the flag * {@link PackageManager#GET_PERMISSIONS} was set. Each value matches * the corresponding entry in {@link #requestedPermissions}, and will have - * the flags {@link #REQUESTED_PERMISSION_REQUIRED} and - * {@link #REQUESTED_PERMISSION_GRANTED} set as appropriate. + * the flag {@link #REQUESTED_PERMISSION_GRANTED} set as appropriate. */ public int[] requestedPermissionsFlags; @@ -176,6 +175,8 @@ public class PackageInfo implements Parcelable { * Flag for {@link #requestedPermissionsFlags}: the requested permission * is required for the application to run; the user can not optionally * disable it. Currently all permissions are required. + * + * @removed We do not support required permissions. */ public static final int REQUESTED_PERMISSION_REQUIRED = 1<<0; diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 3da57cb..314b0b4 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -44,6 +44,7 @@ import android.os.Environment; import android.os.RemoteException; import android.os.UserHandle; import android.util.AndroidException; +import com.android.internal.util.ArrayUtils; import java.io.File; import java.lang.annotation.Retention; @@ -1663,21 +1664,46 @@ public abstract class PackageManager { = "android.content.pm.extra.VERIFICATION_VERSION_CODE"; /** - * The action used to request that the user approve a permission request - * from the application. + * The action used to request that the user approve a grant permissions + * request from the application. * * @hide */ - public static final String ACTION_REQUEST_PERMISSION - = "android.content.pm.action.REQUEST_PERMISSION"; + @SystemApi + public static final String ACTION_REQUEST_PERMISSIONS = + "android.content.pm.action.REQUEST_PERMISSIONS"; /** - * Extra field name for the list of permissions, which the user must approve. + * The component name handling runtime permission grants. * * @hide */ - public static final String EXTRA_REQUEST_PERMISSION_PERMISSION_LIST - = "android.content.pm.extra.PERMISSION_LIST"; + public static final String GRANT_PERMISSIONS_PACKAGE_NAME = + "com.android.packageinstaller"; + + /** + * The names of the requested permissions. + * <p> + * <strong>Type:</strong> String[] + * </p> + * + * @hide + */ + @SystemApi + public static final String EXTRA_REQUEST_PERMISSIONS_NAMES = + "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES"; + + /** + * The results from the permissions request. + * <p> + * <strong>Type:</strong> int[] of #PermissionResult + * </p> + * + * @hide + */ + @SystemApi + public static final String EXTRA_REQUEST_PERMISSIONS_RESULTS + = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS"; /** * String extra for {@link PackageInstallObserver} in the 'extras' Bundle in case of @@ -2184,51 +2210,70 @@ public abstract class PackageManager { public abstract void removePermission(String name); /** - * Returns an {@link Intent} suitable for passing to {@code startActivityForResult} - * which prompts the user to grant {@code permissions} to this application. - * @hide + * Grant a runtime permission to an application which the application does not + * already have. The permission must have been requested by the application. + * If the application is not allowed to hold the permission, a {@link + * java.lang.SecurityException} is thrown. + * <p> + * <strong>Note: </strong>Using this API requires holding + * android.permission.GRANT_REVOKE_PERMISSIONS and if the user id is + * not the current user android.permission.INTERACT_ACROSS_USERS_FULL. + * </p> + * + * @param packageName The package to which to grant the permission. + * @param permissionName The permission name to grant. + * @param user The user for which to grant the permission. * - * @throws NullPointerException if {@code permissions} is {@code null}. - * @throws IllegalArgumentException if {@code permissions} contains {@code null}. + * @see #revokePermission(String, String, android.os.UserHandle) + * + * @hide */ - public Intent buildPermissionRequestIntent(String... permissions) { - if (permissions == null) { - throw new NullPointerException("permissions cannot be null"); - } - for (String permission : permissions) { - if (permission == null) { - throw new IllegalArgumentException("permissions cannot contain null"); - } - } - - Intent i = new Intent(ACTION_REQUEST_PERMISSION); - i.putExtra(EXTRA_REQUEST_PERMISSION_PERMISSION_LIST, permissions); - i.setPackage("com.android.packageinstaller"); - return i; - } + @SystemApi + public abstract void grantPermission(@NonNull String packageName, + @NonNull String permissionName, @NonNull UserHandle user); /** - * Grant a permission to an application which the application does not - * already have. The permission must have been requested by the application, - * but as an optional permission. If the application is not allowed to - * hold the permission, a SecurityException is thrown. - * @hide + * Revoke a runtime permission that was previously granted by {@link + * #grantPermission(String, String, android.os.UserHandle)}. The permission + * must have been requested by and granted to the application. If the + * application is not allowed to hold the permission, a {@link + * java.lang.SecurityException} is thrown. + * <p> + * <strong>Note: </strong>Using this API requires holding + * android.permission.GRANT_REVOKE_PERMISSIONS and if the user id is + * not the current user android.permission.INTERACT_ACROSS_USERS_FULL. + * </p> + * + * @param packageName The package from which to revoke the permission. + * @param permissionName The permission name to revoke. + * @param user The user for which to revoke the permission. + * + * @see #grantPermission(String, String, android.os.UserHandle) * - * @param packageName The name of the package that the permission will be - * granted to. - * @param permissionName The name of the permission. + * @hide */ - public abstract void grantPermission(String packageName, String permissionName); + @SystemApi + public abstract void revokePermission(@NonNull String packageName, + @NonNull String permissionName, @NonNull UserHandle user); /** - * Revoke a permission that was previously granted by {@link #grantPermission}. - * @hide + * Returns an {@link android.content.Intent} suitable for passing to + * {@link android.app.Activity#startActivityForResult(android.content.Intent, int)} + * which prompts the user to grant permissions to this application. + * + * @throws NullPointerException if {@code permissions} is {@code null} or empty. * - * @param packageName The name of the package that the permission will be - * granted to. - * @param permissionName The name of the permission. + * @hide */ - public abstract void revokePermission(String packageName, String permissionName); + public Intent buildRequestPermissionsIntent(@NonNull String[] permissions) { + if (ArrayUtils.isEmpty(permissions)) { + throw new NullPointerException("permission cannot be null or empty"); + } + Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS); + intent.putExtra(EXTRA_REQUEST_PERMISSIONS_NAMES, permissions); + intent.setPackage(GRANT_PERMISSIONS_PACKAGE_NAME); + return intent; + } /** * Compare the signatures of two packages to determine if the same diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index eaad9ac..1ba74d5 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -410,7 +410,7 @@ public class PackageParser { public static PackageInfo generatePackageInfo(PackageParser.Package p, int gids[], int flags, long firstInstallTime, long lastUpdateTime, - ArraySet<String> grantedPermissions, PackageUserState state, int userId) { + Set<String> grantedPermissions, PackageUserState state, int userId) { if (!checkUseInstalledOrHidden(flags, state)) { return null; @@ -569,9 +569,8 @@ public class PackageParser { for (int i=0; i<N; i++) { final String perm = p.requestedPermissions.get(i); pi.requestedPermissions[i] = perm; - if (p.requestedPermissionsRequired.get(i)) { - pi.requestedPermissionsFlags[i] |= PackageInfo.REQUESTED_PERMISSION_REQUIRED; - } + // The notion of requried permissions is deprecated but for compatibility. + pi.requestedPermissionsFlags[i] |= PackageInfo.REQUESTED_PERMISSION_REQUIRED; if (grantedPermissions != null && grantedPermissions.contains(perm)) { pi.requestedPermissionsFlags[i] |= PackageInfo.REQUESTED_PERMISSION_GRANTED; } @@ -1812,7 +1811,6 @@ public class PackageParser { } implicitPerms.append(npi.name); pkg.requestedPermissions.add(npi.name); - pkg.requestedPermissionsRequired.add(Boolean.TRUE); } } if (implicitPerms != null) { @@ -1831,7 +1829,6 @@ public class PackageParser { final String perm = spi.newPerms[in]; if (!pkg.requestedPermissions.contains(perm)) { pkg.requestedPermissions.add(perm); - pkg.requestedPermissionsRequired.add(Boolean.TRUE); } } } @@ -1865,17 +1862,6 @@ public class PackageParser { pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES; } - /* - * b/8528162: Ignore the <uses-permission android:required> attribute if - * targetSdkVersion < JELLY_BEAN_MR2. There are lots of apps in the wild - * which are improperly using this attribute, even though it never worked. - */ - if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR2) { - for (int i = 0; i < pkg.requestedPermissionsRequired.size(); i++) { - pkg.requestedPermissionsRequired.set(i, Boolean.TRUE); - } - } - return pkg; } @@ -1911,11 +1897,6 @@ public class PackageParser { // that may change. String name = sa.getNonResourceString( com.android.internal.R.styleable.AndroidManifestUsesPermission_name); -/* - boolean required = sa.getBoolean( - com.android.internal.R.styleable.AndroidManifestUsesPermission_required, true); -*/ - boolean required = true; // Optional <uses-permission> not supported int maxSdkVersion = 0; TypedValue val = sa.peekValue( @@ -1933,13 +1914,9 @@ public class PackageParser { int index = pkg.requestedPermissions.indexOf(name); if (index == -1) { pkg.requestedPermissions.add(name.intern()); - pkg.requestedPermissionsRequired.add(required ? Boolean.TRUE : Boolean.FALSE); } else { - if (pkg.requestedPermissionsRequired.get(index) != required) { - outError[0] = "conflicting <uses-permission> entries"; - mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; - return false; - } + Slog.w(TAG, "Ignoring duplicate uses-permission: " + name + " in package: " + + pkg.packageName + " at: " + parser.getPositionDescription()); } } } @@ -4217,7 +4194,6 @@ public class PackageParser { public final ArrayList<Instrumentation> instrumentation = new ArrayList<Instrumentation>(0); public final ArrayList<String> requestedPermissions = new ArrayList<String>(); - public final ArrayList<Boolean> requestedPermissionsRequired = new ArrayList<Boolean>(); public ArrayList<String> protectedBroadcasts; diff --git a/core/java/android/widget/AppSecurityPermissions.java b/core/java/android/widget/AppSecurityPermissions.java index 5c05b5a..6feb94b 100644 --- a/core/java/android/widget/AppSecurityPermissions.java +++ b/core/java/android/widget/AppSecurityPermissions.java @@ -16,6 +16,7 @@ */ package android.widget; +import android.os.UserHandle; import com.android.internal.R; import android.app.AlertDialog; @@ -243,7 +244,8 @@ public class AppSecurityPermissions { @Override public void onClick(DialogInterface dialog, int which) { PackageManager pm = getContext().getPackageManager(); - pm.revokePermission(mPackageName, mPerm.name); + pm.revokePermission(mPackageName, mPerm.name, + new UserHandle(mContext.getUserId())); PermissionItemView.this.setVisibility(View.GONE); } }; @@ -298,7 +300,7 @@ public class AppSecurityPermissions { } extractPerms(info, permSet, installedPkgInfo); } - // Get permissions related to shared user if any + // Get permissions related to shared user if any if (info.sharedUserId != null) { int sharedUid; try { @@ -358,7 +360,7 @@ public class AppSecurityPermissions { String permName = strList[i]; // If we are only looking at an existing app, then we only // care about permissions that have actually been granted to it. - if (installedPkgInfo != null && info == installedPkgInfo) { + if (installedPkgInfo != null && info != installedPkgInfo) { if ((flagsList[i]&PackageInfo.REQUESTED_PERMISSION_GRANTED) == 0) { continue; } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 11c3ea6..fa1a1f1 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -76,6 +76,7 @@ import com.android.internal.os.IResultReceiver; import com.android.internal.os.ProcessCpuTracker; import com.android.internal.os.TransferPipe; import com.android.internal.os.Zygote; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.MemInfoReader; @@ -3066,7 +3067,7 @@ public final class ActivityManagerService extends ActivityManagerNative * Add shared application and profile GIDs so applications can share some * resources like shared libraries and access user-wide resources */ - if (permGids == null) { + if (ArrayUtils.isEmpty(permGids)) { gids = new int[2]; } else { gids = new int[permGids.length + 2]; @@ -6530,7 +6531,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (permission == null) { return PackageManager.PERMISSION_DENIED; } - return checkComponentPermission(permission, pid, UserHandle.getAppId(uid), -1, true); + return checkComponentPermission(permission, pid, uid, -1, true); } @Override @@ -6550,7 +6551,7 @@ public final class ActivityManagerService extends ActivityManagerNative pid = tlsIdentity.pid; } - return checkComponentPermission(permission, pid, UserHandle.getAppId(uid), -1, true); + return checkComponentPermission(permission, pid, uid, -1, true); } /** diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 8fe1238..34c1c53 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -765,7 +765,7 @@ public final class BroadcastQueue { try { perm = AppGlobals.getPackageManager(). checkPermission(r.requiredPermission, - info.activityInfo.applicationInfo.packageName); + info.activityInfo.applicationInfo.packageName, r.userId); } catch (RemoteException e) { perm = PackageManager.PERMISSION_DENIED; } diff --git a/services/core/java/com/android/server/pm/BasePermission.java b/services/core/java/com/android/server/pm/BasePermission.java index 4f27408..138a146 100644 --- a/services/core/java/com/android/server/pm/BasePermission.java +++ b/services/core/java/com/android/server/pm/BasePermission.java @@ -56,4 +56,9 @@ final class BasePermission { return "BasePermission{" + Integer.toHexString(System.identityHashCode(this)) + " " + name + "}"; } + + public boolean isRuntime() { + return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) + == PermissionInfo.PROTECTION_DANGEROUS; + } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 52411bf..5d20528 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -54,7 +54,6 @@ import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO import static com.android.internal.content.NativeLibraryHelper.LIB64_DIR_NAME; import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME; import static com.android.internal.util.ArrayUtils.appendInt; -import static com.android.internal.util.ArrayUtils.removeInt; import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet; import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets; @@ -249,6 +248,9 @@ public class PackageManagerService extends IPackageManager.Stub { private static final boolean DEBUG_DEXOPT = false; private static final boolean DEBUG_ABI_SELECTION = false; + private static final boolean RUNTIME_PERMISSIONS_ENABLED = + SystemProperties.getInt("ro.runtime.premissions.enabled", 0) == 1; + private static final int RADIO_UID = Process.PHONE_UID; private static final int LOG_UID = Process.LOG_UID; private static final int NFC_UID = Process.NFC_UID; @@ -321,10 +323,28 @@ public class PackageManagerService extends IPackageManager.Stub { DEFAULT_CONTAINER_PACKAGE, "com.android.defcontainer.DefaultContainerService"); + private static final String KILL_APP_REASON_GIDS_CHANGED = + "permission grant or revoke changed gids"; + + private static final String KILL_APP_REASON_PERMISSIONS_REVOKED = + "permissions revoked"; + private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive"; private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay"; + /** Permission grant: not grant the permission. */ + private static final int GRANT_DENIED = 1; + + /** Permission grant: grant the permission as an install permission. */ + private static final int GRANT_INSTALL = 2; + + /** Permission grant: grant the permission as a runtime permission. */ + private static final int GRANT_RUNTIME = 3; + + /** Permission grant: grant as runtime a permission that was granted as an install time one. */ + private static final int GRANT_UPGRADE = 4; + final ServiceThread mHandlerThread; final PackageHandler mHandler; @@ -1243,7 +1263,7 @@ public class PackageManagerService extends IPackageManager.Stub { } } - public static final PackageManagerService main(Context context, Installer installer, + public static PackageManagerService main(Context context, Installer installer, boolean factoryTest, boolean onlyCore) { PackageManagerService m = new PackageManagerService(context, installer, factoryTest, onlyCore); @@ -1293,7 +1313,7 @@ public class PackageManagerService extends IPackageManager.Stub { mOnlyCore = onlyCore; mLazyDexOpt = "eng".equals(SystemProperties.get("ro.build.type")); mMetrics = new DisplayMetrics(); - mSettings = new Settings(context); + mSettings = new Settings(mContext, mPackages); mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID, ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID, @@ -1832,14 +1852,8 @@ public class PackageManagerService extends IPackageManager.Stub { final String packageName = info.activityInfo.packageName; - final PackageSetting ps = mSettings.mPackages.get(packageName); - if (ps == null) { - continue; - } - - final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps; - if (!gp.grantedPermissions - .contains(android.Manifest.permission.PACKAGE_VERIFICATION_AGENT)) { + if (checkPermission(android.Manifest.permission.PACKAGE_VERIFICATION_AGENT, + packageName, UserHandle.USER_OWNER) != PackageManager.PERMISSION_GRANTED) { continue; } @@ -1895,26 +1909,21 @@ public class PackageManagerService extends IPackageManager.Stub { return cur; } - static int[] removeInts(int[] cur, int[] rem) { - if (rem == null) return cur; - if (cur == null) return cur; - final int N = rem.length; - for (int i=0; i<N; i++) { - cur = removeInt(cur, rem[i]); - } - return cur; - } - PackageInfo generatePackageInfo(PackageParser.Package p, int flags, int userId) { if (!sUserManager.exists(userId)) return null; final PackageSetting ps = (PackageSetting) p.mExtras; if (ps == null) { return null; } - final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps; + + PermissionsState permissionsState = ps.getPermissionsState(); + + final int[] gids = permissionsState.computeGids(userId); + Set<String> permissions = permissionsState.getPermissions(userId); + final PackageUserState state = ps.readUserState(userId); - return PackageParser.generatePackageInfo(p, gp.gids, flags, - ps.firstInstallTime, ps.lastUpdateTime, gp.grantedPermissions, + return PackageParser.generatePackageInfo(p, gids, flags, + ps.firstInstallTime, ps.lastUpdateTime, permissions, state, userId); } @@ -1986,6 +1995,7 @@ public class PackageManagerService extends IPackageManager.Stub { public int getPackageUid(String packageName, int userId) { if (!sUserManager.exists(userId)) return -1; enforceCrossUserPermission(Binder.getCallingUid(), userId, false, false, "get package uid"); + // reader synchronized (mPackages) { PackageParser.Package p = mPackages.get(packageName); @@ -2002,22 +2012,30 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override - public int[] getPackageGids(String packageName) { + public int[] getPackageGids(String packageName, int userId) throws RemoteException { + if (!sUserManager.exists(userId)) { + return null; + } + + enforceCrossUserPermission(Binder.getCallingUid(), userId, false, false, + "getPackageGids"); + // reader synchronized (mPackages) { PackageParser.Package p = mPackages.get(packageName); - if (DEBUG_PACKAGE_INFO) + if (DEBUG_PACKAGE_INFO) { Log.v(TAG, "getPackageGids" + packageName + ": " + p); + } if (p != null) { - final PackageSetting ps = (PackageSetting)p.mExtras; - return ps.getGids(); + PackageSetting ps = (PackageSetting) p.mExtras; + return ps.getPermissionsState().computeGids(userId); } } - // stupid thing to indicate an error. - return new int[0]; + + return null; } - static final PermissionInfo generatePermissionInfo( + static PermissionInfo generatePermissionInfo( BasePermission bp, int flags) { if (bp.perm != null) { return PackageParser.generatePermissionInfo(bp.perm, flags); @@ -2381,30 +2399,37 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override - public int checkPermission(String permName, String pkgName) { + public int checkPermission(String permName, String pkgName, int userId) { + if (!sUserManager.exists(userId)) { + return PackageManager.PERMISSION_DENIED; + } + synchronized (mPackages) { - PackageParser.Package p = mPackages.get(pkgName); + final PackageParser.Package p = mPackages.get(pkgName); if (p != null && p.mExtras != null) { - PackageSetting ps = (PackageSetting)p.mExtras; - if (ps.sharedUser != null) { - if (ps.sharedUser.grantedPermissions.contains(permName)) { - return PackageManager.PERMISSION_GRANTED; - } - } else if (ps.grantedPermissions.contains(permName)) { + final PackageSetting ps = (PackageSetting) p.mExtras; + if (ps.getPermissionsState().hasPermission(permName, userId)) { return PackageManager.PERMISSION_GRANTED; } } } + return PackageManager.PERMISSION_DENIED; } @Override public int checkUidPermission(String permName, int uid) { + final int userId = UserHandle.getUserId(uid); + + if (!sUserManager.exists(userId)) { + return PackageManager.PERMISSION_DENIED; + } + synchronized (mPackages) { Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid)); if (obj != null) { - GrantedPermissions gp = (GrantedPermissions)obj; - if (gp.grantedPermissions.contains(permName)) { + final SettingBase ps = (SettingBase) obj; + if (ps.getPermissionsState().hasPermission(permName, userId)) { return PackageManager.PERMISSION_GRANTED; } } else { @@ -2414,6 +2439,7 @@ public class PackageManagerService extends IPackageManager.Stub { } } } + return PackageManager.PERMISSION_DENIED; } @@ -2620,120 +2646,114 @@ public class PackageManagerService extends IPackageManager.Stub { } } - private static void checkGrantRevokePermissions(PackageParser.Package pkg, BasePermission bp) { + private static void enforceDeclaredAsUsedAndRuntimePermission(PackageParser.Package pkg, + BasePermission bp) { int index = pkg.requestedPermissions.indexOf(bp.name); if (index == -1) { throw new SecurityException("Package " + pkg.packageName + " has not requested permission " + bp.name); } - boolean isNormal = - ((bp.protectionLevel&PermissionInfo.PROTECTION_MASK_BASE) - == PermissionInfo.PROTECTION_NORMAL); - boolean isDangerous = - ((bp.protectionLevel&PermissionInfo.PROTECTION_MASK_BASE) - == PermissionInfo.PROTECTION_DANGEROUS); - boolean isDevelopment = - ((bp.protectionLevel&PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0); - - if (!isNormal && !isDangerous && !isDevelopment) { + if (!bp.isRuntime()) { throw new SecurityException("Permission " + bp.name + " is not a changeable permission type"); } - - if (isNormal || isDangerous) { - if (pkg.requestedPermissionsRequired.get(index)) { - throw new SecurityException("Can't change " + bp.name - + ". It is required by the application"); - } - } } @Override - public void grantPermission(String packageName, String permissionName) { + public boolean grantPermission(String packageName, String name, int userId) { + if (!sUserManager.exists(userId)) { + return false; + } + mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.GRANT_REVOKE_PERMISSIONS, null); + android.Manifest.permission.GRANT_REVOKE_PERMISSIONS, + "grantPermission"); + + enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, + "grantPermission"); + synchronized (mPackages) { final PackageParser.Package pkg = mPackages.get(packageName); if (pkg == null) { throw new IllegalArgumentException("Unknown package: " + packageName); } - final BasePermission bp = mSettings.mPermissions.get(permissionName); + + final BasePermission bp = mSettings.mPermissions.get(name); if (bp == null) { - throw new IllegalArgumentException("Unknown permission: " + permissionName); + throw new IllegalArgumentException("Unknown permission: " + name); } - checkGrantRevokePermissions(pkg, bp); + enforceDeclaredAsUsedAndRuntimePermission(pkg, bp); - final PackageSetting ps = (PackageSetting) pkg.mExtras; - if (ps == null) { - return; + final SettingBase sb = (SettingBase) pkg.mExtras; + if (sb == null) { + throw new IllegalArgumentException("Unknown package: " + packageName); } - final GrantedPermissions gp = (ps.sharedUser != null) ? ps.sharedUser : ps; - if (gp.grantedPermissions.add(permissionName)) { - if (ps.haveGids) { - gp.gids = appendInts(gp.gids, bp.gids); + + final PermissionsState permissionsState = sb.getPermissionsState(); + + final int result = permissionsState.grantRuntimePermission(bp, userId); + switch (result) { + case PermissionsState.PERMISSION_OPERATION_FAILURE: { + return false; } - mSettings.writeLPr(); + + case PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: { + killSettingPackagesForUser(sb, userId, KILL_APP_REASON_GIDS_CHANGED); + } break; } + + // Not critical if that is lost - app has to request again. + mSettings.writeRuntimePermissionsForUserLPr(userId, false); + + return true; } } @Override - public void revokePermission(String packageName, String permissionName) { - int changedAppId = -1; + public boolean revokePermission(String packageName, String name, int userId) { + if (!sUserManager.exists(userId)) { + return false; + } + + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.GRANT_REVOKE_PERMISSIONS, + "revokePermission"); + + enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, + "revokePermission"); synchronized (mPackages) { final PackageParser.Package pkg = mPackages.get(packageName); if (pkg == null) { throw new IllegalArgumentException("Unknown package: " + packageName); } - if (pkg.applicationInfo.uid != Binder.getCallingUid()) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.GRANT_REVOKE_PERMISSIONS, null); - } - final BasePermission bp = mSettings.mPermissions.get(permissionName); + + final BasePermission bp = mSettings.mPermissions.get(name); if (bp == null) { - throw new IllegalArgumentException("Unknown permission: " + permissionName); + throw new IllegalArgumentException("Unknown permission: " + name); } - checkGrantRevokePermissions(pkg, bp); + enforceDeclaredAsUsedAndRuntimePermission(pkg, bp); - final PackageSetting ps = (PackageSetting) pkg.mExtras; - if (ps == null) { - return; - } - final GrantedPermissions gp = (ps.sharedUser != null) ? ps.sharedUser : ps; - if (gp.grantedPermissions.remove(permissionName)) { - gp.grantedPermissions.remove(permissionName); - if (ps.haveGids) { - gp.gids = removeInts(gp.gids, bp.gids); - } - mSettings.writeLPr(); - changedAppId = ps.appId; + final SettingBase sb = (SettingBase) pkg.mExtras; + if (sb == null) { + throw new IllegalArgumentException("Unknown package: " + packageName); } - } - if (changedAppId >= 0) { - // We changed the perm on someone, kill its processes. - IActivityManager am = ActivityManagerNative.getDefault(); - if (am != null) { - final int callingUserId = UserHandle.getCallingUserId(); - final long ident = Binder.clearCallingIdentity(); - try { - //XXX we should only revoke for the calling user's app permissions, - // but for now we impact all users. - //am.killUid(UserHandle.getUid(callingUserId, changedAppId), - // "revoke " + permissionName); - int[] users = sUserManager.getUserIds(); - for (int user : users) { - am.killUid(UserHandle.getUid(user, changedAppId), - "revoke " + permissionName); - } - } catch (RemoteException e) { - } finally { - Binder.restoreCallingIdentity(ident); - } + final PermissionsState permissionsState = sb.getPermissionsState(); + + if (permissionsState.revokeRuntimePermission(bp, userId) == + PermissionsState.PERMISSION_OPERATION_FAILURE) { + return false; } + + killSettingPackagesForUser(sb, userId, KILL_APP_REASON_PERMISSIONS_REVOKED); + + // Critical, after this call all should never have the permission. + mSettings.writeRuntimePermissionsForUserLPr(userId, true); + + return true; } } @@ -2794,6 +2814,46 @@ public class PackageManagerService extends IPackageManager.Stub { } } + private void killSettingPackagesForUser(SettingBase sb, int userId, String reason) { + final long identity = Binder.clearCallingIdentity(); + try { + if (sb instanceof SharedUserSetting) { + SharedUserSetting sus = (SharedUserSetting) sb; + final int packageCount = sus.packages.size(); + for (int i = 0; i < packageCount; i++) { + PackageSetting susPs = sus.packages.valueAt(i); + if (userId == UserHandle.USER_ALL) { + killApplication(susPs.pkg.packageName, susPs.appId, reason); + } else { + final int uid = UserHandle.getUid(userId, susPs.appId); + killUid(uid, reason); + } + } + } else if (sb instanceof PackageSetting) { + PackageSetting ps = (PackageSetting) sb; + if (userId == UserHandle.USER_ALL) { + killApplication(ps.pkg.packageName, ps.appId, reason); + } else { + final int uid = UserHandle.getUid(userId, ps.appId); + killUid(uid, reason); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + private static void killUid(int uid, String reason) { + IActivityManager am = ActivityManagerNative.getDefault(); + if (am != null) { + try { + am.killUid(uid, reason); + } catch (RemoteException e) { + /* ignore - same process */ + } + } + } + /** * Compares two sets of signatures. Returns: * <br /> @@ -3875,9 +3935,10 @@ public class PackageManagerService extends IPackageManager.Stub { private void addPackageHoldingPermissions(ArrayList<PackageInfo> list, PackageSetting ps, String[] permissions, boolean[] tmp, int flags, int userId) { int numMatch = 0; - final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps; + final PermissionsState permissionsState = ps.getPermissionsState(); for (int i=0; i<permissions.length; i++) { - if (gp.grantedPermissions.contains(permissions[i])) { + final String permission = permissions[i]; + if (permissionsState.hasPermission(permission, userId)) { tmp[i] = true; numMatch++; } else { @@ -6853,36 +6914,42 @@ public class PackageManagerService extends IPackageManager.Stub { private void grantPermissionsLPw(PackageParser.Package pkg, boolean replace, String packageOfInterest) { + // IMPORTANT: There are two types of permissions: install and runtime. + // Install time permissions are granted when the app is installed to + // all device users and users added in the future. Runtime permissions + // are granted at runtime explicitly to specific users. Normal and signature + // protected permissions are install time permissions. Dangerous permissions + // are install permissions if the app's target SDK is Lollipop MR1 or older, + // otherwise they are runtime permissions. This function does not manage + // runtime permissions except for the case an app targeting Lollipop MR1 + // being upgraded to target a newer SDK, in which case dangerous permissions + // are transformed from install time to runtime ones. + final PackageSetting ps = (PackageSetting) pkg.mExtras; if (ps == null) { return; } - final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps; - ArraySet<String> origPermissions = gp.grantedPermissions; + + PermissionsState permissionsState = ps.getPermissionsState(); + PermissionsState origPermissions = permissionsState; + boolean changedPermission = false; if (replace) { ps.permissionsFixed = false; - if (gp == ps) { - origPermissions = new ArraySet<String>(gp.grantedPermissions); - gp.grantedPermissions.clear(); - gp.gids = mGlobalGids; - } + origPermissions = new PermissionsState(permissionsState); + permissionsState.reset(); } - if (gp.gids == null) { - gp.gids = mGlobalGids; - } + permissionsState.setGlobalGids(mGlobalGids); final int N = pkg.requestedPermissions.size(); for (int i=0; i<N; i++) { final String name = pkg.requestedPermissions.get(i); - final boolean required = pkg.requestedPermissionsRequired.get(i); final BasePermission bp = mSettings.mPermissions.get(name); + if (DEBUG_INSTALL) { - if (gp != ps) { - Log.i(TAG, "Package " + pkg.packageName + " checking " + name + ": " + bp); - } + Log.i(TAG, "Package " + pkg.packageName + " checking " + name + ": " + bp); } if (bp == null || bp.packageSetting == null) { @@ -6894,10 +6961,11 @@ public class PackageManagerService extends IPackageManager.Stub { } final String perm = bp.name; - boolean allowed; boolean allowedSig = false; - if ((bp.protectionLevel&PermissionInfo.PROTECTION_FLAG_APPOP) != 0) { - // Keep track of app op permissions. + int grant = GRANT_DENIED; + + // Keep track of app op permissions. + if ((bp.protectionLevel & PermissionInfo.PROTECTION_FLAG_APPOP) != 0) { ArraySet<String> pkgs = mAppOpPermissionPackages.get(bp.name); if (pkgs == null) { pkgs = new ArraySet<>(); @@ -6905,65 +6973,108 @@ public class PackageManagerService extends IPackageManager.Stub { } pkgs.add(pkg.packageName); } + final int level = bp.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE; - if (level == PermissionInfo.PROTECTION_NORMAL - || level == PermissionInfo.PROTECTION_DANGEROUS) { - // We grant a normal or dangerous permission if any of the following - // are true: - // 1) The permission is required - // 2) The permission is optional, but was granted in the past - // 3) The permission is optional, but was requested by an - // app in /system (not /data) - // - // Otherwise, reject the permission. - allowed = (required || origPermissions.contains(perm) - || (isSystemApp(ps) && !isUpdatedSystemApp(ps))); - } else if (bp.packageSetting == null) { - // This permission is invalid; skip it. - allowed = false; - } else if (level == PermissionInfo.PROTECTION_SIGNATURE) { - allowed = grantSignaturePermission(perm, pkg, bp, origPermissions); - if (allowed) { - allowedSig = true; - } - } else { - allowed = false; + switch (level) { + case PermissionInfo.PROTECTION_NORMAL: { + // For all apps normal permissions are install time ones. + grant = GRANT_INSTALL; + } break; + + case PermissionInfo.PROTECTION_DANGEROUS: { + if (!RUNTIME_PERMISSIONS_ENABLED + || pkg.applicationInfo.targetSdkVersion + <= Build.VERSION_CODES.LOLLIPOP_MR1) { + // For legacy apps dangerous permissions are install time ones. + grant = GRANT_INSTALL; + } else if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + // For modern system apps dangerous permissions are install time ones. + grant = GRANT_INSTALL; + } else { + if (origPermissions.hasInstallPermission(bp.name)) { + // For legacy apps that became modern, install becomes runtime. + grant = GRANT_UPGRADE; + } else if (replace) { + // For upgraded modern apps keep runtime permissions unchanged. + grant = GRANT_RUNTIME; + } + } + } break; + + case PermissionInfo.PROTECTION_SIGNATURE: { + // For all apps signature permissions are install time ones. + allowedSig = grantSignaturePermission(perm, pkg, bp, origPermissions); + if (allowedSig) { + grant = GRANT_INSTALL; + } + } break; } + if (DEBUG_INSTALL) { - if (gp != ps) { - Log.i(TAG, "Package " + pkg.packageName + " granting " + perm); - } + Log.i(TAG, "Package " + pkg.packageName + " granting " + perm); } - if (allowed) { + + if (grant != GRANT_DENIED) { if (!isSystemApp(ps) && ps.permissionsFixed) { // If this is an existing, non-system package, then // we can't add any new permissions to it. - if (!allowedSig && !gp.grantedPermissions.contains(perm)) { + if (!allowedSig && !origPermissions.hasInstallPermission(perm)) { // Except... if this is a permission that was added // to the platform (note: need to only do this when // updating the platform). - allowed = isNewPlatformPermissionForPackage(perm, pkg); + if (!isNewPlatformPermissionForPackage(perm, pkg)) { + grant = GRANT_DENIED; + } } } - if (allowed) { - if (!gp.grantedPermissions.contains(perm)) { - changedPermission = true; - gp.grantedPermissions.add(perm); - gp.gids = appendInts(gp.gids, bp.gids); - } else if (!ps.haveGids) { - gp.gids = appendInts(gp.gids, bp.gids); - } - } else { - if (packageOfInterest == null || packageOfInterest.equals(pkg.packageName)) { - Slog.w(TAG, "Not granting permission " + perm - + " to package " + pkg.packageName - + " because it was previously installed without"); - } + + switch (grant) { + case GRANT_INSTALL: { + // Grant an install permission. + if (permissionsState.grantInstallPermission(bp) != + PermissionsState.PERMISSION_OPERATION_FAILURE) { + changedPermission = true; + } + } break; + + case GRANT_RUNTIME: { + // Grant previously granted runtime permissions. + for (int userId : UserManagerService.getInstance().getUserIds()) { + // Make sure runtime permissions are loaded. + if (origPermissions.hasRuntimePermission(bp.name, userId)) { + if (permissionsState.grantRuntimePermission(bp, userId) != + PermissionsState.PERMISSION_OPERATION_FAILURE) { + changedPermission = true; + } + } + } + } break; + + case GRANT_UPGRADE: { + // Grant runtime permissions for a previously held install permission. + permissionsState.revokeInstallPermission(bp); + for (int userId : UserManagerService.getInstance().getUserIds()) { + // Make sure runtime permissions are loaded. + if (permissionsState.grantRuntimePermission(bp, userId) != + PermissionsState.PERMISSION_OPERATION_FAILURE) { + changedPermission = true; + } + } + } break; + + default: { + if (packageOfInterest == null + || packageOfInterest.equals(pkg.packageName)) { + Slog.w(TAG, "Not granting permission " + perm + + " to package " + pkg.packageName + + " because it was previously installed without"); + } + } break; } } else { - if (gp.grantedPermissions.remove(perm)) { + if (permissionsState.revokeInstallPermission(bp) != + PermissionsState.PERMISSION_OPERATION_FAILURE) { changedPermission = true; - gp.gids = removeInts(gp.gids, bp.gids); Slog.i(TAG, "Un-granting permission " + perm + " from package " + pkg.packageName + " (protectionLevel=" + bp.protectionLevel @@ -6990,7 +7101,6 @@ public class PackageManagerService extends IPackageManager.Stub { // changed. ps.permissionsFixed = true; } - ps.haveGids = true; } private boolean isNewPlatformPermissionForPackage(String perm, PackageParser.Package pkg) { @@ -7011,7 +7121,7 @@ public class PackageManagerService extends IPackageManager.Stub { } private boolean grantSignaturePermission(String perm, PackageParser.Package pkg, - BasePermission bp, ArraySet<String> origPermissions) { + BasePermission bp, PermissionsState origPermissions) { boolean allowed; allowed = (compareSignatures( bp.packageSetting.signatures.mSignatures, pkg.mSignatures) @@ -7026,10 +7136,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (isUpdatedSystemApp(pkg)) { final PackageSetting sysPs = mSettings .getDisabledSystemPkgLPr(pkg.packageName); - final GrantedPermissions origGp = sysPs.sharedUser != null - ? sysPs.sharedUser : sysPs; - - if (origGp.grantedPermissions.contains(perm)) { + if (sysPs.getPermissionsState().hasInstallPermission(perm)) { // If the original was granted this permission, we take // that grant decision as read and propagate it to the // update. @@ -7063,7 +7170,7 @@ public class PackageManagerService extends IPackageManager.Stub { & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) { // For development permissions, a development permission // is granted only if it was already granted. - allowed = origPermissions.contains(perm); + allowed = origPermissions.hasInstallPermission(perm); } return allowed; } @@ -10821,11 +10928,26 @@ public class PackageManagerService extends IPackageManager.Stub { mSettings.mKeySetManagerService.removeAppKeySetDataLPw(packageName); outInfo.removedAppId = mSettings.removePackageLPw(packageName); } - if (deletedPs != null) { - updatePermissionsLPw(deletedPs.name, null, 0); - if (deletedPs.sharedUser != null) { - // remove permissions associated with package - mSettings.updateSharedUserPermsLPw(deletedPs, mGlobalGids); + updatePermissionsLPw(deletedPs.name, null, 0); + if (deletedPs.sharedUser != null) { + // Remove permissions associated with package. Since runtime + // permissions are per user we have to kill the removed package + // or packages running under the shared user of the removed + // package if revoking the permissions requested only by the removed + // package is successful and this causes a change in gids. + for (int userId : UserManagerService.getInstance().getUserIds()) { + final int userIdToKill = mSettings.updateSharedUserPermsLPw(deletedPs, + userId); + if (userIdToKill == userId) { + // If gids changed for this user, kill all affected packages. + killSettingPackagesForUser(deletedPs, userIdToKill, + KILL_APP_REASON_GIDS_CHANGED); + } else if (userIdToKill == UserHandle.USER_ALL) { + // If gids changed for all users, kill them all - done. + killSettingPackagesForUser(deletedPs, userIdToKill, + KILL_APP_REASON_GIDS_CHANGED); + break; + } } } clearPackagePreferredActivitiesLPw(deletedPs.name, UserHandle.USER_ALL); diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 06d842a..889164c 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -57,8 +57,10 @@ final class PackageSetting extends PackageSettingBase { + " " + name + "/" + appId + "}"; } - public int[] getGids() { - return sharedUser != null ? sharedUser.gids : gids; + public PermissionsState getPermissionsState() { + return (sharedUser != null) + ? sharedUser.getPermissionsState() + : super.getPermissionsState(); } public boolean isPrivileged() { diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java index 4b8ca42..9e8b3df 100644 --- a/services/core/java/com/android/server/pm/PackageSettingBase.java +++ b/services/core/java/com/android/server/pm/PackageSettingBase.java @@ -29,7 +29,7 @@ import java.io.File; /** * Settings base class for pending and resolved classes. */ -class PackageSettingBase extends GrantedPermissions { +abstract class PackageSettingBase extends SettingBase { /** * Indicates the state of installation. Used by PackageManager to figure out * incomplete installations. Say a package is being installed (the state is @@ -93,7 +93,6 @@ class PackageSettingBase extends GrantedPermissions { PackageSignatures signatures = new PackageSignatures(); boolean permissionsFixed; - boolean haveGids; PackageKeySetData keySetData = new PackageKeySetData(); @@ -147,7 +146,6 @@ class PackageSettingBase extends GrantedPermissions { signatures = new PackageSignatures(base.signatures); permissionsFixed = base.permissionsFixed; - haveGids = base.haveGids; userState.clear(); for (int i=0; i<base.userState.size(); i++) { userState.put(base.userState.keyAt(i), @@ -160,7 +158,6 @@ class PackageSettingBase extends GrantedPermissions { installerPackageName = base.installerPackageName; keySetData = new PackageKeySetData(base.keySetData); - } void init(File codePath, File resourcePath, String legacyNativeLibraryPathString, @@ -201,9 +198,7 @@ class PackageSettingBase extends GrantedPermissions { * Make a shallow copy of this package settings. */ public void copyFrom(PackageSettingBase base) { - grantedPermissions = base.grantedPermissions; - gids = base.gids; - + getPermissionsState().copyFrom(base.getPermissionsState()); primaryCpuAbiString = base.primaryCpuAbiString; secondaryCpuAbiString = base.secondaryCpuAbiString; cpuAbiOverrideString = base.cpuAbiOverrideString; @@ -212,7 +207,6 @@ class PackageSettingBase extends GrantedPermissions { lastUpdateTime = base.lastUpdateTime; signatures = base.signatures; permissionsFixed = base.permissionsFixed; - haveGids = base.haveGids; userState.clear(); for (int i=0; i<base.userState.size(); i++) { userState.put(base.userState.keyAt(i), base.userState.valueAt(i)); diff --git a/services/core/java/com/android/server/pm/PermissionsState.java b/services/core/java/com/android/server/pm/PermissionsState.java new file mode 100644 index 0000000..f6417ce --- /dev/null +++ b/services/core/java/com/android/server/pm/PermissionsState.java @@ -0,0 +1,523 @@ +/* + * 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.os.UserHandle; +import android.util.ArrayMap; +import android.util.ArraySet; +import com.android.internal.util.ArrayUtils; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; + +/** + * This class encapsulates the permissions for a package or a shared user. + * <p> + * There are two types of permissions: install (granted at installation) + * and runtime (granted at runtime). Install permissions are granted to + * all device users while runtime permissions are granted explicitly to + * specific users. + * </p> + * <p> + * The permissions are kept on a per device user basis. For example, an + * application may have some runtime permissions granted under the device + * owner but not granted under the secondary user. + * <p> + * This class is also responsible for keeping track of the Linux gids per + * user for a package or a shared user. The gids are computed as a set of + * the gids for all granted permissions' gids on a per user basis. + * </p> + */ +public final class PermissionsState { + + /** The permission operation succeeded and no gids changed. */ + public static final int PERMISSION_OPERATION_SUCCESS = 1; + + /** The permission operation succeeded and gids changed. */ + public static final int PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED = 2; + + /** The permission operation failed. */ + public static final int PERMISSION_OPERATION_FAILURE = 3; + + private static final int[] USERS_ALL = {UserHandle.USER_ALL}; + + private static final int[] USERS_NONE = {}; + + private static final int[] NO_GIDS = {}; + + private ArrayMap<String, PermissionData> mPermissions; + + private int[] mGlobalGids = NO_GIDS; + + public PermissionsState() { + /* do nothing */ + } + + public PermissionsState(PermissionsState prototype) { + copyFrom(prototype); + } + + /** + * Sets the global gids, applicable to all users. + * + * @param globalGids The global gids. + */ + public void setGlobalGids(int[] globalGids) { + if (!ArrayUtils.isEmpty(globalGids)) { + mGlobalGids = Arrays.copyOf(globalGids, globalGids.length); + } + } + + /** + * Initialized this instance from another one. + * + * @param other The other instance. + */ + public void copyFrom(PermissionsState other) { + if (mPermissions != null) { + if (other.mPermissions == null) { + mPermissions = null; + } else { + mPermissions.clear(); + } + } + if (other.mPermissions != null) { + if (mPermissions == null) { + mPermissions = new ArrayMap<>(); + } + final int permissionCount = other.mPermissions.size(); + for (int i = 0; i < permissionCount; i++) { + String name = other.mPermissions.keyAt(i); + PermissionData permissionData = other.mPermissions.valueAt(i); + mPermissions.put(name, new PermissionData(permissionData)); + } + } + + mGlobalGids = NO_GIDS; + if (other.mGlobalGids != NO_GIDS) { + mGlobalGids = Arrays.copyOf(other.mGlobalGids, + other.mGlobalGids.length); + } + } + + /** + * Grant an install permission. + * + * @param permission The permission to grant. + * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS}, + * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link + * #PERMISSION_OPERATION_FAILURE}. + */ + public int grantInstallPermission(BasePermission permission) { + return grantPermission(permission, UserHandle.USER_ALL); + } + + /** + * Revoke an install permission. + * + * @param permission The permission to revoke. + * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS}, + * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link + * #PERMISSION_OPERATION_FAILURE}. + */ + public int revokeInstallPermission(BasePermission permission) { + return revokePermission(permission, UserHandle.USER_ALL); + } + + /** + * Grant a runtime permission. + * + * @param permission The permission to grant. + * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS}, + * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link + * #PERMISSION_OPERATION_FAILURE}. + */ + public int grantRuntimePermission(BasePermission permission, int userId) { + return grantPermission(permission, userId); + } + + /** + * Revoke a runtime permission for a given device user. + * + * @param permission The permission to revoke. + * @param userId The device user id. + * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS}, + * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link + * #PERMISSION_OPERATION_FAILURE}. + */ + public int revokeRuntimePermission(BasePermission permission, int userId) { + return revokePermission(permission, userId); + } + + /** + * Gets whether this state has a given permission, regardless if + * it is install time or runtime one. + * + * @param name The permission name. + * @return Whether this state has the permission. + */ + public boolean hasPermission(String name) { + return mPermissions != null && mPermissions.get(name) != null; + } + + /** + * Gets whether this state has a given runtime permission for a + * given device user id. + * + * @param name The permission name. + * @param userId The device user id. + * @return Whether this state has the permission. + */ + public boolean hasRuntimePermission(String name, int userId) { + return !hasInstallPermission(name) && hasPermission(name, userId); + } + + /** + * Gets whether this state has a given install permission. + * + * @param name The permission name. + * @return Whether this state has the permission. + */ + public boolean hasInstallPermission(String name) { + return hasPermission(name, UserHandle.USER_ALL); + } + + /** + * Revokes a permission for all users regardless if it is an install or + * a runtime permission. + * + * @param permission The permission to revoke. + * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS}, + * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link + * #PERMISSION_OPERATION_FAILURE}. + */ + public int revokePermission(BasePermission permission) { + if (!hasPermission(permission.name)) { + return PERMISSION_OPERATION_FAILURE; + } + + int result = PERMISSION_OPERATION_SUCCESS; + + PermissionData permissionData = mPermissions.get(permission.name); + if (permissionData.getGids() != NO_GIDS) { + for (int userId : permissionData.getUserIds()) { + if (revokePermission(permission, userId) + == PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) { + result = PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED; + break; + } + } + } + + mPermissions.remove(permission.name); + + return result; + } + + /** + * Gets whether the state has a given permission for the specified + * user, regardless if this is an install or a runtime permission. + * + * @param name The permission name. + * @param userId The device user id. + * @return Whether the user has the permission. + */ + public boolean hasPermission(String name, int userId) { + enforceValidUserId(userId); + + if (mPermissions == null) { + return false; + } + + PermissionData permissionData = mPermissions.get(name); + return permissionData != null && permissionData.hasUserId(userId); + } + + /** + * Gets all permissions regardless if they are install or runtime. + * + * @return The permissions or an empty set. + */ + public Set<String> getPermissions() { + if (mPermissions != null) { + return mPermissions.keySet(); + } + + return Collections.emptySet(); + } + + /** + * Gets all permissions for a given device user id regardless if they + * are install time or runtime permissions. + * + * @param userId The device user id. + * @return The permissions or an empty set. + */ + public Set<String> getPermissions(int userId) { + enforceValidUserId(userId); + + if (mPermissions == null) { + return Collections.emptySet(); + } + + Set<String> permissions = new ArraySet<>(); + + final int permissionCount = mPermissions.size(); + for (int i = 0; i < permissionCount; i++) { + String permission = mPermissions.keyAt(i); + if (userId == UserHandle.USER_ALL) { + if (hasInstallPermission(permission)) { + permissions.add(permission); + } + } else { + if (hasRuntimePermission(permission, userId)) { + permissions.add(permission); + } + } + } + + return permissions; + } + + /** + * Gets all runtime permissions. + * + * @return The permissions or an empty set. + */ + public Set<String> getRuntimePermissions(int userId) { + return getPermissions(userId); + } + + /** + * Gets all install permissions. + * + * @return The permissions or an empty set. + */ + public Set<String> getInstallPermissions() { + return getPermissions(UserHandle.USER_ALL); + } + + /** + * Compute the Linux gids for a given device user from the permissions + * granted to this user. Note that these are computed to avoid additional + * state as they are rarely accessed. + * + * @param userId The device user id. + * @return The gids for the device user. + */ + public int[] computeGids(int userId) { + enforceValidUserId(userId); + + int[] gids = mGlobalGids; + + if (mPermissions != null) { + final int permissionCount = mPermissions.size(); + for (int i = 0; i < permissionCount; i++) { + String permission = mPermissions.keyAt(i); + if (!hasPermission(permission, userId)) { + continue; + } + PermissionData permissionData = mPermissions.valueAt(i); + final int[] permGids = permissionData.getGids(); + if (permGids != NO_GIDS) { + gids = appendInts(gids, permGids); + } + } + } + + return gids; + } + + /** + * Compute the Linux gids for all device users from the permissions + * granted to these users. + * + * @return The gids for all device users. + */ + public int[] computeGids() { + int[] gids = mGlobalGids; + + for (int userId : UserManagerService.getInstance().getUserIds()) { + final int[] userGids = computeGids(userId); + gids = appendInts(gids, userGids); + } + + return gids; + } + + /** + * Resets the internal state of this object. + */ + public void reset() { + mGlobalGids = NO_GIDS; + mPermissions = null; + } + + private int grantPermission(BasePermission permission, int userId) { + if (hasPermission(permission.name, userId)) { + return PERMISSION_OPERATION_FAILURE; + } + + final boolean hasGids = permission.gids != NO_GIDS; + final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS; + + if (mPermissions == null) { + mPermissions = new ArrayMap<>(); + } + + PermissionData permissionData = mPermissions.get(permission.name); + if (permissionData == null) { + permissionData = new PermissionData(permission.gids); + mPermissions.put(permission.name, permissionData); + } + + if (!permissionData.addUserId(userId)) { + return PERMISSION_OPERATION_FAILURE; + } + + if (hasGids) { + final int[] newGids = computeGids(userId); + if (oldGids.length != newGids.length) { + return PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED; + } + } + + return PERMISSION_OPERATION_SUCCESS; + } + + private int revokePermission(BasePermission permission, int userId) { + if (!hasPermission(permission.name, userId)) { + return PERMISSION_OPERATION_FAILURE; + } + + final boolean hasGids = permission.gids != NO_GIDS; + final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS; + + PermissionData permissionData = mPermissions.get(permission.name); + + if (!permissionData.removeUserId(userId)) { + return PERMISSION_OPERATION_FAILURE; + } + + if (permissionData.getUserIds() == USERS_NONE) { + mPermissions.remove(permission.name); + } + + if (mPermissions.isEmpty()) { + mPermissions = null; + } + + if (hasGids) { + final int[] newGids = computeGids(userId); + if (oldGids.length != newGids.length) { + return PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED; + } + } + + return PERMISSION_OPERATION_SUCCESS; + } + + private static int[] appendInts(int[] current, int[] added) { + if (current != null && added != null) { + for (int guid : added) { + current = ArrayUtils.appendInt(current, guid); + } + } + return current; + } + + private static void enforceValidUserId(int userId) { + if (userId != UserHandle.USER_ALL && userId < 0) { + throw new IllegalArgumentException("Invalid userId:" + userId); + } + } + + private static final class PermissionData { + private final int[] mGids; + private int[] mUserIds = USERS_NONE; + + public PermissionData(int[] gids) { + mGids = !ArrayUtils.isEmpty(gids) + ? Arrays.copyOf(gids, gids.length) + : NO_GIDS; + } + + public PermissionData(PermissionData other) { + this(other.mGids); + + if (other.mUserIds == USERS_ALL || other.mUserIds == USERS_NONE) { + mUserIds = other.mUserIds; + } else { + mUserIds = Arrays.copyOf(other.mUserIds, other.mUserIds.length); + } + } + + public int[] getGids() { + return mGids; + } + + public int[] getUserIds() { + return mUserIds; + } + + public boolean hasUserId(int userId) { + if (mUserIds == USERS_ALL) { + return true; + } + + if (userId != UserHandle.USER_ALL) { + return ArrayUtils.contains(mUserIds, userId); + } + + return false; + } + + public boolean addUserId(int userId) { + if (hasUserId(userId)) { + return false; + } + + if (userId == UserHandle.USER_ALL) { + mUserIds = USERS_ALL; + return true; + } + + mUserIds = ArrayUtils.appendInt(mUserIds, userId); + + return true; + } + + public boolean removeUserId(int userId) { + if (!hasUserId(userId)) { + return false; + } + + if (mUserIds == USERS_ALL) { + mUserIds = UserManagerService.getInstance().getUserIds(); + } + + mUserIds = ArrayUtils.removeInt(mUserIds, userId); + + if (mUserIds.length == 0) { + mUserIds = USERS_NONE; + } + + return true; + } + } +} diff --git a/services/core/java/com/android/server/pm/GrantedPermissions.java b/services/core/java/com/android/server/pm/SettingBase.java index e87546c..d350c09 100644 --- a/services/core/java/com/android/server/pm/GrantedPermissions.java +++ b/services/core/java/com/android/server/pm/SettingBase.java @@ -19,27 +19,26 @@ package com.android.server.pm; import android.content.pm.ApplicationInfo; import android.util.ArraySet; -class GrantedPermissions { +abstract class SettingBase { int pkgFlags; int pkgPrivateFlags; - ArraySet<String> grantedPermissions = new ArraySet<String>(); + private final PermissionsState mPermissionsState; - int[] gids; - - GrantedPermissions(int pkgFlags, int pkgPrivateFlags) { + SettingBase(int pkgFlags, int pkgPrivateFlags) { setFlags(pkgFlags); setPrivateFlags(pkgPrivateFlags); + mPermissionsState = new PermissionsState(); } - @SuppressWarnings("unchecked") - GrantedPermissions(GrantedPermissions base) { + SettingBase(SettingBase base) { pkgFlags = base.pkgFlags; - grantedPermissions = new ArraySet<>(base.grantedPermissions); + pkgPrivateFlags = base.pkgPrivateFlags; + mPermissionsState = new PermissionsState(base.mPermissionsState); + } - if (base.gids != null) { - gids = base.gids.clone(); - } + public PermissionsState getPermissionsState() { + return mPermissionsState; } void setFlags(int pkgFlags) { diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index b820d7e..1e0a49e 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -25,6 +25,7 @@ import static android.Manifest.permission.READ_EXTERNAL_STORAGE; import static android.os.Process.SYSTEM_UID; import static android.os.Process.PACKAGE_INFO_GID; +import android.content.Context; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ResolveInfo; @@ -33,17 +34,27 @@ import android.os.Binder; import android.os.Build; import android.os.Environment; import android.os.FileUtils; +import android.os.Handler; +import android.os.Message; import android.os.PatternMatcher; import android.os.Process; +import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; +import android.util.AtomicFile; import android.util.LogPrinter; +import android.util.SparseBooleanArray; +import android.util.SparseLongArray; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.os.BackgroundThread; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.JournaledFile; import com.android.internal.util.XmlUtils; import com.android.server.pm.PackageManagerService.DumpState; +import java.io.FileNotFoundException; import java.util.Collection; import org.xmlpull.v1.XmlPullParser; @@ -51,7 +62,6 @@ import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import android.content.ComponentName; -import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.ComponentInfo; @@ -134,6 +144,8 @@ final class Settings { private static final boolean DEBUG_STOPPED = false; private static final boolean DEBUG_MU = false; + private static final String RUNTIME_PERMISSIONS_FILE_NAME = "runtime-permissions.xml"; + private static final String TAG_READ_EXTERNAL_STORAGE = "read-external-storage"; private static final String ATTR_ENFORCEMENT = "enforcement"; @@ -142,6 +154,9 @@ final class Settings { private static final String TAG_ENABLED_COMPONENTS = "enabled-components"; private static final String TAG_PACKAGE_RESTRICTIONS = "package-restrictions"; private static final String TAG_PACKAGE = "pkg"; + private static final String TAG_SHARED_USER = "shared-user"; + private static final String TAG_RUNTIME_PERMISSIONS = "runtime-permissions"; + private static final String TAG_PERMISSIONS = "perms"; private static final String TAG_PERSISTENT_PREFERRED_ACTIVITIES = "persistent-preferred-activities"; static final String TAG_CROSS_PROFILE_INTENT_FILTERS = @@ -161,6 +176,11 @@ final class Settings { private static final String ATTR_INSTALLED = "inst"; private static final String ATTR_BLOCK_UNINSTALL = "blockUninstall"; + private final Object mLock; + private final Context mContext; + + private final RuntimePermissionPersistence mRuntimePermissionsPersistence; + private final File mSettingsFilename; private final File mBackupSettingsFilename; private final File mPackageListFilename; @@ -257,11 +277,16 @@ final class Settings { public final KeySetManagerService mKeySetManagerService = new KeySetManagerService(mPackages); - Settings(Context context) { - this(context, Environment.getDataDirectory()); + Settings(Context context, Object lock) { + this(context, Environment.getDataDirectory(), lock); } - Settings(Context context, File dataDir) { + Settings(Context context, File dataDir, Object lock) { + mContext = context; + mLock = lock; + + mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock); + mSystemDir = new File(dataDir, "system"); mSystemDir.mkdirs(); FileUtils.setPermissions(mSystemDir.toString(), @@ -468,9 +493,9 @@ final class Settings { private PackageSetting getPackageLPw(String name, PackageSetting origPackage, String realName, SharedUserSetting sharedUser, File codePath, File resourcePath, - String legacyNativeLibraryPathString, String primaryCpuAbiString, String secondaryCpuAbiString, - int vc, int pkgFlags, int pkgPrivateFlags, UserHandle installUser, boolean add, - boolean allowInstall) { + String legacyNativeLibraryPathString, String primaryCpuAbiString, + String secondaryCpuAbiString, int vc, int pkgFlags, int pkgPrivateFlags, + UserHandle installUser, boolean add, boolean allowInstall) { PackageSetting p = mPackages.get(name); UserManagerService userManager = UserManagerService.getInstance(); if (p != null) { @@ -589,7 +614,7 @@ final class Settings { } p.appId = dis.appId; // Clone permissions - p.grantedPermissions = new ArraySet<String>(dis.grantedPermissions); + p.getPermissionsState().copyFrom(dis.getPermissionsState()); // Clone component info List<UserInfo> users = getAllUsers(); if (users != null) { @@ -732,45 +757,60 @@ final class Settings { * not in use by other permissions of packages in the * shared user setting. */ - void updateSharedUserPermsLPw(PackageSetting deletedPs, int[] globalGids) { + int updateSharedUserPermsLPw(PackageSetting deletedPs, int userId) { if ((deletedPs == null) || (deletedPs.pkg == null)) { Slog.i(PackageManagerService.TAG, "Trying to update info for null package. Just ignoring"); - return; + return UserHandle.USER_NULL; } + // No sharedUserId if (deletedPs.sharedUser == null) { - return; + return UserHandle.USER_NULL; } + SharedUserSetting sus = deletedPs.sharedUser; + // Update permissions for (String eachPerm : deletedPs.pkg.requestedPermissions) { - boolean used = false; - if (!sus.grantedPermissions.contains(eachPerm)) { + BasePermission bp = mPermissions.get(eachPerm); + if (bp == null) { continue; } - for (PackageSetting pkg:sus.packages) { - if (pkg.pkg != null && - !pkg.pkg.packageName.equals(deletedPs.pkg.packageName) && - pkg.pkg.requestedPermissions.contains(eachPerm)) { + + // If no user has the permission, nothing to remove. + if (!sus.getPermissionsState().hasPermission(bp.name, userId)) { + continue; + } + + boolean used = false; + + // Check if another package in the shared user needs the permission. + for (PackageSetting pkg : sus.packages) { + if (pkg.pkg != null + && !pkg.pkg.packageName.equals(deletedPs.pkg.packageName) + && pkg.pkg.requestedPermissions.contains(eachPerm)) { used = true; break; } } + if (!used) { - // can safely delete this permission from list - sus.grantedPermissions.remove(eachPerm); - } - } - // Update gids - int newGids[] = globalGids; - for (String eachPerm : sus.grantedPermissions) { - BasePermission bp = mPermissions.get(eachPerm); - if (bp != null) { - newGids = PackageManagerService.appendInts(newGids, bp.gids); + // Try to revoke as an install permission which is for all users. + if (sus.getPermissionsState().revokeInstallPermission(bp) == + PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) { + return UserHandle.USER_ALL; + } + + // Try to revoke as an install permission which is per user. + if (sus.getPermissionsState().revokeRuntimePermission(bp, userId) == + PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) { + return userId; + } } } - sus.gids = newGids; + + return UserHandle.USER_NULL; } int removePackageLPw(String name) { @@ -895,7 +935,17 @@ final class Settings { } private File getUserPackagesStateFile(int userId) { - return new File(Environment.getUserSystemDirectory(userId), "package-restrictions.xml"); + // TODO: Implement a cleaner solution when adding tests. + // This instead of Environment.getUserSystemDirectory(userId) to support testing. + File userDir = new File(new File(mSystemDir, "users"), Integer.toString(userId)); + return new File(userDir, "package-restrictions.xml"); + } + + private File getUserRuntimePermissionsFile(int userId) { + // TODO: Implement a cleaner solution when adding tests. + // This instead of Environment.getUserSystemDirectory(userId) to support testing. + File userDir = new File(new File(mSystemDir, "users"), Integer.toString(userId)); + return new File(userDir, RUNTIME_PERMISSIONS_FILE_NAME); } private File getUserPackagesStateBackupFile(int userId) { @@ -912,15 +962,9 @@ final class Settings { } } - void readAllUsersPackageRestrictionsLPr() { - List<UserInfo> users = getAllUsers(); - if (users == null) { - readPackageRestrictionsLPr(0); - return; - } - - for (UserInfo user : users) { - readPackageRestrictionsLPr(user.id); + void writeAllRuntimePermissionsLPr() { + for (int userId : UserManagerService.getInstance().getUserIds()) { + mRuntimePermissionsPersistence.writePermissionsForUserAsyncLPr(userId); } } @@ -1360,6 +1404,7 @@ final class Settings { } serializer.endTag(null, TAG_DISABLED_COMPONENTS); } + serializer.endTag(null, TAG_PACKAGE); } } @@ -1403,6 +1448,58 @@ final class Settings { } } + void readInstallPermissionsLPr(XmlPullParser parser, + PermissionsState permissionsState) throws IOException, XmlPullParserException { + int outerDepth = parser.getDepth(); + int type; + while ((type=parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG + || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG + || type == XmlPullParser.TEXT) { + continue; + } + String tagName = parser.getName(); + if (tagName.equals(TAG_ITEM)) { + String name = parser.getAttributeValue(null, ATTR_NAME); + + BasePermission bp = mPermissions.get(name); + if (bp == null) { + Slog.w(PackageManagerService.TAG, "Unknown permission: " + name); + XmlUtils.skipCurrentTag(parser); + continue; + } + + if (permissionsState.grantInstallPermission(bp) == + PermissionsState.PERMISSION_OPERATION_FAILURE) { + Slog.w(PackageManagerService.TAG, "Permission already added: " + name); + XmlUtils.skipCurrentTag(parser); + } + } else { + Slog.w(PackageManagerService.TAG, "Unknown element under <permissions>: " + + parser.getName()); + XmlUtils.skipCurrentTag(parser); + } + } + } + + void writePermissionsLPr(XmlSerializer serializer, Set<String> permissions) + throws IOException { + if (permissions.isEmpty()) { + return; + } + + serializer.startTag(null, TAG_PERMISSIONS); + + for (String permission : permissions) { + serializer.startTag(null, TAG_ITEM); + serializer.attribute(null, ATTR_NAME, permission); + serializer.endTag(null, TAG_ITEM); + } + + serializer.endTag(null, TAG_PERMISSIONS); + } + // Note: assumed "stopped" field is already cleared in all packages. // Legacy reader, used to read in the old file format after an upgrade. Not used after that. void readStoppedLPw() { @@ -1594,13 +1691,7 @@ final class Settings { serializer.attribute(null, "userId", Integer.toString(usr.userId)); usr.signatures.writeXml(serializer, "sigs", mPastSignatures); - serializer.startTag(null, "perms"); - for (String name : usr.grantedPermissions) { - serializer.startTag(null, TAG_ITEM); - serializer.attribute(null, ATTR_NAME, name); - serializer.endTag(null, TAG_ITEM); - } - serializer.endTag(null, "perms"); + writePermissionsLPr(serializer, usr.getPermissionsState().getInstallPermissions()); serializer.endTag(null, "shared-user"); } @@ -1614,7 +1705,7 @@ final class Settings { serializer.endTag(null, "cleaning-package"); } } - + if (mRenamedPackages.size() > 0) { for (Map.Entry<String, String> e : mRenamedPackages.entrySet()) { serializer.startTag(null, "renamed-package"); @@ -1623,7 +1714,7 @@ final class Settings { serializer.endTag(null, "renamed-package"); } } - + mKeySetManagerService.writeKeySetManagerServiceLPr(serializer); serializer.endTag(null, "packages"); @@ -1662,7 +1753,7 @@ final class Settings { final ApplicationInfo ai = pkg.pkg.applicationInfo; final String dataPath = ai.dataDir; final boolean isDebug = (ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; - final int[] gids = pkg.getGids(); + final int[] gids = pkg.getPermissionsState().computeGids(); // Avoid any application that has a space in its path. if (dataPath.indexOf(" ") >= 0) @@ -1718,6 +1809,8 @@ final class Settings { } writeAllUsersPackageRestrictionsLPr(); + + writeAllRuntimePermissionsLPr(); return; } catch(XmlPullParserException e) { @@ -1770,26 +1863,12 @@ final class Settings { } else { serializer.attribute(null, "sharedUserId", Integer.toString(pkg.appId)); } - serializer.startTag(null, "perms"); + + // If this is a shared user, the permissions will be written there. if (pkg.sharedUser == null) { - // If this is a shared user, the permissions will - // be written there. We still need to write an - // empty permissions list so permissionsFixed will - // be set. - for (final String name : pkg.grantedPermissions) { - BasePermission bp = mPermissions.get(name); - if (bp != null) { - // We only need to write signature or system permissions but - // this wont - // match the semantics of grantedPermissions. So write all - // permissions. - serializer.startTag(null, TAG_ITEM); - serializer.attribute(null, ATTR_NAME, name); - serializer.endTag(null, TAG_ITEM); - } - } + writePermissionsLPr(serializer, pkg.getPermissionsState().getInstallPermissions()); } - serializer.endTag(null, "perms"); + serializer.endTag(null, "updated-package"); } @@ -1840,19 +1919,7 @@ final class Settings { } pkg.signatures.writeXml(serializer, "sigs", mPastSignatures); if ((pkg.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0) { - serializer.startTag(null, "perms"); - if (pkg.sharedUser == null) { - // If this is a shared user, the permissions will - // be written there. We still need to write an - // empty permissions list so permissionsFixed will - // be set. - for (final String name : pkg.grantedPermissions) { - serializer.startTag(null, TAG_ITEM); - serializer.attribute(null, ATTR_NAME, name); - serializer.endTag(null, TAG_ITEM); - } - } - serializer.endTag(null, "perms"); + writePermissionsLPr(serializer, pkg.getPermissionsState().getInstallPermissions()); } writeSigningKeySetsLPr(serializer, pkg.keySetData); @@ -2161,9 +2228,11 @@ final class Settings { } else { if (users == null) { readPackageRestrictionsLPr(0); + mRuntimePermissionsPersistence.readStateForUserSyncLPr(UserHandle.USER_OWNER); } else { for (UserInfo user : users) { readPackageRestrictionsLPr(user.id); + mRuntimePermissionsPersistence.readStateForUserSyncLPr(user.id); } } } @@ -2537,7 +2606,7 @@ final class Settings { final String ptype = parser.getAttributeValue(null, "type"); if (name != null && sourcePackage != null) { final boolean dynamic = "dynamic".equals(ptype); - final BasePermission bp = new BasePermission(name, sourcePackage, + final BasePermission bp = new BasePermission(name.intern(), sourcePackage, dynamic ? BasePermission.TYPE_DYNAMIC : BasePermission.TYPE_NORMAL); bp.protectionLevel = readInt(parser, null, "protection", PermissionInfo.PROTECTION_NORMAL); @@ -2643,6 +2712,7 @@ final class Settings { String sharedIdStr = parser.getAttributeValue(null, "sharedUserId"); ps.appId = sharedIdStr != null ? Integer.parseInt(sharedIdStr) : 0; } + int outerDepth = parser.getDepth(); int type; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT @@ -2651,9 +2721,8 @@ final class Settings { continue; } - String tagName = parser.getName(); - if (tagName.equals("perms")) { - readGrantedPermissionsLPw(parser, ps.grantedPermissions); + if (parser.getName().equals(TAG_PERMISSIONS)) { + readInstallPermissionsLPr(parser, ps.getPermissionsState()); } else { PackageManagerService.reportSettingsProblem(Log.WARN, "Unknown element under <updated-package>: " + parser.getName()); @@ -2711,7 +2780,7 @@ final class Settings { if (primaryCpuAbiString == null && legacyCpuAbiString != null) { primaryCpuAbiString = legacyCpuAbiString; } -; + version = parser.getAttributeValue(null, "version"); if (version != null) { try { @@ -2902,7 +2971,6 @@ final class Settings { packageSetting.installStatus = PackageSettingBase.PKG_INSTALL_COMPLETE; } } - int outerDepth = parser.getDepth(); int type; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT @@ -2919,8 +2987,9 @@ final class Settings { readEnabledComponentsLPw(packageSetting, parser, 0); } else if (tagName.equals("sigs")) { packageSetting.signatures.readXml(parser, mPastSignatures); - } else if (tagName.equals("perms")) { - readGrantedPermissionsLPw(parser, packageSetting.grantedPermissions); + } else if (tagName.equals(TAG_PERMISSIONS)) { + readInstallPermissionsLPr(parser, + packageSetting.getPermissionsState()); packageSetting.permissionsFixed = true; } else if (tagName.equals("proper-signing-keyset")) { long id = Long.parseLong(parser.getAttributeValue(null, "identifier")); @@ -3039,7 +3108,6 @@ final class Settings { "Error in package manager settings: package " + name + " has bad userId " + idStr + " at " + parser.getPositionDescription()); } - ; if (su != null) { int outerDepth = parser.getDepth(); @@ -3054,47 +3122,18 @@ final class Settings { if (tagName.equals("sigs")) { su.signatures.readXml(parser, mPastSignatures); } else if (tagName.equals("perms")) { - readGrantedPermissionsLPw(parser, su.grantedPermissions); + readInstallPermissionsLPr(parser, su.getPermissionsState()); } else { PackageManagerService.reportSettingsProblem(Log.WARN, "Unknown element under <shared-user>: " + parser.getName()); XmlUtils.skipCurrentTag(parser); } } - } else { XmlUtils.skipCurrentTag(parser); } } - private void readGrantedPermissionsLPw(XmlPullParser parser, ArraySet<String> outPerms) - throws IOException, XmlPullParserException { - int outerDepth = parser.getDepth(); - int type; - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { - continue; - } - - String tagName = parser.getName(); - if (tagName.equals(TAG_ITEM)) { - String name = parser.getAttributeValue(null, ATTR_NAME); - if (name != null) { - outPerms.add(name.intern()); - } else { - PackageManagerService.reportSettingsProblem(Log.WARN, - "Error in package manager settings: <perms> has" + " no name at " - + parser.getPositionDescription()); - } - } else { - PackageManagerService.reportSettingsProblem(Log.WARN, - "Unknown element under <perms>: " + parser.getName()); - } - XmlUtils.skipCurrentTag(parser); - } - } - void createNewUserLILPw(PackageManagerService service, Installer installer, int userHandle, File path) { path.mkdir(); @@ -3126,6 +3165,8 @@ final class Settings { file = getUserPackagesStateBackupFile(userId); file.delete(); removeCrossProfileIntentFiltersLPw(userId); + + mRuntimePermissionsPersistence.onUserRemoved(userId); } void removeCrossProfileIntentFiltersLPw(int userId) { @@ -3317,7 +3358,7 @@ final class Settings { return null; } - static final void printFlags(PrintWriter pw, int val, Object[] spec) { + static void printFlags(PrintWriter pw, int val, Object[] spec) { pw.print("[ "); for (int i=0; i<spec.length; i+=2) { int mask = (Integer)spec[i]; @@ -3414,8 +3455,8 @@ final class Settings { pw.println(ps.name); } - pw.print(prefix); pw.print(" userId="); pw.print(ps.appId); - pw.print(" gids="); pw.println(PackageManagerService.arrayToString(ps.gids)); + pw.print(prefix); pw.print(" userId="); pw.println(ps.appId); + if (ps.sharedUser != null) { pw.print(prefix); pw.print(" sharedUser="); pw.println(ps.sharedUser); } @@ -3525,10 +3566,15 @@ final class Settings { } pw.print(prefix); pw.print(" signatures="); pw.println(ps.signatures); pw.print(prefix); pw.print(" permissionsFixed="); pw.print(ps.permissionsFixed); - pw.print(" haveGids="); pw.print(ps.haveGids); pw.print(" installStatus="); pw.println(ps.installStatus); pw.print(prefix); pw.print(" pkgFlags="); printFlags(pw, ps.pkgFlags, FLAG_DUMP_SPEC); pw.println(); + + if (ps.sharedUser == null) { + PermissionsState permissionsState = ps.getPermissionsState(); + dumpInstallPermissionsLPr(pw, prefix + " ", permissionsState); + } + for (UserInfo user : users) { pw.print(prefix); pw.print(" User "); pw.print(user.id); pw.print(": "); pw.print(" installed="); @@ -3546,6 +3592,14 @@ final class Settings { pw.print(prefix); pw.print(" lastDisabledCaller: "); pw.println(lastDisabledAppCaller); } + + if (ps.sharedUser == null) { + PermissionsState permissionsState = ps.getPermissionsState(); + dumpGidsLPr(pw, prefix + " ", permissionsState.computeGids(user.id)); + dumpRuntimePermissionsLPr(pw, prefix + " ", permissionsState + .getRuntimePermissions(user.id)); + } + ArraySet<String> cmp = ps.getDisabledComponents(user.id); if (cmp != null && cmp.size() > 0) { pw.print(prefix); pw.println(" disabledComponents:"); @@ -3561,12 +3615,6 @@ final class Settings { } } } - if (ps.grantedPermissions.size() > 0) { - pw.print(prefix); pw.println(" grantedPermissions:"); - for (String s : ps.grantedPermissions) { - pw.print(prefix); pw.print(" "); pw.println(s); - } - } } void dumpPackagesLPr(PrintWriter pw, String packageName, DumpState dumpState, boolean checkin) { @@ -3688,14 +3736,21 @@ final class Settings { pw.print("] ("); pw.print(Integer.toHexString(System.identityHashCode(su))); pw.println("):"); - pw.print(" userId="); - pw.print(su.userId); - pw.print(" gids="); - pw.println(PackageManagerService.arrayToString(su.gids)); - pw.println(" grantedPermissions:"); - for (String s : su.grantedPermissions) { - pw.print(" "); - pw.println(s); + + String prefix = " "; + pw.print(prefix); pw.print("userId="); pw.println(su.userId); + + PermissionsState permissionsState = su.getPermissionsState(); + dumpInstallPermissionsLPr(pw, prefix, permissionsState); + + for (int userId : UserManagerService.getInstance().getUserIds()) { + final int[] gids = permissionsState.computeGids(userId); + Set<String> permissions = permissionsState.getRuntimePermissions(userId); + if (!ArrayUtils.isEmpty(gids) || !permissions.isEmpty()) { + pw.print(prefix); pw.print("User "); pw.print(userId); pw.println(": "); + dumpGidsLPr(pw, prefix + " ", gids); + dumpRuntimePermissionsLPr(pw, prefix + " ", permissions); + } } } else { pw.print("suid,"); pw.print(su.userId); pw.print(","); pw.println(su.name); @@ -3730,4 +3785,373 @@ final class Settings { pw.print("]"); } } + + void dumpGidsLPr(PrintWriter pw, String prefix, int[] gids) { + if (!ArrayUtils.isEmpty(gids)) { + pw.print(prefix); pw.print("gids="); pw.println( + PackageManagerService.arrayToString(gids)); + } + } + + void dumpRuntimePermissionsLPr(PrintWriter pw, String prefix, Set<String> permissions) { + if (!permissions.isEmpty()) { + pw.print(prefix); pw.println("runtime permissions:"); + for (String permission : permissions) { + pw.print(prefix); pw.print(" "); pw.println(permission); + } + } + } + + void dumpInstallPermissionsLPr(PrintWriter pw, String prefix, + PermissionsState permissionsState) { + Set<String> permissions = permissionsState.getInstallPermissions(); + if (!permissions.isEmpty()) { + pw.print(prefix); pw.println("install permissions:"); + for (String permission : permissions) { + pw.print(prefix); pw.print(" "); pw.println(permission); + } + } + } + + public void writeRuntimePermissionsForUserLPr(int userId, boolean sync) { + if (sync) { + mRuntimePermissionsPersistence.writePermissionsForUserSyncLPr(userId); + } else { + mRuntimePermissionsPersistence.writePermissionsForUserAsyncLPr(userId); + } + } + + private final class RuntimePermissionPersistence { + private static final long WRITE_PERMISSIONS_DELAY_MILLIS = 200; + + private static final long MAX_WRITE_PERMISSIONS_DELAY_MILLIS = 2000; + + private final Handler mHandler = new MyHandler(); + + private final Object mLock; + + @GuardedBy("mLock") + private SparseBooleanArray mWriteScheduled = new SparseBooleanArray(); + + @GuardedBy("mLock") + private SparseLongArray mLastNotWrittenMutationTimesMillis = new SparseLongArray(); + + public RuntimePermissionPersistence(Object lock) { + mLock = lock; + } + + public void writePermissionsForUserSyncLPr(int userId) { + mHandler.removeMessages(userId); + writePermissionsSync(userId); + } + + public void writePermissionsForUserAsyncLPr(int userId) { + final long currentTimeMillis = SystemClock.uptimeMillis(); + + if (mWriteScheduled.get(userId)) { + mHandler.removeMessages(userId); + + // If enough time passed, write without holding off anymore. + final long lastNotWrittenMutationTimeMillis = mLastNotWrittenMutationTimesMillis + .get(userId); + final long timeSinceLastNotWrittenMutationMillis = currentTimeMillis + - lastNotWrittenMutationTimeMillis; + if (timeSinceLastNotWrittenMutationMillis >= MAX_WRITE_PERMISSIONS_DELAY_MILLIS) { + mHandler.obtainMessage(userId).sendToTarget(); + return; + } + + // Hold off a bit more as settings are frequently changing. + final long maxDelayMillis = Math.max(lastNotWrittenMutationTimeMillis + + MAX_WRITE_PERMISSIONS_DELAY_MILLIS - currentTimeMillis, 0); + final long writeDelayMillis = Math.min(WRITE_PERMISSIONS_DELAY_MILLIS, + maxDelayMillis); + + Message message = mHandler.obtainMessage(userId); + mHandler.sendMessageDelayed(message, writeDelayMillis); + } else { + mLastNotWrittenMutationTimesMillis.put(userId, currentTimeMillis); + Message message = mHandler.obtainMessage(userId); + mHandler.sendMessageDelayed(message, WRITE_PERMISSIONS_DELAY_MILLIS); + mWriteScheduled.put(userId, true); + } + } + + private void writePermissionsSync(int userId) { + AtomicFile destination = new AtomicFile(getUserRuntimePermissionsFile(userId)); + + ArrayMap<String, Set<String>> permissionsForPackage = new ArrayMap<>(); + ArrayMap<String, Set<String>> permissionsForSharedUser = new ArrayMap<>(); + + synchronized (mLock) { + mWriteScheduled.delete(userId); + + final int packageCount = mPackages.size(); + for (int i = 0; i < packageCount; i++) { + String packageName = mPackages.keyAt(i); + PackageSetting packageSetting = mPackages.valueAt(i); + if (packageSetting.sharedUser == null) { + PermissionsState permissionsState = packageSetting.getPermissionsState(); + Set<String> permissions = permissionsState.getRuntimePermissions(userId); + if (!permissions.isEmpty()) { + permissionsForPackage.put(packageName, permissions); + } + } + } + + final int sharedUserCount = mSharedUsers.size(); + for (int i = 0; i < sharedUserCount; i++) { + String sharedUserName = mSharedUsers.keyAt(i); + SharedUserSetting sharedUser = mSharedUsers.valueAt(i); + PermissionsState permissionsState = sharedUser.getPermissionsState(); + Set<String> permissions = permissionsState.getRuntimePermissions(userId); + if (!permissions.isEmpty()) { + permissionsForSharedUser.put(sharedUserName, permissions); + } + } + } + + FileOutputStream out = null; + try { + out = destination.startWrite(); + + XmlSerializer serializer = Xml.newSerializer(); + serializer.setOutput(out, "utf-8"); + serializer.setFeature( + "http://xmlpull.org/v1/doc/features.html#indent-output", true); + serializer.startDocument(null, true); + serializer.startTag(null, TAG_RUNTIME_PERMISSIONS); + + final int packageCount = permissionsForPackage.size(); + for (int i = 0; i < packageCount; i++) { + String packageName = permissionsForPackage.keyAt(i); + Set<String> permissions = permissionsForPackage.valueAt(i); + serializer.startTag(null, TAG_PACKAGE); + serializer.attribute(null, ATTR_NAME, packageName); + writePermissions(serializer, permissions); + serializer.endTag(null, TAG_PACKAGE); + } + + final int sharedUserCount = permissionsForSharedUser.size(); + for (int i = 0; i < sharedUserCount; i++) { + String packageName = permissionsForSharedUser.keyAt(i); + Set<String> permissions = permissionsForSharedUser.valueAt(i); + serializer.startTag(null, TAG_SHARED_USER); + serializer.attribute(null, ATTR_NAME, packageName); + writePermissions(serializer, permissions); + serializer.endTag(null, TAG_SHARED_USER); + } + + serializer.endTag(null, TAG_RUNTIME_PERMISSIONS); + serializer.endDocument(); + destination.finishWrite(out); + } catch (IOException e) { + Slog.wtf(PackageManagerService.TAG, + "Failed to write settings, restoring backup", e); + destination.failWrite(out); + } finally { + IoUtils.closeQuietly(out); + } + } + + private void onUserRemoved(int userId) { + // Make sure we do not + mHandler.removeMessages(userId); + + for (SettingBase sb : mPackages.values()) { + revokeRuntimePermissions(sb, userId); + } + + for (SettingBase sb : mSharedUsers.values()) { + revokeRuntimePermissions(sb, userId); + } + } + + private void revokeRuntimePermissions(SettingBase sb, int userId) { + PermissionsState permissionsState = sb.getPermissionsState(); + for (String permission : permissionsState.getRuntimePermissions(userId)) { + BasePermission bp = mPermissions.get(permission); + if (bp != null) { + permissionsState.revokeRuntimePermission(bp, userId); + } + } + } + + public void readStateForUserSyncLPr(int userId) { + File permissionsFile = getUserRuntimePermissionsFile(userId); + if (!permissionsFile.exists()) { + return; + } + + FileInputStream in; + try { + in = new FileInputStream(permissionsFile); + } catch (FileNotFoundException fnfe) { + Slog.i(PackageManagerService.TAG, "No permissions state"); + return; + } + + try { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, null); + parseRuntimePermissionsLPr(parser, userId); + } catch (XmlPullParserException | IOException ise) { + throw new IllegalStateException("Failed parsing permissions file: " + + permissionsFile , ise); + } finally { + IoUtils.closeQuietly(in); + } + } + + private void parseRuntimePermissionsLPr(XmlPullParser parser, int userId) + throws IOException, XmlPullParserException { + parser.next(); + skipEmptyTextTags(parser); + if (!accept(parser, XmlPullParser.START_TAG, TAG_RUNTIME_PERMISSIONS)) { + return; + } + + parser.next(); + + while (parsePackageLPr(parser, userId) + || parseSharedUserLPr(parser, userId)) { + parser.next(); + } + + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_RUNTIME_PERMISSIONS); + } + + private boolean parsePackageLPr(XmlPullParser parser, int userId) + throws IOException, XmlPullParserException { + skipEmptyTextTags(parser); + if (!accept(parser, XmlPullParser.START_TAG, TAG_PACKAGE)) { + return false; + } + + String name = parser.getAttributeValue(null, ATTR_NAME); + + parser.next(); + + PackageSetting ps = mPackages.get(name); + if (ps != null) { + while (parsePermissionLPr(parser, ps.getPermissionsState(), userId)) { + parser.next(); + } + } + + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_PACKAGE); + + return true; + } + + private boolean parseSharedUserLPr(XmlPullParser parser, int userId) + throws IOException, XmlPullParserException { + skipEmptyTextTags(parser); + if (!accept(parser, XmlPullParser.START_TAG, TAG_SHARED_USER)) { + return false; + } + + String name = parser.getAttributeValue(null, ATTR_NAME); + + parser.next(); + + SharedUserSetting sus = mSharedUsers.get(name); + if (sus != null) { + while (parsePermissionLPr(parser, sus.getPermissionsState(), userId)) { + parser.next(); + } + } + + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_SHARED_USER); + + return true; + } + + private boolean parsePermissionLPr(XmlPullParser parser, PermissionsState permissionsState, + int userId) throws IOException, XmlPullParserException { + skipEmptyTextTags(parser); + if (!accept(parser, XmlPullParser.START_TAG, TAG_ITEM)) { + return false; + } + + String name = parser.getAttributeValue(null, ATTR_NAME); + + parser.next(); + + BasePermission bp = mPermissions.get(name); + if (bp != null) { + if (permissionsState.grantRuntimePermission(bp, userId) == + PermissionsState.PERMISSION_OPERATION_FAILURE) { + Slog.w(PackageManagerService.TAG, "Duplicate permission:" + name); + } + } else { + Slog.w(PackageManagerService.TAG, "Unknown permission:" + name); + } + + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_ITEM); + + return true; + } + + private void expect(XmlPullParser parser, int type, String tag) + throws IOException, XmlPullParserException { + if (!accept(parser, type, tag)) { + throw new XmlPullParserException("Expected event: " + type + + " and tag: " + tag + " but got event: " + parser.getEventType() + + " and tag:" + parser.getName()); + } + } + + private void skipEmptyTextTags(XmlPullParser parser) + throws IOException, XmlPullParserException { + while (accept(parser, XmlPullParser.TEXT, null) + && parser.isWhitespace()) { + parser.next(); + } + } + + private boolean accept(XmlPullParser parser, int type, String tag) + throws IOException, XmlPullParserException { + if (parser.getEventType() != type) { + return false; + } + if (tag != null) { + if (!tag.equals(parser.getName())) { + return false; + } + } else if (parser.getName() != null) { + return false; + } + return true; + } + + private void writePermissions(XmlSerializer serializer, Set<String> permissions) + throws IOException { + for (String permission : permissions) { + serializer.startTag(null, TAG_ITEM); + serializer.attribute(null, ATTR_NAME, permission); + serializer.endTag(null, TAG_ITEM); + } + } + + private final class MyHandler extends Handler { + public MyHandler() { + super(BackgroundThread.getHandler().getLooper()); + } + + @Override + public void handleMessage(Message message) { + final int userId = message.what; + Runnable callback = (Runnable) message.obj; + writePermissionsSync(userId); + if (callback != null) { + callback.run(); + } + } + } + } } diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java index d95739c..06e020a 100644 --- a/services/core/java/com/android/server/pm/SharedUserSetting.java +++ b/services/core/java/com/android/server/pm/SharedUserSetting.java @@ -21,7 +21,7 @@ import android.util.ArraySet; /** * Settings data for a particular shared user ID we know about. */ -final class SharedUserSetting extends GrantedPermissions { +final class SharedUserSetting extends SettingBase { final String name; int userId; diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java index b631331..4dc1131 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -32,7 +32,6 @@ import java.io.FileOutputStream; import java.io.IOException; public class PackageManagerSettingsTests extends AndroidTestCase { - private static final String PACKAGE_NAME_2 = "com.google.app2"; private static final String PACKAGE_NAME_3 = "com.android.app3"; private static final String PACKAGE_NAME_1 = "com.google.app1"; @@ -56,7 +55,7 @@ public class PackageManagerSettingsTests extends AndroidTestCase { writeFile(new File(getContext().getFilesDir(), "system/packages.xml"), ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" + "<packages>" - + "<last-platform-version internal=\"15\" external=\"0\" />" + + "<last-platform-version internal=\"15\" external=\"0\" fingerprint=\"foo\" />" + "<permission-trees>" + "<item name=\"com.google.android.permtree\" package=\"com.google.android.permpackage\" />" + "</permission-trees>" @@ -110,28 +109,32 @@ public class PackageManagerSettingsTests extends AndroidTestCase { .getBytes()); } - @Override - protected void setUp() throws Exception { - super.setUp(); + private void deleteSystemFolder() { + File systemFolder = new File(getContext().getFilesDir(), "system"); + deleteFolder(systemFolder); + } + + private static void deleteFolder(File folder) { + File[] files = folder.listFiles(); + if (files != null) { + for (File file : files) { + deleteFolder(file); + } + } + folder.delete(); } private void writeOldFiles() { + deleteSystemFolder(); writePackagesXml(); writeStoppedPackagesXml(); writePackagesList(); } - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - public void testSettingsReadOld() { - // Debug.waitForDebugger(); - // Write the package files and make sure they're parsed properly the first time writeOldFiles(); - Settings settings = new Settings(getContext(), getContext().getFilesDir()); + Settings settings = new Settings(getContext(), getContext().getFilesDir(), new Object()); assertEquals(true, settings.readLPw(null, null, 0, false)); assertNotNull(settings.peekPackageLPr(PACKAGE_NAME_3)); assertNotNull(settings.peekPackageLPr(PACKAGE_NAME_1)); @@ -149,11 +152,12 @@ public class PackageManagerSettingsTests extends AndroidTestCase { public void testNewPackageRestrictionsFile() { // Write the package files and make sure they're parsed properly the first time writeOldFiles(); - Settings settings = new Settings(getContext(), getContext().getFilesDir()); + Settings settings = new Settings(getContext(), getContext().getFilesDir(), new Object()); assertEquals(true, settings.readLPw(null, null, 0, false)); + settings.writeLPr(); // Create Settings again to make it read from the new files - settings = new Settings(getContext(), getContext().getFilesDir()); + settings = new Settings(getContext(), getContext().getFilesDir(), new Object()); assertEquals(true, settings.readLPw(null, null, 0, false)); PackageSetting ps = settings.peekPackageLPr(PACKAGE_NAME_2); @@ -164,7 +168,7 @@ public class PackageManagerSettingsTests extends AndroidTestCase { public void testEnableDisable() { // Write the package files and make sure they're parsed properly the first time writeOldFiles(); - Settings settings = new Settings(getContext(), getContext().getFilesDir()); + Settings settings = new Settings(getContext(), getContext().getFilesDir(), new Object()); assertEquals(true, settings.readLPw(null, null, 0, false)); // Enable/Disable a package diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java index cfbebba..b265d47 100644 --- a/test-runner/src/android/test/mock/MockContext.java +++ b/test-runner/src/android/test/mock/MockContext.java @@ -506,6 +506,11 @@ public class MockContext extends Context { } @Override + public int checkSelfPermission(String permission) { + throw new UnsupportedOperationException(); + } + + @Override public void enforcePermission( String permission, int pid, int uid, String message) { throw new UnsupportedOperationException(); diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java index 7531d7b..67a8c2b 100644 --- a/test-runner/src/android/test/mock/MockPackageManager.java +++ b/test-runner/src/android/test/mock/MockPackageManager.java @@ -191,13 +191,13 @@ public class MockPackageManager extends PackageManager { /** @hide */ @Override - public void grantPermission(String packageName, String permissionName) { + public void grantPermission(String packageName, String permissionName, UserHandle user) { throw new UnsupportedOperationException(); } /** @hide */ @Override - public void revokePermission(String packageName, String permissionName) { + public void revokePermission(String packageName, String permissionName, UserHandle user) { throw new UnsupportedOperationException(); } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java index e1c58fd..8e74ce1 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java @@ -1010,6 +1010,12 @@ public final class BridgeContext extends Context { } @Override + public int checkSelfPermission(String arg0) { + // pass + return 0; + } + + @Override public int checkPermission(String arg0, int arg1, int arg2, IBinder arg3) { // pass return 0; |