diff options
41 files changed, 1197 insertions, 230 deletions
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java index 27f6a266597c..8926ff7a98c6 100644 --- a/core/java/android/appwidget/AppWidgetHostView.java +++ b/core/java/android/appwidget/AppWidgetHostView.java @@ -20,7 +20,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityOptions; -import android.app.LoadedApk; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; @@ -733,9 +732,6 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW */ protected Context getRemoteContextEnsuringCorrectCachedApkPath() { try { - ApplicationInfo expectedAppInfo = mInfo.providerInfo.applicationInfo; - LoadedApk.checkAndUpdateApkPaths(expectedAppInfo); - // Return if cloned successfully, otherwise default Context newContext = mContext.createApplicationContext( mInfo.providerInfo.applicationInfo, Context.CONTEXT_RESTRICTED); diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index 2e40f6096ccb..71d1015cd6e3 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -460,6 +460,20 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan } /** + * Set the class name of ConfirmDeviceCredentialActivity. + * + * @return This builder. + * @hide + */ + @NonNull + @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL}) + public Builder setClassNameIfItIsConfirmDeviceCredentialActivity() { + mPromptInfo.setClassNameIfItIsConfirmDeviceCredentialActivity( + mContext.getClass().getName()); + return this; + } + + /** * Creates a {@link BiometricPrompt}. * * @return An instance of {@link BiometricPrompt}. diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java index 02aad1dc4b4b..f1fce7290f99 100644 --- a/core/java/android/hardware/biometrics/PromptInfo.java +++ b/core/java/android/hardware/biometrics/PromptInfo.java @@ -48,6 +48,7 @@ public class PromptInfo implements Parcelable { private boolean mAllowBackgroundAuthentication; private boolean mIgnoreEnrollmentState; private boolean mIsForLegacyFingerprintManager = false; + private String mClassNameIfItIsConfirmDeviceCredentialActivity = null; public PromptInfo() { @@ -72,6 +73,7 @@ public class PromptInfo implements Parcelable { mAllowBackgroundAuthentication = in.readBoolean(); mIgnoreEnrollmentState = in.readBoolean(); mIsForLegacyFingerprintManager = in.readBoolean(); + mClassNameIfItIsConfirmDeviceCredentialActivity = in.readString(); } public static final Creator<PromptInfo> CREATOR = new Creator<PromptInfo>() { @@ -111,6 +113,7 @@ public class PromptInfo implements Parcelable { dest.writeBoolean(mAllowBackgroundAuthentication); dest.writeBoolean(mIgnoreEnrollmentState); dest.writeBoolean(mIsForLegacyFingerprintManager); + dest.writeString(mClassNameIfItIsConfirmDeviceCredentialActivity); } public boolean containsTestConfigurations() { @@ -122,6 +125,8 @@ public class PromptInfo implements Parcelable { return true; } else if (mAllowBackgroundAuthentication) { return true; + } else if (mClassNameIfItIsConfirmDeviceCredentialActivity != null) { + return true; } return false; } @@ -222,6 +227,13 @@ public class PromptInfo implements Parcelable { mAllowedSensorIds.add(sensorId); } + /** + * Set the class name of ConfirmDeviceCredentialActivity. + */ + void setClassNameIfItIsConfirmDeviceCredentialActivity(String className) { + mClassNameIfItIsConfirmDeviceCredentialActivity = className; + } + // Getters public CharSequence getTitle() { @@ -303,4 +315,12 @@ public class PromptInfo implements Parcelable { public boolean isForLegacyFingerprintManager() { return mIsForLegacyFingerprintManager; } + + /** + * Get the class name of ConfirmDeviceCredentialActivity. Returns null if the direct caller is + * not ConfirmDeviceCredentialActivity. + */ + public String getClassNameIfItIsConfirmDeviceCredentialActivity() { + return mClassNameIfItIsConfirmDeviceCredentialActivity; + } } diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java index 4e3adfbdda3f..9773134f6419 100644 --- a/core/java/android/os/BaseBundle.java +++ b/core/java/android/os/BaseBundle.java @@ -471,10 +471,10 @@ public class BaseBundle { map.erase(); map.ensureCapacity(count); } - int numLazyValues = 0; + int[] numLazyValues = new int[]{0}; try { - numLazyValues = parcelledData.readArrayMap(map, count, !parcelledByNative, - /* lazy */ ownsParcel, mClassLoader); + parcelledData.readArrayMap(map, count, !parcelledByNative, + /* lazy */ ownsParcel, mClassLoader, numLazyValues); } catch (BadParcelableException e) { if (sShouldDefuse) { Log.w(TAG, "Failed to parse Bundle, but defusing quietly", e); @@ -485,14 +485,14 @@ public class BaseBundle { } finally { mWeakParcelledData = null; if (ownsParcel) { - if (numLazyValues == 0) { + if (numLazyValues[0] == 0) { recycleParcel(parcelledData); } else { mWeakParcelledData = new WeakReference<>(parcelledData); } } - mLazyValues = numLazyValues; + mLazyValues = numLazyValues[0]; mParcelledByNative = false; mMap = map; // Set field last as it is volatile diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 453aba34dbcf..11eb5ccb709c 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -5335,7 +5335,7 @@ public final class Parcel { private void readArrayMapInternal(@NonNull ArrayMap<? super String, Object> outVal, int size, @Nullable ClassLoader loader) { - readArrayMap(outVal, size, /* sorted */ true, /* lazy */ false, loader); + readArrayMap(outVal, size, /* sorted */ true, /* lazy */ false, loader, null); } /** @@ -5345,17 +5345,16 @@ public final class Parcel { * @param lazy Whether to populate the map with lazy {@link Function} objects for * length-prefixed values. See {@link Parcel#readLazyValue(ClassLoader)} for more * details. - * @return a count of the lazy values in the map + * @param lazyValueCount number of lazy values added here * @hide */ - int readArrayMap(ArrayMap<? super String, Object> map, int size, boolean sorted, - boolean lazy, @Nullable ClassLoader loader) { - int lazyValues = 0; + void readArrayMap(ArrayMap<? super String, Object> map, int size, boolean sorted, + boolean lazy, @Nullable ClassLoader loader, int[] lazyValueCount) { while (size > 0) { String key = readString(); Object value = (lazy) ? readLazyValue(loader) : readValue(loader); if (value instanceof LazyValue) { - lazyValues++; + lazyValueCount[0]++; } if (sorted) { map.append(key, value); @@ -5367,7 +5366,6 @@ public final class Parcel { if (sorted) { map.validate(); } - return lazyValues; } /** diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 88ce92f24702..7db77b12b3dc 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -34,7 +34,6 @@ import android.app.Activity; import android.app.ActivityOptions; import android.app.ActivityThread; import android.app.Application; -import android.app.LoadedApk; import android.app.PendingIntent; import android.app.RemoteInput; import android.appwidget.AppWidgetHostView; @@ -6057,9 +6056,18 @@ public class RemoteViews implements Parcelable, Filter { return context; } try { - LoadedApk.checkAndUpdateApkPaths(mApplication); - return context.createApplicationContext(mApplication, + // Use PackageManager as the source of truth for application information, rather + // than the parceled ApplicationInfo provided by the app. + ApplicationInfo sanitizedApplication = + context.getPackageManager().getApplicationInfoAsUser( + mApplication.packageName, 0, + UserHandle.getUserId(mApplication.uid)); + Context applicationContext = context.createApplicationContext( + sanitizedApplication, Context.CONTEXT_RESTRICTED); + // Get the correct apk paths while maintaining the current context's configuration. + return applicationContext.createConfigurationContext( + context.getResources().getConfiguration()); } catch (NameNotFoundException e) { Log.e(LOG_TAG, "Package name " + mApplication.packageName + " not found"); } diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index f9d553ffc0c1..ebd015f1c429 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -21,6 +21,7 @@ import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_PERSONAL; import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_WORK; import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE; +import static android.content.ContentProvider.getUriWithoutUserId; import static android.content.ContentProvider.getUserIdFromUri; import static android.stats.devicepolicy.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL; import static android.stats.devicepolicy.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK; @@ -40,7 +41,9 @@ import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityOptions; +import android.app.IUriGrantsManager; import android.app.SharedElementCallback; +import android.app.UriGrantsManager; import android.app.prediction.AppPredictionContext; import android.app.prediction.AppPredictionManager; import android.app.prediction.AppPredictor; @@ -77,6 +80,7 @@ import android.graphics.Paint; import android.graphics.Path; import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; import android.metrics.LogMaker; import android.net.Uri; import android.os.AsyncTask; @@ -86,6 +90,7 @@ import android.os.Handler; import android.os.Message; import android.os.Parcelable; import android.os.PatternMatcher; +import android.os.RemoteException; import android.os.ResultReceiver; import android.os.UserHandle; import android.os.UserManager; @@ -692,7 +697,11 @@ public class ChooserActivity extends ResolverActivity implements targets = null; break; } - targets[i] = (ChooserTarget) pa[i]; + ChooserTarget chooserTarget = (ChooserTarget) pa[i]; + if (!hasValidIcon(chooserTarget)) { + chooserTarget = removeIcon(chooserTarget); + } + targets[i] = chooserTarget; } mCallerChooserTargets = targets; } @@ -4313,4 +4322,43 @@ public class ChooserActivity extends ResolverActivity implements private boolean shouldNearbyShareBeIncludedAsActionButton() { return !shouldNearbyShareBeFirstInRankedRow(); } + + private boolean hasValidIcon(ChooserTarget target) { + Icon icon = target.getIcon(); + if (icon == null) { + return true; + } + if (icon.getType() == Icon.TYPE_URI || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) { + Uri uri = icon.getUri(); + try { + getUriGrantsManager().checkGrantUriPermission_ignoreNonSystem( + getLaunchedFromUid(), + getPackageName(), + getUriWithoutUserId(uri), + Intent.FLAG_GRANT_READ_URI_PERMISSION, + getUserIdFromUri(uri) + ); + } catch (SecurityException | RemoteException e) { + Log.e(TAG, "Failed to get URI permission for: " + uri, e); + return false; + } + } + return true; + } + + private IUriGrantsManager getUriGrantsManager() { + return UriGrantsManager.getService(); + } + + private static ChooserTarget removeIcon(ChooserTarget target) { + if (target == null) { + return null; + } + return new ChooserTarget( + target.getTitle(), + null, + target.getScore(), + target.getComponentName(), + target.getIntentExtras()); + } } diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java index 65b59790e327..259e82181da1 100644 --- a/core/java/com/android/internal/app/IntentForwarderActivity.java +++ b/core/java/com/android/internal/app/IntentForwarderActivity.java @@ -512,24 +512,35 @@ public class IntentForwarderActivity extends Activity { Intent.FLAG_ACTIVITY_FORWARD_RESULT | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP); sanitizeIntent(forwardIntent); - Intent intentToCheck = forwardIntent; - if (Intent.ACTION_CHOOSER.equals(forwardIntent.getAction())) { + if (!canForwardInner(forwardIntent, sourceUserId, targetUserId, packageManager, + contentResolver)) { return null; } if (forwardIntent.getSelector() != null) { - intentToCheck = forwardIntent.getSelector(); + sanitizeIntent(forwardIntent.getSelector()); + if (!canForwardInner(forwardIntent.getSelector(), sourceUserId, targetUserId, + packageManager, contentResolver)) { + return null; + } + } + return forwardIntent; + } + + private static boolean canForwardInner(Intent intent, int sourceUserId, int targetUserId, + IPackageManager packageManager, ContentResolver contentResolver) { + if (Intent.ACTION_CHOOSER.equals(intent.getAction())) { + return false; } - String resolvedType = intentToCheck.resolveTypeIfNeeded(contentResolver); - sanitizeIntent(intentToCheck); + String resolvedType = intent.resolveTypeIfNeeded(contentResolver); try { if (packageManager.canForwardTo( - intentToCheck, resolvedType, sourceUserId, targetUserId)) { - return forwardIntent; + intent, resolvedType, sourceUserId, targetUserId)) { + return true; } } catch (RemoteException e) { Slog.e(TAG, "PackageManagerService is dead?"); } - return null; + return false; } /** diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 70013911904e..d1911e43f7ee 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -6462,4 +6462,8 @@ <!-- Whether unlocking and waking a device are sequenced --> <bool name="config_orderUnlockAndWake">false</bool> + + <!-- List of protected packages that require biometric authentication for modification + (Disable, force-stop or uninstalling updates). --> + <string-array name="config_biometric_protected_package_names" translatable="false" /> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 2425d367e1db..20c179289a20 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -5135,4 +5135,8 @@ <!-- Whether we order unlocking and waking --> <java-symbol type="bool" name="config_orderUnlockAndWake" /> + + <!-- List of protected packages that require biometric authentication for modification --> + <java-symbol type="array" name="config_biometric_protected_package_names" /> + </resources> diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java index 229b7a73a2a3..e70acc314658 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java @@ -68,7 +68,7 @@ public class InstallStart extends Activity { mUserManager = getSystemService(UserManager.class); Intent intent = getIntent(); - String callingPackage = getCallingPackage(); + String callingPackage = getLaunchedFromPackage(); String callingAttributionTag = null; final boolean isSessionInstall = diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java index 9c67817cbef4..bf3bc8e7dcb3 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java @@ -56,8 +56,6 @@ import com.android.packageinstaller.television.ErrorFragment; import com.android.packageinstaller.television.UninstallAlertFragment; import com.android.packageinstaller.television.UninstallAppProgress; -import java.util.List; - /* * This activity presents UI to uninstall an application. Usually launched with intent * Intent.ACTION_UNINSTALL_PKG_COMMAND and attribute @@ -162,12 +160,15 @@ public class UninstallerActivity extends Activity { if (mDialogInfo.user == null) { mDialogInfo.user = Process.myUserHandle(); } else { - List<UserHandle> profiles = userManager.getUserProfiles(); - if (!profiles.contains(mDialogInfo.user)) { - Log.e(TAG, "User " + Process.myUserHandle() + " can't request uninstall " - + "for user " + mDialogInfo.user); - showUserIsNotAllowed(); - return; + if (!mDialogInfo.user.equals(Process.myUserHandle())) { + final boolean isCurrentUserProfileOwner = Process.myUserHandle().equals( + userManager.getProfileParent(mDialogInfo.user)); + if (!isCurrentUserProfileOwner) { + Log.e(TAG, "User " + Process.myUserHandle() + " can't request uninstall " + + "for user " + mDialogInfo.user); + showUserIsNotAllowed(); + return; + } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java new file mode 100644 index 000000000000..03c1f92aad4c --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java @@ -0,0 +1,545 @@ +/* + * Copyright (C) 2021 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.systemui.qs.tiles; + +import static android.content.pm.PackageManager.FEATURE_NFC_HOST_CARD_EMULATION; +import static android.provider.Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT; + +import static com.google.common.truth.Truth.assertThat; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertFalse; +import static junit.framework.TestCase.assertNotNull; +import static junit.framework.TestCase.assertNull; +import static junit.framework.TestCase.assertTrue; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; +import android.os.Handler; +import android.os.UserHandle; +import android.service.quickaccesswallet.GetWalletCardsError; +import android.service.quickaccesswallet.GetWalletCardsResponse; +import android.service.quickaccesswallet.QuickAccessWalletClient; +import android.service.quickaccesswallet.QuickAccessWalletService; +import android.service.quickaccesswallet.WalletCard; +import android.service.quicksettings.Tile; +import android.testing.TestableLooper; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.internal.logging.MetricsLogger; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.classifier.FalsingManagerFake; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.qs.QSTile; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; +import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.res.R; +import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.settings.SecureSettings; +import com.android.systemui.wallet.controller.QuickAccessWalletController; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Collections; + +@RunWith(AndroidJUnit4.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +public class QuickAccessWalletTileTest extends SysuiTestCase { + + private static final String CARD_ID = "card_id"; + private static final String LABEL = "QAW"; + private static final String CARD_DESCRIPTION = "•••• 1234"; + private static final Icon CARD_IMAGE = + Icon.createWithBitmap(Bitmap.createBitmap(70, 50, Bitmap.Config.ARGB_8888)); + private static final Icon INVALID_CARD_IMAGE = + Icon.createWithContentUri("content://media/external/images/media"); + private static final int PRIMARY_USER_ID = 0; + private static final int SECONDARY_USER_ID = 10; + + private final Drawable mTileIcon = mContext.getDrawable(R.drawable.ic_qs_wallet); + private final Intent mWalletIntent = new Intent(QuickAccessWalletService.ACTION_VIEW_WALLET) + .setComponent(new ComponentName(mContext.getPackageName(), "WalletActivity")); + + @Mock + private QSHost mHost; + @Mock + private MetricsLogger mMetricsLogger; + @Mock + private StatusBarStateController mStatusBarStateController; + @Mock + private ActivityStarter mActivityStarter; + @Mock + private QSLogger mQSLogger; + @Mock + private QsEventLogger mUiEventLogger; + @Mock + private QuickAccessWalletClient mQuickAccessWalletClient; + @Mock + private KeyguardStateController mKeyguardStateController; + @Mock + private PackageManager mPackageManager; + @Mock + private SecureSettings mSecureSettings; + @Mock + private QuickAccessWalletController mController; + @Mock + private Icon mCardImage; + @Captor + ArgumentCaptor<QuickAccessWalletClient.OnWalletCardsRetrievedCallback> mCallbackCaptor; + + private Context mSpiedContext; + private TestableLooper mTestableLooper; + private QuickAccessWalletTile mTile; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mTestableLooper = TestableLooper.get(this); + mSpiedContext = spy(mContext); + + doNothing().when(mSpiedContext).startActivity(any(Intent.class)); + when(mHost.getContext()).thenReturn(mSpiedContext); + when(mQuickAccessWalletClient.getServiceLabel()).thenReturn(LABEL); + when(mQuickAccessWalletClient.getTileIcon()).thenReturn(mTileIcon); + when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(true); + when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(true); + when(mQuickAccessWalletClient.isWalletFeatureAvailableWhenDeviceLocked()).thenReturn(true); + when(mController.getWalletClient()).thenReturn(mQuickAccessWalletClient); + when(mCardImage.getType()).thenReturn(Icon.TYPE_URI); + when(mCardImage.loadDrawableAsUser(any(), eq(SECONDARY_USER_ID))).thenReturn(null); + + mTile = new QuickAccessWalletTile( + mHost, + mUiEventLogger, + mTestableLooper.getLooper(), + new Handler(mTestableLooper.getLooper()), + new FalsingManagerFake(), + mMetricsLogger, + mStatusBarStateController, + mActivityStarter, + mQSLogger, + mKeyguardStateController, + mPackageManager, + mSecureSettings, + mController); + + mTile.initialize(); + mTestableLooper.processAllMessages(); + } + + @After + public void tearDown() { + mTile.destroy(); + mTestableLooper.processAllMessages(); + } + + @Test + public void testNewTile() { + assertFalse(mTile.newTileState().handlesLongClick); + } + + @Test + public void testWalletServiceUnavailable_recreateWalletClient() { + when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(false); + + mTile.handleSetListening(true); + + verify(mController, times(1)).reCreateWalletClient(); + } + + @Test + public void testWalletFeatureUnavailable_recreateWalletClient() { + when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(false); + + mTile.handleSetListening(true); + + verify(mController, times(1)).reCreateWalletClient(); + } + + @Test + public void testIsAvailable_qawFeatureAvailableWalletUnavailable() { + when(mController.isWalletRoleAvailable()).thenReturn(false); + when(mPackageManager.hasSystemFeature(FEATURE_NFC_HOST_CARD_EMULATION)).thenReturn(true); + when(mPackageManager.hasSystemFeature("org.chromium.arc")).thenReturn(false); + when(mSecureSettings.getStringForUser(NFC_PAYMENT_DEFAULT_COMPONENT, + UserHandle.USER_CURRENT)).thenReturn("Component"); + + assertTrue(mTile.isAvailable()); + } + + @Test + public void testIsAvailable_nfcUnavailableWalletAvailable() { + when(mController.isWalletRoleAvailable()).thenReturn(true); + when(mHost.getUserId()).thenReturn(PRIMARY_USER_ID); + when(mPackageManager.hasSystemFeature(FEATURE_NFC_HOST_CARD_EMULATION)).thenReturn(false); + when(mPackageManager.hasSystemFeature("org.chromium.arc")).thenReturn(false); + + assertTrue(mTile.isAvailable()); + } + + @Test + public void testHandleClick_startQuickAccessUiIntent_noCard() { + setUpWalletCard(/* hasCard= */ false); + + mTile.handleClick(/* view= */ null); + mTestableLooper.processAllMessages(); + + verify(mController).startQuickAccessUiIntent( + eq(mActivityStarter), + eq(null), + /* hasCard= */ eq(false)); + } + + @Test + public void testHandleClick_startQuickAccessUiIntent_hasCard() { + setUpWalletCard(/* hasCard= */ true); + + mTile.handleClick(null /* view */); + mTestableLooper.processAllMessages(); + + verify(mController).startQuickAccessUiIntent( + eq(mActivityStarter), + eq(null), + /* hasCard= */ eq(true)); + } + + @Test + public void testHandleUpdateState_updateLabelAndIcon() { + QSTile.State state = new QSTile.State(); + + mTile.handleUpdateState(state, null); + + assertEquals(LABEL, state.label.toString()); + assertTrue(state.label.toString().contentEquals(state.contentDescription)); + assertEquals(mTileIcon, state.icon.getDrawable(mContext)); + } + + @Test + public void testHandleUpdateState_updateLabelAndIcon_noIconFromApi() { + when(mQuickAccessWalletClient.getTileIcon()).thenReturn(null); + QSTile.State state = new QSTile.State(); + QSTile.Icon icon = QSTileImpl.ResourceIcon.get(R.drawable.ic_wallet_lockscreen); + + mTile.handleUpdateState(state, null); + + assertEquals(LABEL, state.label.toString()); + assertTrue(state.label.toString().contentEquals(state.contentDescription)); + assertEquals(icon, state.icon); + } + + @Test + public void testGetTileLabel_serviceLabelExists() { + assertEquals(LABEL, mTile.getTileLabel().toString()); + } + + @Test + public void testGetTileLabel_serviceLabelDoesNotExist() { + when(mQuickAccessWalletClient.getServiceLabel()).thenReturn(null); + assertEquals(mContext.getString(R.string.wallet_title), mTile.getTileLabel().toString()); + } + + @Test + public void testHandleUpdateState_walletIsUpdating() { + when(mKeyguardStateController.isUnlocked()).thenReturn(true); + QSTile.State state = new QSTile.State(); + GetWalletCardsResponse response = + new GetWalletCardsResponse( + Collections.singletonList(createWalletCard(mContext)), 0); + + mTile.handleSetListening(true); + + verify(mController).queryWalletCards(mCallbackCaptor.capture()); + + // Wallet cards fetching on its way; wallet updating. + mTile.handleUpdateState(state, null); + + assertEquals(Tile.STATE_INACTIVE, state.state); + assertEquals( + mContext.getString(R.string.wallet_secondary_label_updating), state.secondaryLabel); + assertNotNull(state.stateDescription); + assertNull(state.sideViewCustomDrawable); + + // Wallet cards fetching completed. + mCallbackCaptor.getValue().onWalletCardsRetrieved(response); + mTestableLooper.processAllMessages(); + + mTile.handleUpdateState(state, null); + + assertEquals(Tile.STATE_ACTIVE, state.state); + assertEquals(CARD_DESCRIPTION, state.secondaryLabel); + assertNotNull(state.stateDescription); + assertNotNull(state.sideViewCustomDrawable); + } + + @Test + public void testHandleUpdateState_hasCard_deviceLocked_tileInactive() { + when(mKeyguardStateController.isUnlocked()).thenReturn(false); + QSTile.State state = new QSTile.State(); + setUpWalletCard(/* hasCard= */ true); + + mTile.handleUpdateState(state, null); + + assertEquals(Tile.STATE_INACTIVE, state.state); + assertEquals(CARD_DESCRIPTION, state.secondaryLabel); + assertNotNull(state.stateDescription); + assertNotNull(state.sideViewCustomDrawable); + } + + @Test + public void testHandleUpdateState_hasCard_deviceUnlocked_tileActive() { + when(mKeyguardStateController.isUnlocked()).thenReturn(true); + QSTile.State state = new QSTile.State(); + setUpWalletCard(/* hasCard= */ true); + + mTile.handleUpdateState(state, null); + + assertEquals(Tile.STATE_ACTIVE, state.state); + assertEquals(CARD_DESCRIPTION, state.secondaryLabel); + assertNotNull(state.stateDescription); + assertNotNull(state.sideViewCustomDrawable); + } + + + @Test + public void testHandleUpdateState_noCard_tileInactive() { + QSTile.State state = new QSTile.State(); + setUpWalletCard(/* hasCard= */ false); + + mTile.handleUpdateState(state, null); + + assertEquals(Tile.STATE_INACTIVE, state.state); + assertEquals( + mContext.getString(R.string.wallet_secondary_label_no_card), + state.secondaryLabel); + assertNotNull(state.stateDescription); + assertNull(state.sideViewCustomDrawable); + } + + @Test + public void testHandleUpdateState_qawServiceUnavailable_tileUnavailable() { + when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(false); + QSTile.State state = new QSTile.State(); + + mTile.handleUpdateState(state, null); + + assertEquals(Tile.STATE_UNAVAILABLE, state.state); + assertNull(state.stateDescription); + assertNull(state.sideViewCustomDrawable); + } + + @Test + public void testHandleUpdateState_qawFeatureUnavailable_tileUnavailable() { + when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(false); + QSTile.State state = new QSTile.State(); + + mTile.handleUpdateState(state, null); + + assertEquals(Tile.STATE_UNAVAILABLE, state.state); + assertNull(state.stateDescription); + assertNull(state.sideViewCustomDrawable); + } + + @Test + public void testHandleSetListening_queryCards() { + mTile.handleSetListening(true); + + verify(mController).queryWalletCards(mCallbackCaptor.capture()); + + assertThat(mCallbackCaptor.getValue()).isInstanceOf( + QuickAccessWalletClient.OnWalletCardsRetrievedCallback.class); + } + + @Test + public void testQueryCards_hasCards_updateSideViewDrawable() { + when(mKeyguardStateController.isUnlocked()).thenReturn(true); + setUpWalletCard(/* hasCard= */ true); + + assertNotNull(mTile.getState().sideViewCustomDrawable); + } + + @Test + public void testQueryCards_notCurrentUser_hasCards_noSideViewDrawable() { + when(mKeyguardStateController.isUnlocked()).thenReturn(true); + + PendingIntent pendingIntent = + PendingIntent.getActivity(mContext, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE); + WalletCard walletCard = + new WalletCard.Builder( + CARD_ID, mCardImage, CARD_DESCRIPTION, pendingIntent).build(); + GetWalletCardsResponse response = + new GetWalletCardsResponse(Collections.singletonList(walletCard), 0); + + mTile.handleSetListening(true); + + verify(mController).queryWalletCards(mCallbackCaptor.capture()); + + mCallbackCaptor.getValue().onWalletCardsRetrieved(response); + mTestableLooper.processAllMessages(); + + assertNull(mTile.getState().sideViewCustomDrawable); + } + + @Test + public void testQueryCards_cardDataPayment_updateSideViewDrawable() { + when(mKeyguardStateController.isUnlocked()).thenReturn(true); + setUpWalletCardWithType(/* hasCard =*/ true, WalletCard.CARD_TYPE_PAYMENT); + + assertNotNull(mTile.getState().sideViewCustomDrawable); + } + + @Test + public void testQueryCards_cardDataNonPayment_updateSideViewDrawable() { + when(mKeyguardStateController.isUnlocked()).thenReturn(true); + setUpWalletCardWithType(/* hasCard =*/ true, WalletCard.CARD_TYPE_NON_PAYMENT); + + assertNull(mTile.getState().sideViewCustomDrawable); + } + + @Test + public void testQueryCards_noCards_notUpdateSideViewDrawable() { + setUpWalletCard(/* hasCard= */ false); + + assertNull(mTile.getState().sideViewCustomDrawable); + } + + @Test + public void testQueryCards_invalidDrawable_noSideViewDrawable() { + when(mKeyguardStateController.isUnlocked()).thenReturn(true); + setUpInvalidWalletCard(/* hasCard= */ true); + + assertNull(mTile.getState().sideViewCustomDrawable); + } + + @Test + public void testQueryCards_error_notUpdateSideViewDrawable() { + String errorMessage = "getWalletCardsError"; + GetWalletCardsError error = new GetWalletCardsError(CARD_IMAGE, errorMessage); + + mTile.handleSetListening(true); + + verify(mController).queryWalletCards(mCallbackCaptor.capture()); + + mCallbackCaptor.getValue().onWalletCardRetrievalError(error); + mTestableLooper.processAllMessages(); + + assertNull(mTile.getState().sideViewCustomDrawable); + } + + @Test + public void testHandleSetListening_notListening_notQueryCards() { + mTile.handleSetListening(false); + + verifyNoMoreInteractions(mQuickAccessWalletClient); + } + + private WalletCard createWalletCardWithType(Context context, int cardType) { + PendingIntent pendingIntent = + PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE); + return new WalletCard.Builder(CARD_ID, cardType, CARD_IMAGE, CARD_DESCRIPTION, + pendingIntent).build(); + } + + private void setUpWalletCardWithType(boolean hasCard, int cardType) { + GetWalletCardsResponse response = + new GetWalletCardsResponse( + hasCard + ? Collections.singletonList( + createWalletCardWithType(mContext, cardType)) + : Collections.EMPTY_LIST, 0); + + mTile.handleSetListening(true); + + verify(mController).queryWalletCards(mCallbackCaptor.capture()); + + mCallbackCaptor.getValue().onWalletCardsRetrieved(response); + mTestableLooper.processAllMessages(); + } + + private void setUpWalletCard(boolean hasCard) { + GetWalletCardsResponse response = + new GetWalletCardsResponse( + hasCard + ? Collections.singletonList(createWalletCard(mContext)) + : Collections.EMPTY_LIST, 0); + + mTile.handleSetListening(true); + + verify(mController).queryWalletCards(mCallbackCaptor.capture()); + + mCallbackCaptor.getValue().onWalletCardsRetrieved(response); + mTestableLooper.processAllMessages(); + } + + private void setUpInvalidWalletCard(boolean hasCard) { + GetWalletCardsResponse response = + new GetWalletCardsResponse( + hasCard + ? Collections.singletonList(createInvalidWalletCard(mContext)) + : Collections.EMPTY_LIST, 0); + + mTile.handleSetListening(true); + + verify(mController).queryWalletCards(mCallbackCaptor.capture()); + + mCallbackCaptor.getValue().onWalletCardsRetrieved(response); + mTestableLooper.processAllMessages(); + } + + private WalletCard createWalletCard(Context context) { + PendingIntent pendingIntent = + PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE); + return new WalletCard.Builder(CARD_ID, CARD_IMAGE, CARD_DESCRIPTION, pendingIntent).build(); + } + + private WalletCard createInvalidWalletCard(Context context) { + PendingIntent pendingIntent = + PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE); + return new WalletCard.Builder( + CARD_ID, INVALID_CARD_IMAGE, CARD_DESCRIPTION, pendingIntent).build(); + } + + +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 7f706859abb3..c95a1b6ba46a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -802,6 +802,11 @@ public class AuthContainerView extends LinearLayout } @Override + public String getClassNameIfItIsConfirmDeviceCredentialActivity() { + return mConfig.mPromptInfo.getClassNameIfItIsConfirmDeviceCredentialActivity(); + } + + @Override public void animateToCredentialUI() { if (mBiometricView != null) { mBiometricView.startTransitionToCredentialUI(); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 57f1928fe545..ec132e4dec50 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -26,6 +26,7 @@ import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.TaskStackListener; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -243,28 +244,21 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, mExecution.assertIsMainThread(); if (mCurrentDialog != null) { try { - final String clientPackage = mCurrentDialog.getOpPackageName(); - Log.w(TAG, "Task stack changed, current client: " + clientPackage); - final List<ActivityManager.RunningTaskInfo> runningTasks = - mActivityTaskManager.getTasks(1); - if (!runningTasks.isEmpty()) { - final String topPackage = runningTasks.get(0).topActivity.getPackageName(); - if (!topPackage.contentEquals(clientPackage) - && !Utils.isSystem(mContext, clientPackage)) { - Log.e(TAG, "Evicting client due to: " + topPackage); - mCurrentDialog.dismissWithoutCallback(true /* animate */); - mCurrentDialog = null; - - for (Callback cb : mCallbacks) { - cb.onBiometricPromptDismissed(); - } + if (isOwnerInBackground()) { + Log.w(TAG, "Evicting client due to top activity is not : " + + mCurrentDialog.getOpPackageName()); + mCurrentDialog.dismissWithoutCallback(true /* animate */); + mCurrentDialog = null; + + for (Callback cb : mCallbacks) { + cb.onBiometricPromptDismissed(); + } - if (mReceiver != null) { - mReceiver.onDialogDismissed( - BiometricPrompt.DISMISSED_REASON_USER_CANCEL, - null /* credentialAttestation */); - mReceiver = null; - } + if (mReceiver != null) { + mReceiver.onDialogDismissed( + BiometricPrompt.DISMISSED_REASON_USER_CANCEL, + null /* credentialAttestation */); + mReceiver = null; } } } catch (RemoteException e) { @@ -273,6 +267,45 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, } } + private boolean isOwnerInBackground() { + if (mCurrentDialog != null) { + final String clientPackage = mCurrentDialog.getOpPackageName(); + + final List<ActivityManager.RunningTaskInfo> runningTasks = + mActivityTaskManager.getTasks(1); + if (runningTasks == null || runningTasks.isEmpty()) { + Log.w(TAG, "No running tasks reported"); + return false; + } + + final boolean isSystemApp = Utils.isSystem(mContext, clientPackage); + + final ComponentName topActivity = runningTasks.get(0).topActivity; + final String topPackage = topActivity.getPackageName(); + final boolean topPackageEqualsToClient = + topPackage == null + || topActivity.getPackageName().contentEquals(clientPackage); + + // b/339532378: If it's ConfirmDeviceCredentialActivity, we need to check further on + // class name. + final String clientClassNameForCDCA = + mCurrentDialog.getClassNameIfItIsConfirmDeviceCredentialActivity(); + final boolean isClientCDCA = clientClassNameForCDCA != null; + final String topClassName = topActivity.getClassName(); + final boolean isCDCAWithWrongTopClass = + isClientCDCA + && !(topClassName == null + || topClassName.contentEquals(clientClassNameForCDCA)); + + final boolean isInBackground = + !(isSystemApp || topPackageEqualsToClient) || isCDCAWithWrongTopClass; + + Log.w(TAG, "isInBackground " + isInBackground); + return isInBackground; + } + return false; + } + /** * Whether all fingerprint authentictors have been registered. */ diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java index b6eabfa76e36..d589608490ad 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java @@ -160,6 +160,12 @@ public interface AuthDialog extends Dumpable { long getRequestId(); /** + * Get the class name of ConfirmDeviceCredentialActivity. Returns null if the direct caller is + * not ConfirmDeviceCredentialActivity. + */ + String getClassNameIfItIsConfirmDeviceCredentialActivity(); + + /** * Animate to credential UI. Typically called after biometric is locked out. */ void animateToCredentialUI(); diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java index f6a2f3704283..eb7698c42329 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java @@ -98,12 +98,11 @@ public class MediaProjectionPermissionActivity extends Activity final Intent launchingIntent = getIntent(); mReviewGrantedConsentRequired = launchingIntent.getBooleanExtra( EXTRA_USER_REVIEW_GRANTED_CONSENT, false); - - mPackageName = getCallingPackage(); + mPackageName = getLaunchedFromPackage(); // This activity is launched directly by an app, or system server. System server provides // the package name through the intent if so. - if (mPackageName == null) { + if (getCallingPackage() == null) { if (launchingIntent.hasExtra(EXTRA_PACKAGE_REUSING_GRANTED_CONSENT)) { mPackageName = launchingIntent.getStringExtra( EXTRA_PACKAGE_REUSING_GRANTED_CONSENT); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java index 544e6ad295ff..6f29866e3ecb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java @@ -16,8 +16,13 @@ package com.android.systemui.qs.tiles; -import static android.graphics.drawable.Icon.TYPE_URI; import static android.provider.Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT; +import static android.graphics.drawable.Icon.TYPE_URI; +import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP; +import static android.graphics.drawable.Icon.TYPE_RESOURCE; +import static android.graphics.drawable.Icon.TYPE_BITMAP; +import static android.graphics.drawable.Icon.TYPE_ADAPTIVE_BITMAP; +import static android.graphics.drawable.Icon.TYPE_DATA; import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE; @@ -226,11 +231,21 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> { return; } mSelectedCard = cards.get(selectedIndex); - android.graphics.drawable.Icon cardImageIcon = mSelectedCard.getCardImage(); - if (cardImageIcon.getType() == TYPE_URI) { - mCardViewDrawable = null; - } else { - mCardViewDrawable = mSelectedCard.getCardImage().loadDrawable(mContext); + switch (mSelectedCard.getCardImage().getType()) { + case TYPE_BITMAP: + case TYPE_ADAPTIVE_BITMAP: + mCardViewDrawable = mSelectedCard.getCardImage().loadDrawable(mContext); + break; + case TYPE_URI: + case TYPE_URI_ADAPTIVE_BITMAP: + case TYPE_RESOURCE: + case TYPE_DATA: + mCardViewDrawable = null; + break; + default: + Log.e(TAG, "Unknown icon type: " + mSelectedCard.getCardImage().getType()); + mCardViewDrawable = null; + break; } refreshState(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index 91c08a062b54..5c0b2a873620 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -601,17 +601,7 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi } } - CharSequence title = n.extras.getCharSequence(Notification.EXTRA_TITLE); - CharSequence text = n.extras.getCharSequence(Notification.EXTRA_TEXT); - CharSequence ticker = n.tickerText; - - // Some apps just put the app name into the title - CharSequence titleOrText = TextUtils.equals(title, appName) ? text : title; - - CharSequence desc = !TextUtils.isEmpty(titleOrText) ? titleOrText - : !TextUtils.isEmpty(ticker) ? ticker : ""; - - return c.getString(R.string.accessibility_desc_notification_icon, appName, desc); + return c.getString(R.string.accessibility_desc_notification_icon, appName, ""); } /** diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java index 81d04d4458c0..5627ce987aab 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java @@ -339,13 +339,19 @@ public class WalletScreenController implements QAWalletCardViewInfo(Context context, WalletCard walletCard) { mWalletCard = walletCard; Icon cardImageIcon = mWalletCard.getCardImage(); - if (cardImageIcon.getType() == Icon.TYPE_URI) { - mCardDrawable = null; - } else { + if (cardImageIcon.getType() == Icon.TYPE_BITMAP + || cardImageIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) { mCardDrawable = mWalletCard.getCardImage().loadDrawable(context); + } else { + mCardDrawable = null; } Icon icon = mWalletCard.getCardIcon(); - mIconDrawable = icon == null ? null : icon.loadDrawable(context); + if (icon != null && (icon.getType() == Icon.TYPE_BITMAP + || icon.getType() == Icon.TYPE_ADAPTIVE_BITMAP)) { + mIconDrawable = icon.loadDrawable(context); + } else { + mIconDrawable = null; + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java index cab47a3c4b4b..2bb493132b9d 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java @@ -282,6 +282,11 @@ public class WalletView extends FrameLayout implements WalletCardCarousel.OnCard return mCardLabel; } + @VisibleForTesting + ImageView getIcon() { + return mIcon; + } + @Nullable private static Drawable getHeaderIcon(Context context, WalletCardViewInfo walletCard) { Drawable icon = walletCard.getIcon(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java index 692af6a9a37b..f7b16e295a48 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java @@ -316,6 +316,31 @@ public class WalletScreenControllerTest extends SysuiTestCase { } @Test + public void queryCards_hasCards_showCarousel_invalidIconSource_noIcon() { + GetWalletCardsResponse response = + new GetWalletCardsResponse( + Collections.singletonList(createWalletCardWithInvalidIcon(mContext)), 0); + + mController.queryWalletCards(); + mTestableLooper.processAllMessages(); + + verify(mWalletClient).getWalletCards(any(), any(), mCallbackCaptor.capture()); + + QuickAccessWalletClient.OnWalletCardsRetrievedCallback callback = + mCallbackCaptor.getValue(); + + assertEquals(mController, callback); + + callback.onWalletCardsRetrieved(response); + mTestableLooper.processAllMessages(); + + assertEquals(VISIBLE, mWalletView.getCardCarousel().getVisibility()); + assertEquals(GONE, mWalletView.getEmptyStateView().getVisibility()); + assertEquals(GONE, mWalletView.getErrorView().getVisibility()); + assertEquals(null, mWalletView.getIcon().getDrawable()); + } + + @Test public void queryCards_noCards_showEmptyState() { GetWalletCardsResponse response = new GetWalletCardsResponse(Collections.EMPTY_LIST, 0); @@ -507,6 +532,16 @@ public class WalletScreenControllerTest extends SysuiTestCase { .build(); } + private WalletCard createWalletCardWithInvalidIcon(Context context) { + PendingIntent pendingIntent = + PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE); + return new WalletCard.Builder( + CARD_ID_1, createIconWithInvalidSource(), "•••• 1234", pendingIntent) + .setCardIcon(createIconWithInvalidSource()) + .setCardLabel("Hold to reader") + .build(); + } + private WalletCard createCrazyWalletCard(Context context, boolean hasLabel) { PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE); @@ -520,6 +555,10 @@ public class WalletScreenControllerTest extends SysuiTestCase { return Icon.createWithBitmap(Bitmap.createBitmap(70, 44, Bitmap.Config.ARGB_8888)); } + private static Icon createIconWithInvalidSource() { + return Icon.createWithContentUri("content://media/external/images/media"); + } + private WalletCardViewInfo createCardViewInfo(WalletCard walletCard) { return new WalletScreenController.QAWalletCardViewInfo( mContext, walletCard); diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 71ed5dbe8ab6..faebe7359eb5 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -3124,6 +3124,12 @@ public class AccountManagerService "the type and name should not be empty"); return; } + if (!type.equals(mAccountType)) { + onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, + "incorrect account type"); + return; + } + Account resultAccount = new Account(name, type); if (!customTokens) { saveAuthTokenToDatabase( diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 46adc79ed056..4f6b855ebd2c 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -13804,8 +13804,7 @@ public class ActivityManagerService extends IActivityManager.Stub return null; } if (callerApp.info.uid != SYSTEM_UID - && !callerApp.getPkgList().containsKey(callerPackage) - && !"android".equals(callerPackage)) { + && !callerApp.getPkgList().containsKey(callerPackage)) { throw new SecurityException("Given caller package " + callerPackage + " is not running in process " + callerApp); } diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index e2388e2918ab..1211ee25bfd8 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -215,6 +215,12 @@ public class AppOpsService extends IAppOpsService.Stub { */ private static final int CURRENT_VERSION = 1; + /** + * The upper limit of total number of attributed op entries that can be returned in a binder + * transaction to avoid TransactionTooLargeException + */ + private static final int NUM_ATTRIBUTED_OP_ENTRY_THRESHOLD = 2000; + // Write at most every 30 minutes. static final long WRITE_DELAY = DEBUG ? 1000 : 30*60*1000; @@ -589,7 +595,7 @@ public class AppOpsService extends IAppOpsService.Stub { } } - /** Returned from {@link #verifyAndGetBypass(int, String, String, String, boolean)}. */ + /** Returned from {@link #verifyAndGetBypass(int, String, String, int, String, boolean)}. */ private static final class PackageVerificationResult { final RestrictionBypass bypass; @@ -1434,6 +1440,8 @@ public class AppOpsService extends IAppOpsService.Stub { Manifest.permission.GET_APP_OPS_STATS, Binder.getCallingPid(), Binder.getCallingUid()) == PackageManager.PERMISSION_GRANTED; + int totalAttributedOpEntryCount = 0; + if (ops == null) { resOps = new ArrayList<>(); for (int j = 0; j < pkgOps.size(); j++) { @@ -1441,7 +1449,12 @@ public class AppOpsService extends IAppOpsService.Stub { if (opRestrictsRead(curOp.op) && !shouldReturnRestrictedAppOps) { continue; } - resOps.add(getOpEntryForResult(curOp)); + if (totalAttributedOpEntryCount > NUM_ATTRIBUTED_OP_ENTRY_THRESHOLD) { + break; + } + OpEntry opEntry = getOpEntryForResult(curOp); + resOps.add(opEntry); + totalAttributedOpEntryCount += opEntry.getAttributedOpEntries().size(); } } else { for (int j = 0; j < ops.length; j++) { @@ -1453,10 +1466,21 @@ public class AppOpsService extends IAppOpsService.Stub { if (resOps == null) { resOps = new ArrayList<>(); } - resOps.add(getOpEntryForResult(curOp)); + if (totalAttributedOpEntryCount > NUM_ATTRIBUTED_OP_ENTRY_THRESHOLD) { + break; + } + OpEntry opEntry = getOpEntryForResult(curOp); + resOps.add(opEntry); + totalAttributedOpEntryCount += opEntry.getAttributedOpEntries().size(); } } } + + if (totalAttributedOpEntryCount > NUM_ATTRIBUTED_OP_ENTRY_THRESHOLD) { + Slog.w(TAG, "The number of attributed op entries has exceeded the threshold. This " + + "could be due to DoS attack from malicious apps. The result is throttled."); + } + return resOps; } @@ -2503,10 +2527,10 @@ public class AppOpsService extends IAppOpsService.Stub { public int checkPackage(int uid, String packageName) { Objects.requireNonNull(packageName); try { - verifyAndGetBypass(uid, packageName, null, null, true); + verifyAndGetBypass(uid, packageName, null, Process.INVALID_UID, null, true); // When the caller is the system, it's possible that the packageName is the special // one (e.g., "root") which isn't actually existed. - if (resolveUid(packageName) == uid + if (resolveNonAppUid(packageName) == uid || (isPackageExisted(packageName) && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(uid)))) { return AppOpsManager.MODE_ALLOWED; @@ -2636,7 +2660,7 @@ public class AppOpsService extends IAppOpsService.Stub { boolean shouldCollectMessage) { PackageVerificationResult pvr; try { - pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName); + pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyUid, proxyPackageName); boolean wasNull = attributionTag == null; if (!pvr.isAttributionTagValid) { attributionTag = null; @@ -3178,7 +3202,7 @@ public class AppOpsService extends IAppOpsService.Stub { int attributionChainId, boolean dryRun) { PackageVerificationResult pvr; try { - pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName); + pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyUid, proxyPackageName); if (!pvr.isAttributionTagValid) { attributionTag = null; } @@ -3671,13 +3695,17 @@ public class AppOpsService extends IAppOpsService.Stub { private boolean isSpecialPackage(int callingUid, @Nullable String packageName) { final String resolvedPackage = AppOpsManager.resolvePackageName(callingUid, packageName); return callingUid == Process.SYSTEM_UID - || resolveUid(resolvedPackage) != Process.INVALID_UID; + || resolveNonAppUid(resolvedPackage) != Process.INVALID_UID; } private boolean isCallerAndAttributionTrusted(@NonNull AttributionSource attributionSource) { if (attributionSource.getUid() != Binder.getCallingUid() && attributionSource.isTrusted(mContext)) { - return true; + // if there is a next attribution source, it must be trusted, as well. + if (attributionSource.getNext() == null + || attributionSource.getNext().isTrusted(mContext)) { + return true; + } } return mContext.checkPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS, Binder.getCallingPid(), Binder.getCallingUid(), null) @@ -3760,19 +3788,20 @@ public class AppOpsService extends IAppOpsService.Stub { } /** - * @see #verifyAndGetBypass(int, String, String, String, boolean) + * @see #verifyAndGetBypass(int, String, String, int, String, boolean) */ private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName, @Nullable String attributionTag) { - return verifyAndGetBypass(uid, packageName, attributionTag, null); + return verifyAndGetBypass(uid, packageName, attributionTag, Process.INVALID_UID, null); } /** - * @see #verifyAndGetBypass(int, String, String, String, boolean) + * @see #verifyAndGetBypass(int, String, String, int, String, boolean) */ private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName, - @Nullable String attributionTag, @Nullable String proxyPackageName) { - return verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName, false); + @Nullable String attributionTag, int proxyUid, @Nullable String proxyPackageName) { + return verifyAndGetBypass(uid, packageName, attributionTag, proxyUid, proxyPackageName, + false); } /** @@ -3783,14 +3812,15 @@ public class AppOpsService extends IAppOpsService.Stub { * @param uid The uid the package belongs to * @param packageName The package the might belong to the uid * @param attributionTag attribution tag or {@code null} if no need to verify - * @param proxyPackageName The proxy package, from which the attribution tag is to be pulled + * @param proxyUid The proxy uid, from which the attribution tag is to be pulled + * @param proxyPackageName The proxy package, from which the attribution tag may be pulled * @param suppressErrorLogs Whether to print to logcat about nonmatching parameters * * @return PackageVerificationResult containing {@link RestrictionBypass} and whether the * attribution tag is valid */ private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName, - @Nullable String attributionTag, @Nullable String proxyPackageName, + @Nullable String attributionTag, int proxyUid, @Nullable String proxyPackageName, boolean suppressErrorLogs) { if (uid == Process.ROOT_UID) { // For backwards compatibility, don't check package name for root UID. @@ -3834,34 +3864,47 @@ public class AppOpsService extends IAppOpsService.Stub { int callingUid = Binder.getCallingUid(); - // Allow any attribution tag for resolvable uids - int pkgUid; + // Allow any attribution tag for resolvable, non-app uids + int nonAppUid; if (Objects.equals(packageName, "com.android.shell")) { // Special case for the shell which is a package but should be able // to bypass app attribution tag restrictions. - pkgUid = Process.SHELL_UID; + nonAppUid = Process.SHELL_UID; } else { - pkgUid = resolveUid(packageName); + nonAppUid = resolveNonAppUid(packageName); } - if (pkgUid != Process.INVALID_UID) { - if (pkgUid != UserHandle.getAppId(uid)) { + if (nonAppUid != Process.INVALID_UID) { + if (nonAppUid != UserHandle.getAppId(uid)) { if (!suppressErrorLogs) { Slog.e(TAG, "Bad call made by uid " + callingUid + ". " - + "Package \"" + packageName + "\" does not belong to uid " + uid - + "."); + + "Package \"" + packageName + "\" does not belong to uid " + uid + + "."); + } + String otherUidMessage = + DEBUG ? " but it is really " + nonAppUid : " but it is not"; + throw new SecurityException("Specified package \"" + packageName + + "\" under uid " + UserHandle.getAppId(uid) + otherUidMessage); + } + // We only allow bypassing the attribution tag verification if the proxy is a + // system app (or is null), in order to prevent abusive apps clogging the appops + // system with unlimited attribution tags via proxy calls. + boolean proxyIsSystemAppOrNull = true; + if (proxyPackageName != null) { + int proxyAppId = UserHandle.getAppId(proxyUid); + if (proxyAppId >= Process.FIRST_APPLICATION_UID) { + proxyIsSystemAppOrNull = + mPackageManagerInternal.isSystemPackage(proxyPackageName); } - String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not"; - throw new SecurityException("Specified package \"" + packageName + "\" under uid " - + UserHandle.getAppId(uid) + otherUidMessage); } return new PackageVerificationResult(RestrictionBypass.UNRESTRICTED, - /* isAttributionTagValid */ true); + /* isAttributionTagValid */ proxyIsSystemAppOrNull); } int userId = UserHandle.getUserId(uid); RestrictionBypass bypass = null; boolean isAttributionTagValid = false; + int pkgUid = nonAppUid; final long ident = Binder.clearCallingIdentity(); try { PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class); @@ -4613,7 +4656,7 @@ public class AppOpsService extends IAppOpsService.Stub { if (nonpackageUid != -1) { packageName = null; } else { - packageUid = resolveUid(packageName); + packageUid = resolveNonAppUid(packageName); if (packageUid < 0) { packageUid = AppGlobals.getPackageManager().getPackageUid(packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, userId); @@ -5628,7 +5671,13 @@ public class AppOpsService extends IAppOpsService.Stub { if (restricted && attrOp.isRunning()) { attrOp.pause(); } else if (attrOp.isPaused()) { - attrOp.resume(); + RestrictionBypass bypass = verifyAndGetBypass(uid, ops.packageName, attrOp.tag) + .bypass; + if (!isOpRestrictedLocked(uid, code, ops.packageName, attrOp.tag, + bypass, false)) { + // Only resume if there are no other restrictions remaining on this op + attrOp.resume(); + } } } } @@ -6077,7 +6126,7 @@ public class AppOpsService extends IAppOpsService.Stub { } } - private static int resolveUid(String packageName) { + private static int resolveNonAppUid(String packageName) { if (packageName == null) { return Process.INVALID_UID; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index b62fd616d299..9752299bca01 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2346,6 +2346,7 @@ public class NotificationManagerService extends SystemService { mPermissionHelper, mNotificationChannelLogger, mAppOps, + mUgmInternal, new SysUiStatsEvent.BuilderFactory(), mShowReviewPermissionsNotification); mRankingHelper = new RankingHelper(getContext(), @@ -5875,13 +5876,7 @@ public class NotificationManagerService extends SystemService { final Uri originalSoundUri = (originalChannel != null) ? originalChannel.getSound() : null; if (soundUri != null && !Objects.equals(originalSoundUri, soundUri)) { - Binder.withCleanCallingIdentity(() -> { - mUgmInternal.checkGrantUriPermission(sourceUid, null, - ContentProvider.getUriWithoutUserId(soundUri), - Intent.FLAG_GRANT_READ_URI_PERMISSION, - ContentProvider.getUserIdFromUri(soundUri, - UserHandle.getUserId(sourceUid))); - }); + PermissionHelper.grantUriPermission(mUgmInternal, soundUri, sourceUid); } } diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index c9a6c630d41b..672130eef969 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -1406,19 +1406,14 @@ public final class NotificationRecord { * {@link SecurityException} depending on target SDK of enqueuing app. */ private void visitGrantableUri(Uri uri, boolean userOverriddenUri, boolean isSound) { - if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return; + if (mGrantableUris != null && mGrantableUris.contains(uri)) { + return; // already verified this URI + } - // We can't grant Uri permissions from system final int sourceUid = getSbn().getUid(); if (sourceUid == android.os.Process.SYSTEM_UID) return; - - final long ident = Binder.clearCallingIdentity(); try { - // This will throw SecurityException if caller can't grant - mUgmInternal.checkGrantUriPermission(sourceUid, null, - ContentProvider.getUriWithoutUserId(uri), - Intent.FLAG_GRANT_READ_URI_PERMISSION, - ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(sourceUid))); + PermissionHelper.grantUriPermission(mUgmInternal, uri, sourceUid); if (mGrantableUris == null) { mGrantableUris = new ArraySet<>(); @@ -1438,8 +1433,6 @@ public final class NotificationRecord { } } } - } finally { - Binder.restoreCallingIdentity(ident); } } diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java index 93c83e181ec1..e62613f92e1c 100644 --- a/services/core/java/com/android/server/notification/PermissionHelper.java +++ b/services/core/java/com/android/server/notification/PermissionHelper.java @@ -25,19 +25,25 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED; import android.Manifest; import android.annotation.NonNull; import android.annotation.UserIdInt; +import android.content.ContentProvider; +import android.content.ContentResolver; import android.content.Context; +import android.content.Intent; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; +import android.net.Uri; import android.os.Binder; import android.os.RemoteException; +import android.os.UserHandle; import android.permission.IPermissionManager; import android.util.ArrayMap; import android.util.Pair; import android.util.Slog; import com.android.internal.util.ArrayUtils; +import com.android.server.uri.UriGrantsManagerInternal; import java.util.Collections; import java.util.HashSet; @@ -58,7 +64,7 @@ public final class PermissionHelper { private final IPermissionManager mPermManager; public PermissionHelper(Context context, IPackageManager packageManager, - IPermissionManager permManager) { + IPermissionManager permManager) { mContext = context; mPackageManager = packageManager; mPermManager = permManager; @@ -296,6 +302,19 @@ public final class PermissionHelper { return false; } + static void grantUriPermission(final UriGrantsManagerInternal ugmInternal, Uri uri, + int sourceUid) { + if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return; + + Binder.withCleanCallingIdentity(() -> { + // This will throw a SecurityException if the caller can't grant. + ugmInternal.checkGrantUriPermission(sourceUid, null, + ContentProvider.getUriWithoutUserId(uri), + Intent.FLAG_GRANT_READ_URI_PERMISSION, + ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(sourceUid))); + }); + } + public static class PackagePermission { public final String packageName; public final @UserIdInt int userId; diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 4399a3ca46c5..1aa3fcf25a59 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -78,6 +78,7 @@ import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import com.android.server.notification.PermissionHelper.PackagePermission; +import com.android.server.uri.UriGrantsManagerInternal; import org.json.JSONArray; import org.json.JSONException; @@ -185,6 +186,7 @@ public class PreferencesHelper implements RankingConfig { private final PermissionHelper mPermissionHelper; private final NotificationChannelLogger mNotificationChannelLogger; private final AppOpsManager mAppOps; + private final UriGrantsManagerInternal mUgmInternal; private SparseBooleanArray mBadgingEnabled; private SparseBooleanArray mBubblesEnabled; @@ -201,6 +203,7 @@ public class PreferencesHelper implements RankingConfig { ZenModeHelper zenHelper, PermissionHelper permHelper, NotificationChannelLogger notificationChannelLogger, AppOpsManager appOpsManager, + UriGrantsManagerInternal ugmInternal, SysUiStatsEvent.BuilderFactory statsEventBuilderFactory, boolean showReviewPermissionsNotification) { mContext = context; @@ -211,6 +214,7 @@ public class PreferencesHelper implements RankingConfig { mNotificationChannelLogger = notificationChannelLogger; mAppOps = appOpsManager; mStatsEventBuilderFactory = statsEventBuilderFactory; + mUgmInternal = ugmInternal; mShowReviewPermissionsNotification = showReviewPermissionsNotification; XML_VERSION = 4; @@ -999,6 +1003,11 @@ public class PreferencesHelper implements RankingConfig { } clearLockedFieldsLocked(channel); + // Verify that the app has permission to read the sound Uri + // Only check for new channels, as regular apps can only set sound + // before creating. See: {@link NotificationChannel#setSound} + PermissionHelper.grantUriPermission(mUgmInternal, channel.getSound(), uid); + channel.setImportanceLockedByCriticalDeviceFunction( r.defaultAppLockedImportance || r.fixedImportance); diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 78f1fa60b69f..f6253e1f5aae 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -67,6 +67,7 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.admin.DevicePolicyManagerInternal; import android.content.ComponentName; +import android.content.ContentProvider; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -634,11 +635,11 @@ public class ComputerEngine implements Computer { String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId, int callingUid, boolean includeInstantApps) { if (!mUserManager.exists(userId)) return Collections.emptyList(); - enforceCrossUserOrProfilePermission(callingUid, + enforceCrossUserOrProfilePermission(Binder.getCallingUid(), userId, false /*requireFullPermission*/, false /*checkShell*/, - "query intent receivers"); + "query intent services"); final String instantAppPkgName = getInstantAppPackageName(callingUid); flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps, false /* isImplicitImageCaptureIntentAndNotSetByDpc */); @@ -2155,10 +2156,10 @@ public class ComputerEngine implements Computer { return true; } if (requireFullPermission) { - return hasPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL); + return hasPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingUid); } - return hasPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) - || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS); + return hasPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingUid) + || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS, callingUid); } /** @@ -2174,6 +2175,11 @@ public class ComputerEngine implements Computer { == PackageManager.PERMISSION_GRANTED; } + private boolean hasPermission(String permission, int uid) { + return mContext.checkPermission(permission, Process.INVALID_PID, uid) + == PackageManager.PERMISSION_GRANTED; + } + public final boolean isCallerSameApp(String packageName, int uid) { return isCallerSameApp(packageName, uid, false /* resolveIsolatedUid */); } @@ -4590,7 +4596,7 @@ public class ComputerEngine implements Computer { final boolean listApex = (flags & MATCH_APEX) != 0; enforceCrossUserPermission( - callingUid, + Binder.getCallingUid(), userId, false /* requireFullPermission */, false /* checkShell */, @@ -4667,8 +4673,14 @@ public class ComputerEngine implements Computer { int callingUid) { if (!mUserManager.exists(userId)) return null; flags = updateFlagsForComponent(flags, userId); - final ProviderInfo providerInfo = mComponentResolver.queryProvider(this, name, flags, - userId); + + // Callers of this API may not always separate the userID and authority. Let's parse it + // before resolving + String authorityWithoutUserId = ContentProvider.getAuthorityWithoutUserId(name); + userId = ContentProvider.getUserIdFromAuthority(name, userId); + + final ProviderInfo providerInfo = mComponentResolver.queryProvider(this, + authorityWithoutUserId, flags, userId); boolean checkedGrants = false; if (providerInfo != null) { // Looking for cross-user grants before enforcing the typical cross-users permissions @@ -4682,7 +4694,7 @@ public class ComputerEngine implements Computer { if (!checkedGrants) { boolean enforceCrossUser = true; - if (isAuthorityRedirectedForCloneProfile(name)) { + if (isAuthorityRedirectedForCloneProfile(authorityWithoutUserId)) { final UserManagerInternal umInternal = mInjector.getUserManagerInternal(); UserInfo userInfo = umInternal.getUserInfo(UserHandle.getUserId(callingUid)); @@ -5136,7 +5148,7 @@ public class ComputerEngine implements Computer { @Override public int getComponentEnabledSetting(@NonNull ComponentName component, int callingUid, @UserIdInt int userId) { - enforceCrossUserPermission(callingUid, userId, false /*requireFullPermission*/, + enforceCrossUserPermission(Binder.getCallingUid(), userId, false /*requireFullPermission*/, false /*checkShell*/, "getComponentEnabled"); return getComponentEnabledSettingInternal(component, callingUid, userId); } diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index 8815834f6b5c..0b3f6fd87013 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -282,7 +282,8 @@ public class UserRestrictionsUtils { * in settings. So it is handled separately. */ private static final Set<String> DEFAULT_ENABLED_FOR_MANAGED_PROFILES = Sets.newArraySet( - UserManager.DISALLOW_BLUETOOTH_SHARING + UserManager.DISALLOW_BLUETOOTH_SHARING, + UserManager.DISALLOW_DEBUGGING_FEATURES ); /** diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index a88e2b06a4c3..50d15816b2ce 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -3003,7 +3003,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } break; case KeyEvent.KEYCODE_I: - if (down && event.isMetaPressed()) { + if (down && event.isMetaPressed() && isUserSetupComplete() && !keyguardOn) { showSystemSettings(); return key_consumed; } diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java index 0c98fb5000d5..941d612676da 100644 --- a/services/core/java/com/android/server/wm/LockTaskController.java +++ b/services/core/java/com/android/server/wm/LockTaskController.java @@ -652,6 +652,10 @@ public class LockTaskController { if (!isSystemCaller) { task.mLockTaskUid = callingUid; if (task.mLockTaskAuth == LOCK_TASK_AUTH_PINNABLE) { + if (mLockTaskModeTasks.contains(task)) { + Slog.i(TAG_LOCKTASK, "Already locked."); + return; + } // startLockTask() called by app, but app is not part of lock task allowlist. Show // app pinning request. We will come back here with isSystemCaller true. ProtoLog.w(WM_DEBUG_LOCKTASK, "Mode default, asking user"); diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 2ac3125de961..6037fa55d78f 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -1240,16 +1240,15 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } // Update the input-sink (touch-blocking) state now that the animation is finished. - SurfaceControl.Transaction inputSinkTransaction = null; + boolean scheduleAnimation = false; for (int i = 0; i < mParticipants.size(); ++i) { final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord(); if (ar == null || !ar.isVisible() || ar.getParent() == null) continue; - if (inputSinkTransaction == null) { - inputSinkTransaction = ar.mWmService.mTransactionFactory.get(); - } - ar.mActivityRecordInputSink.applyChangesToSurfaceIfChanged(inputSinkTransaction); + scheduleAnimation = true; + ar.mActivityRecordInputSink.applyChangesToSurfaceIfChanged(ar.getPendingTransaction()); } - if (inputSinkTransaction != null) inputSinkTransaction.apply(); + // To apply pending transactions. + if (scheduleAnimation) mController.mAtm.mWindowManager.scheduleAnimationLocked(); // Always schedule stop processing when transition finishes because activities don't // stop while they are in a transition thus their stop could still be pending. diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 1549677fdbb2..59ca6151639b 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -2663,13 +2663,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * Apply default restrictions that haven't been applied to a given admin yet. */ private void maybeSetDefaultRestrictionsForAdminLocked(int userId, ActiveAdmin admin) { - Set<String> defaultRestrictions = - UserRestrictionsUtils.getDefaultEnabledForManagedProfiles(); - if (defaultRestrictions.equals(admin.defaultEnabledRestrictionsAlreadySet)) { + final Set<String> restrictionsToSet = + new ArraySet<>(UserRestrictionsUtils.getDefaultEnabledForManagedProfiles()); + restrictionsToSet.removeAll(admin.defaultEnabledRestrictionsAlreadySet); + if (restrictionsToSet.isEmpty()) { return; // The same set of default restrictions has been already applied. } if (isPolicyEngineForFinanceFlagEnabled()) { - for (String restriction : defaultRestrictions) { + for (String restriction : restrictionsToSet) { mDevicePolicyEngine.setLocalPolicy( PolicyDefinition.getPolicyDefinitionForUserRestriction(restriction), EnforcingAdmin.createEnterpriseEnforcingAdmin( @@ -2678,9 +2679,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { new BooleanPolicyValue(true), userId); } - admin.defaultEnabledRestrictionsAlreadySet.addAll(defaultRestrictions); + admin.defaultEnabledRestrictionsAlreadySet.addAll(restrictionsToSet); Slogf.i(LOG_TAG, "Enabled the following restrictions by default: " + - defaultRestrictions); + restrictionsToSet); return; } @@ -2688,21 +2689,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (VERBOSE_LOG) { Slogf.d(LOG_TAG, "Default enabled restrictions: " - + defaultRestrictions + + restrictionsToSet + ". Restrictions already enabled: " + admin.defaultEnabledRestrictionsAlreadySet); } - - final Set<String> restrictionsToSet = new ArraySet<>(defaultRestrictions); - restrictionsToSet.removeAll(admin.defaultEnabledRestrictionsAlreadySet); - if (!restrictionsToSet.isEmpty()) { - for (final String restriction : restrictionsToSet) { - admin.ensureUserRestrictions().putBoolean(restriction, true); - } - admin.defaultEnabledRestrictionsAlreadySet.addAll(restrictionsToSet); - Slogf.i(LOG_TAG, "Enabled the following restrictions by default: " + restrictionsToSet); - saveUserRestrictionsLocked(userId); + for (final String restriction : restrictionsToSet) { + admin.ensureUserRestrictions().putBoolean(restriction, true); } + admin.defaultEnabledRestrictionsAlreadySet.addAll(restrictionsToSet); + Slogf.i(LOG_TAG, "Enabled the following restrictions by default: " + restrictionsToSet); + saveUserRestrictionsLocked(userId); } private void setDeviceOwnershipSystemPropertyLocked() { @@ -10192,7 +10188,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return false; } - if (isAdb(caller)) { + boolean isAdb = isAdb(caller); + if (isAdb) { // Log profile owner provisioning was started using adb. MetricsLogger.action(mContext, PROVISIONING_ENTRY_POINT_ADB, LOG_TAG_PROFILE_OWNER); DevicePolicyEventLogger @@ -10214,7 +10211,19 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { maybeSetDefaultRestrictionsForAdminLocked(userHandle, admin); ensureUnknownSourcesRestrictionForProfileOwnerLocked(userHandle, admin, true /* newOwner */); + if (isAdb) { + // DISALLOW_DEBUGGING_FEATURES is being added to newly-created + // work profile by default due to b/382064697 . This would have + // impacted certain CTS test flows when they interact with the + // work profile via ADB (for example installing an app into the + // work profile). Remove DISALLOW_DEBUGGING_FEATURES here to + // reduce the potential impact. + setLocalUserRestrictionInternal( + EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userHandle), + UserManager.DISALLOW_DEBUGGING_FEATURES, false, userHandle); + } } + sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED, userHandle); }); @@ -11131,7 +11140,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (mOwners.hasDeviceOwner()) { return false; } - + final ComponentName profileOwner = getProfileOwnerAsUser(userId); if (profileOwner == null) { return false; @@ -11140,7 +11149,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (isManagedProfile(userId)) { return false; } - + return true; } private void enforceCanQueryLockTaskLocked(ComponentName who, String callerPackageName) { @@ -24460,7 +24469,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } }); } - + private void migrateUserControlDisabledPackagesLocked() { Binder.withCleanCallingIdentity(() -> { List<UserInfo> users = mUserManager.getUsers(); diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt index f3ac7d55c5db..7c6f0943dd02 100644 --- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt +++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt @@ -55,6 +55,7 @@ import org.junit.BeforeClass import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized +import org.mockito.ArgumentMatchers.eq import org.mockito.Mockito.any import org.mockito.Mockito.anyInt import org.mockito.Mockito.doReturn @@ -385,6 +386,10 @@ class PackageManagerComponentLabelIconOverrideTest { android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)) { PackageManager.PERMISSION_GRANTED } + whenever(this.checkPermission( + eq(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL), anyInt(), anyInt())) { + PackageManager.PERMISSION_GRANTED + } } val mockSharedLibrariesImpl: SharedLibrariesImpl = mock { whenever(this.snapshot()) { this@mock } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index f203cfa9e442..63688d1242c6 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -3646,7 +3646,42 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { doThrow(new SecurityException("no access")).when(mUgmInternal) .checkGrantUriPermission(eq(Process.myUid()), any(), eq(soundUri), - anyInt(), eq(Process.myUserHandle().getIdentifier())); + anyInt(), eq(Process.myUserHandle().getIdentifier())); + + mBinderService.updateNotificationChannelFromPrivilegedListener( + null, mPkg, Process.myUserHandle(), updatedNotificationChannel); + + verify(mPreferencesHelper, times(1)).updateNotificationChannel( + anyString(), anyInt(), any(), anyBoolean(), anyInt(), anyBoolean()); + + verify(mListeners, never()).notifyNotificationChannelChanged(eq(mPkg), + eq(Process.myUserHandle()), eq(mTestNotificationChannel), + eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED)); + } + + @Test + public void + testUpdateNotificationChannelFromPrivilegedListener_oldSoundNoUriPerm_newSoundHasUriPerm() + throws Exception { + mService.setPreferencesHelper(mPreferencesHelper); + when(mCompanionMgr.getAssociations(mPkg, mUserId)) + .thenReturn(singletonList(mock(AssociationInfo.class))); + when(mPreferencesHelper.getNotificationChannel(eq(mPkg), anyInt(), + eq(mTestNotificationChannel.getId()), anyBoolean())) + .thenReturn(mTestNotificationChannel); + + // Missing Uri permissions for the old channel sound + final Uri oldSoundUri = Settings.System.DEFAULT_NOTIFICATION_URI; + doThrow(new SecurityException("no access")).when(mUgmInternal) + .checkGrantUriPermission(eq(Process.myUid()), any(), eq(oldSoundUri), + anyInt(), eq(Process.myUserHandle().getIdentifier())); + + // Has Uri permissions for the old channel sound + final Uri newSoundUri = Uri.parse("content://media/test/sound/uri"); + final NotificationChannel updatedNotificationChannel = new NotificationChannel( + TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT); + updatedNotificationChannel.setSound(newSoundUri, + updatedNotificationChannel.getAudioAttributes()); mBinderService.updateNotificationChannelFromPrivilegedListener( null, PKG, Process.myUserHandle(), updatedNotificationChannel); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java index fae92d9ac738..bcdbd227d248 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java @@ -36,6 +36,7 @@ import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -76,6 +77,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.R; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.server.LocalServices; import com.android.server.UiServiceTestCase; import com.android.server.uri.UriGrantsManagerInternal; @@ -855,22 +857,19 @@ public class NotificationRecordTest extends UiServiceTestCase { when(ugm.checkGrantUriPermission(anyInt(), eq(null), any(Uri.class), anyInt(), anyInt())).thenThrow(new SecurityException()); + LocalServices.removeServiceForTest(UriGrantsManagerInternal.class); + LocalServices.addService(UriGrantsManagerInternal.class, ugm); + channel.setSound(null, null); Notification n = new Notification.Builder(mContext, channel.getId()) .setSmallIcon(Icon.createWithContentUri(Uri.parse("content://something"))) .build(); StatusBarNotification sbn = new StatusBarNotification(PKG_P, PKG_P, id1, tag1, uid, uid, n, mUser, null, uid); - NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel); - record.mAm = am; - record.mUgmInternal = ugm; - try { - record.calculateGrantableUris(); - fail("App provided uri for p targeting app should throw exception"); - } catch (SecurityException e) { - // expected - } + assertThrows("App provided uri for p targeting app should throw exception", + SecurityException.class, + () -> new NotificationRecord(mMockContext, sbn, channel)); } @Test @@ -880,17 +879,17 @@ public class NotificationRecordTest extends UiServiceTestCase { when(ugm.checkGrantUriPermission(anyInt(), eq(null), any(Uri.class), anyInt(), anyInt())).thenThrow(new SecurityException()); + LocalServices.removeServiceForTest(UriGrantsManagerInternal.class); + LocalServices.addService(UriGrantsManagerInternal.class, ugm); + channel.setSound(Uri.parse("content://something"), mock(AudioAttributes.class)); Notification n = mock(Notification.class); when(n.getChannelId()).thenReturn(channel.getId()); StatusBarNotification sbn = new StatusBarNotification(PKG_P, PKG_P, id1, tag1, uid, uid, n, mUser, null, uid); - NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel); - record.mAm = am; - record.mUgmInternal = ugm; - record.calculateGrantableUris(); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel); assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, record.getSound()); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 48ad86da1bc5..53bdce6718d1 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -29,6 +29,10 @@ import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MAX; import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; +import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE; +import static android.content.ContentResolver.SCHEME_ANDROID_RESOURCE; +import static android.content.ContentResolver.SCHEME_CONTENT; +import static android.content.ContentResolver.SCHEME_FILE; import static android.media.AudioAttributes.CONTENT_TYPE_SONIFICATION; import static android.media.AudioAttributes.USAGE_NOTIFICATION; import static android.os.UserHandle.USER_SYSTEM; @@ -67,6 +71,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; @@ -98,6 +103,7 @@ import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Parcel; +import android.os.Process; import android.os.RemoteCallback; import android.os.RemoteException; import android.os.UserHandle; @@ -303,7 +309,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mStatsEventBuilderFactory = new WrappedSysUiStatsEvent.WrappedBuilderFactory(); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); + mPermissionHelper, mLogger, mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, false); resetZenModeHelper(); mAudioAttributes = new AudioAttributes.Builder() @@ -645,7 +651,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testReadXml_oldXml_migrates() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, true); + mPermissionHelper, mLogger, mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, true); String xml = "<ranking version=\"2\">\n" + "<package name=\"" + PKG_N_MR1 + "\" uid=\"" + UID_N_MR1 @@ -711,7 +717,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testReadXml_oldXml_backup_migratesWhenPkgInstalled() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); + mPermissionHelper, mLogger, mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, false); when(mPm.getPackageUidAsUser("pkg1", USER_SYSTEM)).thenReturn(UNKNOWN_UID); when(mPm.getPackageUidAsUser("pkg2", USER_SYSTEM)).thenReturn(UNKNOWN_UID); @@ -789,7 +795,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testReadXml_newXml_noMigration_showPermissionNotification() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, true); + mPermissionHelper, mLogger, mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, true); String xml = "<ranking version=\"3\">\n" + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n" @@ -846,7 +852,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testReadXml_newXml_permissionNotificationOff() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); + mPermissionHelper, mLogger, mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, false); String xml = "<ranking version=\"3\">\n" + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n" @@ -903,7 +909,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testReadXml_newXml_noMigration_noPermissionNotification() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, true); + mPermissionHelper, mLogger, mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, true); String xml = "<ranking version=\"4\">\n" + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n" @@ -959,7 +965,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testReadXml_oldXml_migration_NoUid() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); + mPermissionHelper, mLogger, mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, false); when(mPm.getPackageUidAsUser("something", USER_SYSTEM)).thenReturn(UNKNOWN_UID); String xml = "<ranking version=\"2\">\n" @@ -992,7 +998,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testReadXml_newXml_noMigration_NoUid() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); + mPermissionHelper, mLogger, mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, false); when(mPm.getPackageUidAsUser("something", USER_SYSTEM)).thenReturn(UNKNOWN_UID); String xml = "<ranking version=\"3\">\n" @@ -1024,7 +1030,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testChannelXmlForNonBackup_postMigration() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); + mPermissionHelper, mLogger, mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, false); ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); appPermissions.put(new Pair<>(1, "first"), new Pair<>(true, false)); @@ -1110,7 +1116,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testChannelXmlForBackup_postMigration() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); + mPermissionHelper, mLogger, mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, false); ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); appPermissions.put(new Pair<>(1, "first"), new Pair<>(true, false)); @@ -1202,7 +1208,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testChannelXmlForBackup_postMigration_noExternal() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); + mPermissionHelper, mLogger, mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, false); ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); appPermissions.put(new Pair<>(UID_P, PKG_P), new Pair<>(true, false)); @@ -1287,7 +1293,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testChannelXmlForBackup_postMigration_noLocalSettings() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); + mPermissionHelper, mLogger, mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, false); ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); appPermissions.put(new Pair<>(1, "first"), new Pair<>(true, false)); @@ -1432,7 +1438,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertTrue(actualChannel.isSoundRestored()); } - /** * Although we don't make backups with uncanonicalized uris anymore, we used to, so we have to * handle its restore properly. @@ -1498,7 +1503,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { new FileNotFoundException("")).thenReturn(resId); mHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); + mPermissionHelper, mLogger, mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, false); NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_LOW); @@ -2444,8 +2449,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); - + mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, false); // create notification channel that can bypass dnd, but app is blocked // expected result: areChannelsBypassingDnd = false @@ -2475,7 +2479,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); + mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, false); // create notification channel that can bypass dnd, but app is blocked // expected result: areChannelsBypassingDnd = false @@ -2499,7 +2503,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); + mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, false); // create notification channel that can bypass dnd, but app is blocked // expected result: areChannelsBypassingDnd = false @@ -2553,7 +2557,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); + mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, false); assertFalse(mHelper.areChannelsBypassingDnd()); verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean()); resetZenModeHelper(); @@ -2566,7 +2570,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); + mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, false); assertFalse(mHelper.areChannelsBypassingDnd()); verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean()); resetZenModeHelper(); @@ -2663,6 +2667,61 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test + public void testCreateChannel_noSoundUriPermission_contentSchemeVerified() { + final Uri sound = Uri.parse(SCHEME_CONTENT + "://media/test/sound/uri"); + + doThrow(new SecurityException("no access")).when(mUgmInternal) + .checkGrantUriPermission(eq(UID_N_MR1), any(), eq(sound), + anyInt(), eq(Process.myUserHandle().getIdentifier())); + + final NotificationChannel channel = new NotificationChannel("id2", "name2", + NotificationManager.IMPORTANCE_DEFAULT); + channel.setSound(sound, mAudioAttributes); + + assertThrows(SecurityException.class, + () -> mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, + true, false, UID_N_MR1, false)); + assertThat(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId(), true)) + .isNull(); + } + + @Test + public void testCreateChannel_noSoundUriPermission_fileSchemaIgnored() { + final Uri sound = Uri.parse(SCHEME_FILE + "://path/sound"); + + doThrow(new SecurityException("no access")).when(mUgmInternal) + .checkGrantUriPermission(eq(UID_N_MR1), any(), any(), + anyInt(), eq(Process.myUserHandle().getIdentifier())); + + final NotificationChannel channel = new NotificationChannel("id2", "name2", + NotificationManager.IMPORTANCE_DEFAULT); + channel.setSound(sound, mAudioAttributes); + + mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false, UID_N_MR1, + false); + assertThat(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId(), true) + .getSound()).isEqualTo(sound); + } + + @Test + public void testCreateChannel_noSoundUriPermission_resourceSchemaIgnored() { + final Uri sound = Uri.parse(SCHEME_ANDROID_RESOURCE + "://resId/sound"); + + doThrow(new SecurityException("no access")).when(mUgmInternal) + .checkGrantUriPermission(eq(UID_N_MR1), any(), any(), + anyInt(), eq(Process.myUserHandle().getIdentifier())); + + final NotificationChannel channel = new NotificationChannel("id2", "name2", + NotificationManager.IMPORTANCE_DEFAULT); + channel.setSound(sound, mAudioAttributes); + + mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false, UID_N_MR1, + false); + assertThat(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId(), true) + .getSound()).isEqualTo(sound); + } + + @Test public void testPermanentlyDeleteChannels() throws Exception { NotificationChannel channel1 = new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH); @@ -3669,7 +3728,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { + "</ranking>\n"; mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); + mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, false); loadByteArrayXml(preQXml.getBytes(), true, USER_SYSTEM); assertEquals(PreferencesHelper.DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS, @@ -3683,7 +3742,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); + mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, false); loadStreamXml(baos, false, UserHandle.USER_ALL); assertEquals(!PreferencesHelper.DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS, @@ -3753,7 +3812,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); + mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, false); loadStreamXml(baos, false, UserHandle.USER_ALL); assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O)); @@ -3766,7 +3825,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); + mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, false); loadStreamXml(baos, false, UserHandle.USER_ALL); assertEquals("other", mHelper.getNotificationDelegate(PKG_O, UID_O)); @@ -3780,7 +3839,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); + mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, false); loadStreamXml(baos, false, UserHandle.USER_ALL); assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O)); @@ -3796,7 +3855,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); + mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, false); loadStreamXml(baos, false, UserHandle.USER_ALL); assertEquals(BUBBLE_PREFERENCE_NONE, mHelper.getBubblePreference(PKG_O, UID_O)); @@ -3852,7 +3911,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); + mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, false); loadStreamXml(baos, false, UserHandle.USER_ALL); assertEquals(BUBBLE_PREFERENCE_SELECTED, mHelper.getBubblePreference(PKG_O, UID_O)); @@ -3890,7 +3949,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); + mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, false); loadStreamXml(baos, false, UserHandle.USER_ALL); assertEquals(mHelper.getBubblePreference(PKG_O, UID_O), BUBBLE_PREFERENCE_NONE); @@ -4548,7 +4607,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testPlaceholderConversationId_shortcutRequired() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); + mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, false); final String xml = "<ranking version=\"1\">\n" + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n" @@ -4568,7 +4627,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testNormalConversationId_shortcutRequired() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); + mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, false); final String xml = "<ranking version=\"1\">\n" + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n" @@ -4588,7 +4647,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testNoConversationId_shortcutRequired() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); + mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, false); final String xml = "<ranking version=\"1\">\n" + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n" @@ -4608,7 +4667,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testDeleted_noTime() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); + mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, false); final String xml = "<ranking version=\"1\">\n" + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n" @@ -4628,7 +4687,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testDeleted_twice() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); + mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, false); mHelper.createNotificationChannel( PKG_P, UID_P, new NotificationChannel("id", "id", 2), true, false, @@ -4643,7 +4702,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testDeleted_recentTime() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); + mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, false); mHelper.createNotificationChannel( PKG_P, UID_P, new NotificationChannel("id", "id", 2), true, false, @@ -4662,7 +4721,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { parser.nextTag(); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); + mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, false); mHelper.readXml(parser, true, USER_SYSTEM); NotificationChannel nc = mHelper.getNotificationChannel(PKG_P, UID_P, "id", true); @@ -4674,7 +4733,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testUnDelete_time() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); + mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, false); mHelper.createNotificationChannel( PKG_P, UID_P, new NotificationChannel("id", "id", 2), true, false, @@ -4696,7 +4755,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testDeleted_longTime() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); + mAppOpsManager, mUgmInternal, mStatsEventBuilderFactory, false); long time = System.currentTimeMillis() - (DateUtils.DAY_IN_MILLIS * 30); diff --git a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java index 1d14dc31fa26..587a2363fca2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java @@ -239,6 +239,11 @@ public class LockTaskControllerTest { verifyLockTaskStarted(STATUS_BAR_MASK_PINNED, DISABLE2_NONE); // THEN screen pinning toast should be shown verify(mStatusBarService).showPinningEnterExitToast(eq(true /* entering */)); + + // WHEN the app calls startLockTaskMode while the Task is already locked + mLockTaskController.startLockTaskMode(tr, false, TEST_UID); + // THEN a pinning request should NOT be shown + verify(mStatusBarManagerInternal, never()).showScreenPinningRequest(anyInt()); } @Test diff --git a/telecomm/java/android/telecom/StatusHints.java b/telecomm/java/android/telecom/StatusHints.java index 5f0c8d729e74..31b84ff04b85 100644 --- a/telecomm/java/android/telecom/StatusHints.java +++ b/telecomm/java/android/telecom/StatusHints.java @@ -27,6 +27,7 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; +import android.util.Log; import com.android.internal.annotations.VisibleForTesting; @@ -40,6 +41,7 @@ public final class StatusHints implements Parcelable { private final CharSequence mLabel; private Icon mIcon; private final Bundle mExtras; + private static final String TAG = StatusHints.class.getSimpleName(); /** * @hide @@ -150,17 +152,37 @@ public final class StatusHints implements Parcelable { // incompatible types. if (icon != null && (icon.getType() == Icon.TYPE_URI || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) { - String encodedUser = icon.getUri().getEncodedUserInfo(); - // If there is no encoded user, the URI is calling into the calling user space - if (encodedUser != null) { - int userId = Integer.parseInt(encodedUser); - // Do not try to save the icon if the user id isn't in the calling user space. - if (userId != callingUserHandle.getIdentifier()) return null; + int callingUserId = callingUserHandle.getIdentifier(); + int requestingUserId = getUserIdFromAuthority( + icon.getUri().getAuthority(), callingUserId); + if (callingUserId != requestingUserId) { + return null; } + } return icon; } + /** + * Derives the user id from the authority or the default user id if none could be found. + * @param auth + * @param defaultUserId + * @return The user id from the given authority. + * @hide + */ + public static int getUserIdFromAuthority(String auth, int defaultUserId) { + if (auth == null) return defaultUserId; + int end = auth.lastIndexOf('@'); + if (end == -1) return defaultUserId; + String userIdString = auth.substring(0, end); + try { + return Integer.parseInt(userIdString); + } catch (NumberFormatException e) { + Log.w(TAG, "Error parsing userId." + e); + return UserHandle.USER_NULL; + } + } + @Override public void writeToParcel(Parcel out, int flags) { out.writeCharSequence(mLabel); |