From 5e7a973241eec01db82ba25e84818190402d3990 Mon Sep 17 00:00:00 2001 From: Felipe Leme Date: Mon, 11 Dec 2017 14:37:35 -0800 Subject: Proper autofill fix to let phone process autofill Settings activity. Test: adb shell am start com.android.settings/.RadioInfo Bug: 69981710 Fixes: 70506888 Change-Id: Id29bad2d20b621f7379eb6144c95dcc819949b3d Merged-In: Id29bad2d20b621f7379eb6144c95dcc819949b3d (cherry picked from commit 97f16a76db29269619d9a1b45d4cea49026a5b6a) (cherry picked from commit 92b5d2783a1b97bee476f04754481403839b4e45) --- core/java/android/app/ActivityManagerInternal.java | 5 +++++ .../server/autofill/AutofillManagerServiceImpl.java | 9 ++++++--- .../android/server/am/ActivityManagerService.java | 20 ++++++++++++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index c8d983933fc6..9dceb7f9e433 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -268,4 +268,9 @@ public abstract class ActivityManagerInternal { * @param token The IApplicationToken for the activity */ public abstract void setFocusedActivity(IBinder token); + + /** + * Returns {@code true} if {@code uid} is running an activity from {@code packageName}. + */ + public abstract boolean hasRunningActivity(int uid, @Nullable String packageName); } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index a17c3ca92e5d..6d3398ea2648 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -26,6 +26,7 @@ import static com.android.server.autofill.Helper.sVerbose; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.ActivityManagerInternal; import android.app.AppGlobals; import android.app.IActivityManager; import android.content.ComponentName; @@ -69,6 +70,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.os.HandlerCaller; +import com.android.server.LocalServices; import com.android.server.autofill.ui.AutoFillUI; import java.io.PrintWriter; @@ -417,16 +419,17 @@ final class AutofillManagerServiceImpl { * Asserts the component is owned by the caller. */ private void assertCallerLocked(@NonNull ComponentName componentName) { + final String packageName = componentName.getPackageName(); final PackageManager pm = mContext.getPackageManager(); final int callingUid = Binder.getCallingUid(); final int packageUid; try { - packageUid = pm.getPackageUidAsUser(componentName.getPackageName(), - UserHandle.getCallingUserId()); + packageUid = pm.getPackageUidAsUser(packageName, UserHandle.getCallingUserId()); } catch (NameNotFoundException e) { throw new SecurityException("Could not verify UID for " + componentName); } - if (callingUid != packageUid) { + if (callingUid != packageUid && !LocalServices.getService(ActivityManagerInternal.class) + .hasRunningActivity(callingUid, packageName)) { final String[] packages = pm.getPackagesForUid(callingUid); final String callingPackage = packages != null ? packages[0] : "uid-" + callingUid; Slog.w(TAG, "App (package=" + callingPackage + ", UID=" + callingUid diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 1520b96a4afd..4a2d9c9ca5a4 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -24248,6 +24248,26 @@ public class ActivityManagerService extends IActivityManager.Stub } } } + + @Override + public boolean hasRunningActivity(int uid, @Nullable String packageName) { + if (packageName == null) return false; + + synchronized (ActivityManagerService.this) { + for (int i = 0; i < mLruProcesses.size(); i++) { + final ProcessRecord processRecord = mLruProcesses.get(i); + if (processRecord.uid == uid) { + for (int j = 0; j < processRecord.activities.size(); j++) { + final ActivityRecord activityRecord = processRecord.activities.get(j); + if (packageName.equals(activityRecord.packageName)) { + return true; + } + } + } + } + } + return false; + } } /** -- cgit v1.2.3 From 92115c03a84304758637b51f222aa6280dae5cb5 Mon Sep 17 00:00:00 2001 From: Marco Nelissen Date: Tue, 7 Nov 2017 13:52:02 -0800 Subject: Rework thumbnail cleanup Bug: 63766886 Test: ran CTS tests Change-Id: I1f92bb014e275eafe3f42aef1f8c817f187c6608 (cherry picked from commit 6d2096f3889d38da60099b1b5678347de4f042bf) --- core/java/android/provider/MediaStore.java | 4 +-- media/java/android/media/MediaScanner.java | 52 --------------------------- media/java/android/media/MiniThumbFile.java | 54 +++++++++++++++++++++++++++-- 3 files changed, 54 insertions(+), 56 deletions(-) diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index 13e1e26b51c3..ff4f358529a4 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -694,8 +694,8 @@ public final class MediaStore { // Log.v(TAG, "getThumbnail: origId="+origId+", kind="+kind+", isVideo="+isVideo); // If the magic is non-zero, we simply return thumbnail if it does exist. // querying MediaProvider and simply return thumbnail. - MiniThumbFile thumbFile = new MiniThumbFile(isVideo ? Video.Media.EXTERNAL_CONTENT_URI - : Images.Media.EXTERNAL_CONTENT_URI); + MiniThumbFile thumbFile = MiniThumbFile.instance( + isVideo ? Video.Media.EXTERNAL_CONTENT_URI : Images.Media.EXTERNAL_CONTENT_URI); Cursor c = null; try { long magic = thumbFile.getMagic(origId); diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java index cb4e46fe945a..e66945bd7ec4 100644 --- a/media/java/android/media/MediaScanner.java +++ b/media/java/android/media/MediaScanner.java @@ -323,7 +323,6 @@ public class MediaScanner implements AutoCloseable { private final Uri mAudioUri; private final Uri mVideoUri; private final Uri mImagesUri; - private final Uri mThumbsUri; private final Uri mPlaylistsUri; private final Uri mFilesUri; private final Uri mFilesUriNoNotify; @@ -419,7 +418,6 @@ public class MediaScanner implements AutoCloseable { mAudioUri = Audio.Media.getContentUri(volumeName); mVideoUri = Video.Media.getContentUri(volumeName); mImagesUri = Images.Media.getContentUri(volumeName); - mThumbsUri = Images.Thumbnails.getContentUri(volumeName); mFilesUri = Files.getContentUri(volumeName); mFilesUriNoNotify = mFilesUri.buildUpon().appendQueryParameter("nonotify", "1").build(); @@ -1283,53 +1281,6 @@ public class MediaScanner implements AutoCloseable { } } - private void pruneDeadThumbnailFiles() { - HashSet existingFiles = new HashSet(); - String directory = "/sdcard/DCIM/.thumbnails"; - String [] files = (new File(directory)).list(); - Cursor c = null; - if (files == null) - files = new String[0]; - - for (int i = 0; i < files.length; i++) { - String fullPathString = directory + "/" + files[i]; - existingFiles.add(fullPathString); - } - - try { - c = mMediaProvider.query( - mThumbsUri, - new String [] { "_data" }, - null, - null, - null, null); - Log.v(TAG, "pruneDeadThumbnailFiles... " + c); - if (c != null && c.moveToFirst()) { - do { - String fullPathString = c.getString(0); - existingFiles.remove(fullPathString); - } while (c.moveToNext()); - } - - for (String fileToDelete : existingFiles) { - if (false) - Log.v(TAG, "fileToDelete is " + fileToDelete); - try { - (new File(fileToDelete)).delete(); - } catch (SecurityException ex) { - } - } - - Log.v(TAG, "/pruneDeadThumbnailFiles... " + c); - } catch (RemoteException e) { - // We will soon be killed... - } finally { - if (c != null) { - c.close(); - } - } - } - static class MediaBulkDeleter { StringBuilder whereClause = new StringBuilder(); ArrayList whereArgs = new ArrayList(100); @@ -1373,9 +1324,6 @@ public class MediaScanner implements AutoCloseable { processPlayLists(); } - if (mOriginalCount == 0 && mImagesUri.equals(Images.Media.getContentUri("external"))) - pruneDeadThumbnailFiles(); - // allow GC to clean up mPlayLists.clear(); } diff --git a/media/java/android/media/MiniThumbFile.java b/media/java/android/media/MiniThumbFile.java index 664308c45bf9..98993676ce43 100644 --- a/media/java/android/media/MiniThumbFile.java +++ b/media/java/android/media/MiniThumbFile.java @@ -44,13 +44,14 @@ import java.util.Hashtable; */ public class MiniThumbFile { private static final String TAG = "MiniThumbFile"; - private static final int MINI_THUMB_DATA_FILE_VERSION = 3; + private static final int MINI_THUMB_DATA_FILE_VERSION = 4; public static final int BYTES_PER_MINTHUMB = 10000; private static final int HEADER_SIZE = 1 + 8 + 4; private Uri mUri; private RandomAccessFile mMiniThumbFile; private FileChannel mChannel; private ByteBuffer mBuffer; + private ByteBuffer mEmptyBuffer; private static final Hashtable sThumbFiles = new Hashtable(); @@ -127,9 +128,10 @@ public class MiniThumbFile { return mMiniThumbFile; } - public MiniThumbFile(Uri uri) { + private MiniThumbFile(Uri uri) { mUri = uri; mBuffer = ByteBuffer.allocateDirect(BYTES_PER_MINTHUMB); + mEmptyBuffer = ByteBuffer.allocateDirect(BYTES_PER_MINTHUMB); } public synchronized void deactivate() { @@ -184,6 +186,54 @@ public class MiniThumbFile { return 0; } + public synchronized void eraseMiniThumb(long id) { + RandomAccessFile r = miniThumbDataFile(); + if (r != null) { + long pos = id * BYTES_PER_MINTHUMB; + FileLock lock = null; + try { + mBuffer.clear(); + mBuffer.limit(1 + 8); + + lock = mChannel.lock(pos, BYTES_PER_MINTHUMB, false); + // check that we can read the following 9 bytes + // (1 for the "status" and 8 for the long) + if (mChannel.read(mBuffer, pos) == 9) { + mBuffer.position(0); + if (mBuffer.get() == 1) { + long currentMagic = mBuffer.getLong(); + if (currentMagic == 0) { + // there is no thumbnail stored here + Log.i(TAG, "no thumbnail for id " + id); + return; + } + // zero out the thumbnail slot + // Log.v(TAG, "clearing slot " + id + ", magic " + currentMagic + // + " at offset " + pos); + mChannel.write(mEmptyBuffer, pos); + } + } else { + // Log.v(TAG, "No slot"); + } + } catch (IOException ex) { + Log.v(TAG, "Got exception checking file magic: ", ex); + } catch (RuntimeException ex) { + // Other NIO related exception like disk full, read only channel..etc + Log.e(TAG, "Got exception when reading magic, id = " + id + + ", disk full or mount read-only? " + ex.getClass()); + } finally { + try { + if (lock != null) lock.release(); + } + catch (IOException ex) { + // ignore it. + } + } + } else { + // Log.v(TAG, "No data file"); + } + } + public synchronized void saveMiniThumbToFile(byte[] data, long id, long magic) throws IOException { RandomAccessFile r = miniThumbDataFile(); -- cgit v1.2.3 From 0ec0c2b357e5ac3da480c26b20f6aeaf58f4c903 Mon Sep 17 00:00:00 2001 From: Fyodor Kupolov Date: Fri, 16 Mar 2018 12:20:40 -0700 Subject: Use concrete CREATOR instance for parceling lists Replaced readTypedArrayList/writeTypedArrayList with writeTypedList/createTypedArrayList(CREATOR) Bug: 71508348 Test: CtsAutoFillServiceTestCases pass Merged-In: I2a8321023b40cc74b7026eb0fb32a9cc5f5543a9 Change-Id: Id17d02e40a4ae567bf2d74d2ea8ba4d8a943bdb7 (cherry picked from commit 4921986db76b1580bcb6ec8b2fd381d1364a6325) --- core/java/android/os/Parcel.java | 15 ++++++++------- core/java/android/service/autofill/Dataset.java | 9 +++++---- core/java/android/service/autofill/SaveRequest.java | 8 ++++---- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index fae9d5310f8e..c6d3860c6600 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -1340,6 +1340,13 @@ public final class Parcel { * @see Parcelable */ public final void writeTypedList(List val) { + writeTypedList(val, 0); + } + + /** + * @hide + */ + public void writeTypedList(List val, int parcelableFlags) { if (val == null) { writeInt(-1); return; @@ -1348,13 +1355,7 @@ public final class Parcel { int i=0; writeInt(N); while (i < N) { - T item = val.get(i); - if (item != null) { - writeInt(1); - item.writeToParcel(this, 0); - } else { - writeInt(0); - } + writeTypedObject(val.get(i), parcelableFlags); i++; } } diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java index 65b0efcbe032..21a3df89e0f8 100644 --- a/core/java/android/service/autofill/Dataset.java +++ b/core/java/android/service/autofill/Dataset.java @@ -316,8 +316,8 @@ public final class Dataset implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeParcelable(mPresentation, flags); - parcel.writeTypedArrayList(mFieldIds, flags); - parcel.writeTypedArrayList(mFieldValues, flags); + parcel.writeTypedList(mFieldIds, flags); + parcel.writeTypedList(mFieldValues, flags); parcel.writeParcelableList(mFieldPresentations, flags); parcel.writeParcelable(mAuthentication, flags); parcel.writeString(mId); @@ -333,8 +333,9 @@ public final class Dataset implements Parcelable { final Builder builder = (presentation == null) ? new Builder() : new Builder(presentation); - final ArrayList ids = parcel.readTypedArrayList(null); - final ArrayList values = parcel.readTypedArrayList(null); + final ArrayList ids = parcel.createTypedArrayList(AutofillId.CREATOR); + final ArrayList values = + parcel.createTypedArrayList(AutofillValue.CREATOR); final ArrayList presentations = new ArrayList<>(); parcel.readParcelableList(presentations, null); final int idCount = (ids != null) ? ids.size() : 0; diff --git a/core/java/android/service/autofill/SaveRequest.java b/core/java/android/service/autofill/SaveRequest.java index 9de931542cb9..fc4272dd7742 100644 --- a/core/java/android/service/autofill/SaveRequest.java +++ b/core/java/android/service/autofill/SaveRequest.java @@ -19,9 +19,9 @@ package android.service.autofill; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Bundle; -import android.os.CancellationSignal; import android.os.Parcel; import android.os.Parcelable; + import com.android.internal.util.Preconditions; import java.util.ArrayList; @@ -45,7 +45,7 @@ public final class SaveRequest implements Parcelable { } private SaveRequest(@NonNull Parcel parcel) { - this(parcel.readTypedArrayList(null), parcel.readBundle()); + this(parcel.createTypedArrayList(FillContext.CREATOR), parcel.readBundle()); } /** @@ -57,7 +57,7 @@ public final class SaveRequest implements Parcelable { /** * Gets the extra client state returned from the last {@link - * AutofillService#onFillRequest(FillRequest, CancellationSignal, FillCallback)} + * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)} * fill request}. * * @return The client state. @@ -73,7 +73,7 @@ public final class SaveRequest implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { - parcel.writeTypedArrayList(mFillContexts, flags); + parcel.writeTypedList(mFillContexts, flags); parcel.writeBundle(mClientState); } -- cgit v1.2.3 From 251e895b20cae08dd534278cd2467d5223f6c22d Mon Sep 17 00:00:00 2001 From: y Date: Thu, 5 Apr 2018 17:57:27 -0700 Subject: ResStringPool: Fix security vulnerability Adds detection of attacker-modified size and data fields passed to ResStringPool::setTo(). These attacks are modified apks that AAPT would not normally generate. In the rare case this occurs, the installation cannot be allowed to continue. Bug: 71361168 Bug: 71360999 Test: run cts -m CtsAppSecurityHostTestCases \ -t android.appsecurity.cts.CorruptApkTests Change-Id: If7eb93a9e723b16c8a0556fc4e20006aa0391d57 Merged-In: If7eb93a9e723b16c8a0556fc4e20006aa0391d57 (cherry picked from commit 7e54c3f261d81316b75cb734075319108d8bc1d1) --- libs/androidfw/ResourceTypes.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 733ffb180b3b..401c7b070188 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -457,6 +457,22 @@ status_t ResStringPool::setTo(const void* data, size_t size, bool copyData) uninit(); + // The chunk must be at least the size of the string pool header. + if (size < sizeof(ResStringPool_header)) { + LOG_ALWAYS_FATAL("Bad string block: data size %zu is too small to be a string block", size); + return (mError=BAD_TYPE); + } + + // The data is at least as big as a ResChunk_header, so we can safely validate the other + // header fields. + // `data + size` is safe because the source of `size` comes from the kernel/filesystem. + if (validate_chunk(reinterpret_cast(data), sizeof(ResStringPool_header), + reinterpret_cast(data) + size, + "ResStringPool_header") != NO_ERROR) { + LOG_ALWAYS_FATAL("Bad string block: malformed block dimensions"); + return (mError=BAD_TYPE); + } + const bool notDeviceEndian = htods(0xf0) != 0xf0; if (copyData || notDeviceEndian) { @@ -468,6 +484,8 @@ status_t ResStringPool::setTo(const void* data, size_t size, bool copyData) data = mOwnedData; } + // The size has been checked, so it is safe to read the data in the ResStringPool_header + // data structure. mHeader = (const ResStringPool_header*)data; if (notDeviceEndian) { -- cgit v1.2.3 From d6b3b61648d509383cf06532c8c108a0764d882c Mon Sep 17 00:00:00 2001 From: "Philip P. Moltmann" Date: Thu, 12 Apr 2018 14:45:14 -0700 Subject: DO NOT MERGE (O) Revoke permision when group changed If a run time permission of a group is already granted we grant the other permission of the group automatically when requested. Hence if an already granted permission changed its group during an update suddenly permission of a potentially not approved group will get auto-granted. This is undesirable, hence we revoke the permission during the update process. Test: atest android.permission.cts.PermissionGroupChange Change-Id: Ib2165d1ae53b80455ebe02e07775853e37a2e339 Fixes: 72710897 (cherry picked from commit 0ed1b472af194896fcf421dc862c443343c7c127) --- .../android/server/pm/PackageManagerService.java | 105 +++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 06600bf75ffa..d6b572835950 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -184,6 +184,7 @@ import android.database.ContentObserver; import android.graphics.Bitmap; import android.hardware.display.DisplayManager; import android.net.Uri; +import android.os.AsyncTask; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -5849,6 +5850,83 @@ public class PackageManagerService extends IPackageManager.Stub killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED); } + /** + * We might auto-grant permissions if any permission of the group is already granted. Hence if + * the group of a granted permission changes we need to revoke it to avoid having permissions of + * the new group auto-granted. + * + * @param newPackage The new package that was installed + * @param oldPackage The old package that was updated + * @param allPackageNames All package names + */ + private void revokeRuntimePermissionsIfGroupChanged( + PackageParser.Package newPackage, + PackageParser.Package oldPackage, + ArrayList allPackageNames) { + final int numOldPackagePermissions = oldPackage.permissions.size(); + final ArrayMap oldPermissionNameToGroupName + = new ArrayMap<>(numOldPackagePermissions); + + for (int i = 0; i < numOldPackagePermissions; i++) { + final PackageParser.Permission permission = oldPackage.permissions.get(i); + + if (permission.group != null) { + oldPermissionNameToGroupName.put(permission.info.name, + permission.group.info.name); + } + } + + final int numNewPackagePermissions = newPackage.permissions.size(); + for (int newPermissionNum = 0; newPermissionNum < numNewPackagePermissions; + newPermissionNum++) { + final PackageParser.Permission newPermission = + newPackage.permissions.get(newPermissionNum); + final int newProtection = newPermission.info.protectionLevel; + + if ((newProtection & PermissionInfo.PROTECTION_DANGEROUS) != 0) { + final String permissionName = newPermission.info.name; + final String newPermissionGroupName = + newPermission.group == null ? null : newPermission.group.info.name; + final String oldPermissionGroupName = oldPermissionNameToGroupName.get( + permissionName); + + if (newPermissionGroupName != null + && !newPermissionGroupName.equals(oldPermissionGroupName)) { + final List users = mContext.getSystemService(UserManager.class) + .getUsers(); + + final int numUsers = users.size(); + for (int userNum = 0; userNum < numUsers; userNum++) { + final int userId = users.get(userNum).id; + final int numPackages = allPackageNames.size(); + + for (int packageNum = 0; packageNum < numPackages; packageNum++) { + final String packageName = allPackageNames.get(packageNum); + + if (checkPermission(permissionName, packageName, userId) + == PackageManager.PERMISSION_GRANTED) { + EventLog.writeEvent(0x534e4554, "72710897", + newPackage.applicationInfo.uid, + "Revoking permission", permissionName, "from package", + packageName, "as the group changed from", + oldPermissionGroupName, "to", newPermissionGroupName); + + try { + revokeRuntimePermission(packageName, permissionName, userId, + false); + } catch (IllegalArgumentException e) { + Slog.e(TAG, "Could not revoke " + permissionName + " from " + + packageName, e); + } + } + } + } + } + } + } + } + + /** * Get the first event id for the permission. * @@ -10743,6 +10821,8 @@ public class PackageManagerService extends IPackageManager.Stub String primaryCpuAbiFromSettings = null; String secondaryCpuAbiFromSettings = null; + final PackageParser.Package oldPkg; + // writer synchronized (mPackages) { if (pkg.mSharedUserId != null) { @@ -10843,6 +10923,12 @@ public class PackageManagerService extends IPackageManager.Stub final PackageSetting disabledPkgSetting = mSettings.getDisabledSystemPkgLPr(pkg.packageName); + if (oldPkgSetting == null) { + oldPkg = null; + } else { + oldPkg = oldPkgSetting.pkg; + } + String[] usesStaticLibraries = null; if (pkg.usesStaticLibraries != null) { usesStaticLibraries = new String[pkg.usesStaticLibraries.size()]; @@ -11175,6 +11261,25 @@ public class PackageManagerService extends IPackageManager.Stub mInstantAppRegistry.addInstantAppLPw(userId, pkgSetting.appId); } } + + if (oldPkg != null) { + // We need to call revokeRuntimePermissionsIfGroupChanged async as permission + // revokation from this method might need to kill apps which need the + // mPackages lock on a different thread. This would dead lock. + // + // Hence create a copy of all package names and pass it into + // revokeRuntimePermissionsIfGroupChanged. Only for those permissions might get + // revoked. If a new package is added before the async code runs the permission + // won't be granted yet, hence new packages are no problem. + final ArrayList allPackageNames = new ArrayList<>(mPackages.keySet()); + + AsyncTask.execute(new Runnable() { + public void run() { + revokeRuntimePermissionsIfGroupChanged(pkg, oldPkg, allPackageNames); + } + }); + } + return pkg; } -- cgit v1.2.3