diff options
| author | 2020-09-09 18:09:05 +0000 | |
|---|---|---|
| committer | 2020-09-09 18:10:46 +0000 | |
| commit | a8fee79b8150eb8a7127bb0ecb4144da4374c49a (patch) | |
| tree | 2a2e0925599d7bc5d7b935c4f45cb7fea905c87c | |
| parent | d1f7f96a5803c0d7e11ece57919aae1958b65438 (diff) | |
Revert "clean-up unrequired slice access from default home app"
This reverts commit d1f7f96a5803c0d7e11ece57919aae1958b65438.
Reason for revert: regression, see b/167993144. (original bug: b/163121804)
Change-Id: I54699b02e7de8c6e6ecebe0a5b684b7383caa81b
4 files changed, 252 insertions, 9 deletions
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java index 0e6a0637d801..3522b1b8aff5 100644 --- a/core/java/android/app/usage/UsageEvents.java +++ b/core/java/android/app/usage/UsageEvents.java @@ -199,7 +199,7 @@ public final class UsageEvents implements Parcelable { public static final int NOTIFICATION_INTERRUPTION = 12; /** - * A Slice was pinned by the default assistant. + * A Slice was pinned by the default launcher or the default assistant. * @hide */ @SystemApi diff --git a/services/core/java/com/android/server/slice/SliceManagerService.java b/services/core/java/com/android/server/slice/SliceManagerService.java index 5aedfc19028b..2a74b3d23829 100644 --- a/services/core/java/com/android/server/slice/SliceManagerService.java +++ b/services/core/java/com/android/server/slice/SliceManagerService.java @@ -28,6 +28,8 @@ import static android.os.Process.SYSTEM_UID; import android.Manifest.permission; import android.annotation.NonNull; import android.app.AppOpsManager; +import android.app.role.OnRoleHoldersChangedListener; +import android.app.role.RoleManager; import android.app.slice.ISliceManager; import android.app.slice.SliceSpec; import android.app.usage.UsageStatsManagerInternal; @@ -39,7 +41,9 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; import android.content.pm.ProviderInfo; +import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.Binder; import android.os.Handler; @@ -61,6 +65,7 @@ import com.android.internal.app.AssistUtils; import com.android.server.LocalServices; import com.android.server.ServiceThread; import com.android.server.SystemService; +import com.android.server.SystemService.TargetUser; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -72,7 +77,10 @@ import java.io.ByteArrayOutputStream; import java.io.FileDescriptor; import java.io.IOException; import java.util.ArrayList; +import java.util.List; import java.util.Objects; +import java.util.concurrent.Executor; +import java.util.function.Supplier; public class SliceManagerService extends ISliceManager.Stub { @@ -80,13 +88,16 @@ public class SliceManagerService extends ISliceManager.Stub { private final Object mLock = new Object(); private final Context mContext; + private final PackageManagerInternal mPackageManagerInternal; private final AppOpsManager mAppOps; private final AssistUtils mAssistUtils; @GuardedBy("mLock") private final ArrayMap<Uri, PinnedSliceState> mPinnedSlicesByUri = new ArrayMap<>(); @GuardedBy("mLock") - private final SparseArray<String> mLastAssistantPackage = new SparseArray<>(); + private final SparseArray<PackageMatchingCache> mAssistantLookup = new SparseArray<>(); + @GuardedBy("mLock") + private final SparseArray<PackageMatchingCache> mHomeLookup = new SparseArray<>(); private final Handler mHandler; private final SlicePermissionManager mPermissions; @@ -99,6 +110,8 @@ public class SliceManagerService extends ISliceManager.Stub { @VisibleForTesting SliceManagerService(Context context, Looper looper) { mContext = context; + mPackageManagerInternal = Objects.requireNonNull( + LocalServices.getService(PackageManagerInternal.class)); mAppOps = context.getSystemService(AppOpsManager.class); mAssistUtils = new AssistUtils(context); mHandler = new Handler(looper); @@ -111,6 +124,7 @@ public class SliceManagerService extends ISliceManager.Stub { filter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addDataScheme("package"); + mRoleObserver = new RoleObserver(); mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler); } @@ -160,7 +174,8 @@ public class SliceManagerService extends ISliceManager.Stub { mHandler.post(() -> { if (slicePkg != null && !Objects.equals(pkg, slicePkg)) { mAppUsageStats.reportEvent(slicePkg, user, - isAssistant(pkg, user) ? SLICE_PINNED_PRIV : SLICE_PINNED); + isAssistant(pkg, user) || isDefaultHomeApp(pkg, user) + ? SLICE_PINNED_PRIV : SLICE_PINNED); } }); } @@ -425,19 +440,38 @@ public class SliceManagerService extends ISliceManager.Stub { private boolean hasFullSliceAccess(String pkg, int userId) { long ident = Binder.clearCallingIdentity(); try { - return isAssistant(pkg, userId) || isGrantedFullAccess(pkg, userId); + boolean ret = isDefaultHomeApp(pkg, userId) || isAssistant(pkg, userId) + || isGrantedFullAccess(pkg, userId); + return ret; } finally { Binder.restoreCallingIdentity(ident); } } private boolean isAssistant(String pkg, int userId) { - if (pkg == null) return false; - if (!pkg.equals(mLastAssistantPackage.get(userId))) { - // Failed on cached value, try updating. - mLastAssistantPackage.put(userId, getAssistant(userId)); + return getAssistantMatcher(userId).matches(pkg); + } + + private boolean isDefaultHomeApp(String pkg, int userId) { + return getHomeMatcher(userId).matches(pkg); + } + + private PackageMatchingCache getAssistantMatcher(int userId) { + PackageMatchingCache matcher = mAssistantLookup.get(userId); + if (matcher == null) { + matcher = new PackageMatchingCache(() -> getAssistant(userId)); + mAssistantLookup.put(userId, matcher); } - return pkg.equals(mLastAssistantPackage.get(userId)); + return matcher; + } + + private PackageMatchingCache getHomeMatcher(int userId) { + PackageMatchingCache matcher = mHomeLookup.get(userId); + if (matcher == null) { + matcher = new PackageMatchingCache(() -> getDefaultHome(userId)); + mHomeLookup.put(userId, matcher); + } + return matcher; } private String getAssistant(int userId) { @@ -448,6 +482,111 @@ public class SliceManagerService extends ISliceManager.Stub { return cn.getPackageName(); } + /** + * A cached value of the default home app + */ + private String mCachedDefaultHome = null; + + // Based on getDefaultHome in ShortcutService. + // TODO: Unify if possible + @VisibleForTesting + protected String getDefaultHome(int userId) { + + // Set VERIFY to true to run the cache in "shadow" mode for cache + // testing. Do not commit set to true; + final boolean VERIFY = false; + + if (mCachedDefaultHome != null) { + if (!VERIFY) { + return mCachedDefaultHome; + } + } + + final long token = Binder.clearCallingIdentity(); + try { + final List<ResolveInfo> allHomeCandidates = new ArrayList<>(); + + // Default launcher from package manager. + final ComponentName defaultLauncher = mPackageManagerInternal + .getHomeActivitiesAsUser(allHomeCandidates, userId); + + ComponentName detected = defaultLauncher; + + // Cache the default launcher. It is not a problem if the + // launcher is null - eventually, the default launcher will be + // set to something non-null. + mCachedDefaultHome = ((detected != null) ? detected.getPackageName() : null); + + if (detected == null) { + // If we reach here, that means it's the first check since the user was created, + // and there's already multiple launchers and there's no default set. + // Find the system one with the highest priority. + // (We need to check the priority too because of FallbackHome in Settings.) + // If there's no system launcher yet, then no one can access slices, until + // the user explicitly sets one. + final int size = allHomeCandidates.size(); + + int lastPriority = Integer.MIN_VALUE; + for (int i = 0; i < size; i++) { + final ResolveInfo ri = allHomeCandidates.get(i); + if (!ri.activityInfo.applicationInfo.isSystemApp()) { + continue; + } + if (ri.priority < lastPriority) { + continue; + } + detected = ri.activityInfo.getComponentName(); + lastPriority = ri.priority; + } + } + final String ret = ((detected != null) ? detected.getPackageName() : null); + if (VERIFY) { + if (mCachedDefaultHome != null && !mCachedDefaultHome.equals(ret)) { + Slog.e(TAG, "getDefaultHome() cache failure, is " + + mCachedDefaultHome + " should be " + ret); + } + } + return ret; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + public void invalidateCachedDefaultHome() { + mCachedDefaultHome = null; + } + + /** + * Listen for changes in the roles, and invalidate the cached default + * home as necessary. + */ + private RoleObserver mRoleObserver; + + class RoleObserver implements OnRoleHoldersChangedListener { + private RoleManager mRm; + private final Executor mExecutor; + + RoleObserver() { + mExecutor = mContext.getMainExecutor(); + register(); + } + + public void register() { + mRm = mContext.getSystemService(RoleManager.class); + if (mRm != null) { + mRm.addOnRoleHoldersChangedListenerAsUser(mExecutor, this, UserHandle.ALL); + invalidateCachedDefaultHome(); + } + } + + @Override + public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) { + if (RoleManager.ROLE_HOME.equals(roleName)) { + invalidateCachedDefaultHome(); + } + } + } + private boolean isGrantedFullAccess(String pkg, int userId) { return mPermissions.hasFullAccess(pkg, userId); } @@ -496,6 +635,30 @@ public class SliceManagerService extends ISliceManager.Stub { return mPermissions.getAllPackagesGranted(pkg); } + /** + * Holder that caches a package that has access to a slice. + */ + static class PackageMatchingCache { + + private final Supplier<String> mPkgSource; + private String mCurrentPkg; + + public PackageMatchingCache(Supplier<String> pkgSource) { + mPkgSource = pkgSource; + } + + public boolean matches(String pkgCandidate) { + if (pkgCandidate == null) return false; + + if (Objects.equals(pkgCandidate, mCurrentPkg)) { + return true; + } + // Failed on cached value, try updating. + mCurrentPkg = mPkgSource.get(); + return Objects.equals(pkgCandidate, mCurrentPkg); + } + } + public static class Lifecycle extends SystemService { private SliceManagerService mService; diff --git a/services/tests/uiservicestests/src/com/android/server/slice/PackageMatchingCacheTest.java b/services/tests/uiservicestests/src/com/android/server/slice/PackageMatchingCacheTest.java new file mode 100644 index 000000000000..f6c854e23494 --- /dev/null +++ b/services/tests/uiservicestests/src/com/android/server/slice/PackageMatchingCacheTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2018 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.slice; + +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper.RunWithLooper; + +import androidx.test.filters.SmallTest; + +import com.android.server.UiServiceTestCase; +import com.android.server.slice.SliceManagerService.PackageMatchingCache; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.function.Supplier; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@RunWithLooper +public class PackageMatchingCacheTest extends UiServiceTestCase { + + private final Supplier<String> supplier = mock(Supplier.class); + private final PackageMatchingCache cache = new PackageMatchingCache(supplier); + + @Test + public void testNulls() { + // Doesn't get for a null input + cache.matches(null); + verify(supplier, never()).get(); + + // Gets once valid input in sent. + cache.matches(""); + verify(supplier).get(); + } + + @Test + public void testCaching() { + when(supplier.get()).thenReturn("ret.pkg"); + + assertTrue(cache.matches("ret.pkg")); + assertTrue(cache.matches("ret.pkg")); + assertTrue(cache.matches("ret.pkg")); + + verify(supplier, times(1)).get(); + } + + @Test + public void testGetOnFailure() { + when(supplier.get()).thenReturn("ret.pkg"); + assertTrue(cache.matches("ret.pkg")); + + when(supplier.get()).thenReturn("other.pkg"); + assertTrue(cache.matches("other.pkg")); + verify(supplier, times(2)).get(); + } +} diff --git a/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java index cf1c36c0d243..a4436951f48b 100644 --- a/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java @@ -90,6 +90,8 @@ public class SliceManagerServiceTest extends UiServiceTestCase { @Test public void testAddPinCreatesPinned() throws RemoteException { + doReturn("pkg").when(mService).getDefaultHome(anyInt()); + mService.pinSlice("pkg", TEST_URI, EMPTY_SPECS, mToken); mService.pinSlice("pkg", TEST_URI, EMPTY_SPECS, mToken); verify(mService, times(1)).createPinnedSlice(eq(maybeAddUserId(TEST_URI, 0)), anyString()); @@ -97,6 +99,8 @@ public class SliceManagerServiceTest extends UiServiceTestCase { @Test public void testRemovePinDestroysPinned() throws RemoteException { + doReturn("pkg").when(mService).getDefaultHome(anyInt()); + mService.pinSlice("pkg", TEST_URI, EMPTY_SPECS, mToken); when(mCreatedSliceState.unpin(eq("pkg"), eq(mToken))).thenReturn(false); |