summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatt Lee <matthewhlee@google.com>2023-08-11 14:59:02 -0700
committerMatt Lee <matthewhlee@google.com>2023-08-11 15:51:05 -0700
commitda4e6b0612a340b211f987fecb5db178aff14400 (patch)
tree02017cefc380f7f7fcb46a0a9bafc624fa8faf05
parent9ac2cdc622c906dfc9447c0ab63b730658f8a15a (diff)
parent502a47c26e38f898478c569ccd422f02f8e68a48 (diff)
Merge t-mpr-2023-08
Change-Id: Id53ca628b754f072352fa9323ceec5c249b0267f
-rw-r--r--core/java/android/app/ActivityOptions.java6
-rw-r--r--core/java/android/app/Notification.java42
-rw-r--r--core/java/android/app/NotificationManager.java6
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java3
-rw-r--r--core/java/android/content/pm/ShortcutInfo.java20
-rw-r--r--core/java/android/permission/IPermissionManager.aidl3
-rw-r--r--core/java/android/permission/PermissionManager.java3
-rw-r--r--core/java/android/widget/RemoteViews.java26
-rw-r--r--core/tests/coretests/src/android/widget/RemoteViewsTest.java141
-rw-r--r--libs/androidfw/CursorWindow.cpp10
-rw-r--r--libs/androidfw/include/androidfw/CursorWindow.h3
-rw-r--r--libs/androidfw/tests/CursorWindow_test.cpp31
-rw-r--r--media/java/android/media/session/MediaSession.java8
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java8
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserFactory.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java113
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt72
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java28
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt283
-rw-r--r--services/core/java/com/android/server/accounts/AccountManagerService.java5
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java12
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java86
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java5
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java22
-rw-r--r--services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java230
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerService.java6
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java88
-rw-r--r--services/core/java/com/android/server/vr/VrManagerService.java6
-rwxr-xr-xservices/core/java/com/android/server/wm/ActivityStarter.java3
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java12
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java92
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java9
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java171
-rw-r--r--telecomm/java/android/telecom/ParcelableConference.java12
-rw-r--r--telecomm/java/android/telecom/StatusHints.java53
46 files changed, 1446 insertions, 295 deletions
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index d6f44e60eb0c..2540f1dea732 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -21,6 +21,8 @@ import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIO
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
import static android.view.Display.INVALID_DISPLAY;
import android.annotation.IntDef;
@@ -1727,7 +1729,9 @@ public class ActivityOptions extends ComponentOptions {
* @hide
*/
public int getPendingIntentLaunchFlags() {
- return mPendingIntentLaunchFlags;
+ // b/243794108: Ignore all flags except the new task flag, to be reconsidered in b/254490217
+ return mPendingIntentLaunchFlags &
+ (FLAG_ACTIVITY_NEW_TASK | FLAG_RECEIVER_FOREGROUND);
}
/**
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 7f48e39a9a79..5584003dcad3 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -2138,6 +2138,10 @@ public class Notification implements Parcelable
}
}
+ private void visitUris(@NonNull Consumer<Uri> visitor) {
+ visitIconUri(visitor, getIcon());
+ }
+
@Override
public Action clone() {
return new Action(
@@ -2807,6 +2811,10 @@ public class Notification implements Parcelable
* @hide
*/
public void visitUris(@NonNull Consumer<Uri> visitor) {
+ if (publicVersion != null) {
+ publicVersion.visitUris(visitor);
+ }
+
visitor.accept(sound);
if (tickerView != null) tickerView.visitUris(visitor);
@@ -2819,7 +2827,7 @@ public class Notification implements Parcelable
if (actions != null) {
for (Action action : actions) {
- visitIconUri(visitor, action.getIcon());
+ action.visitUris(visitor);
}
}
@@ -2853,6 +2861,17 @@ public class Notification implements Parcelable
if (person != null) {
visitor.accept(person.getIconUri());
}
+
+ final RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[])
+ extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+ if (history != null) {
+ for (int i = 0; i < history.length; i++) {
+ RemoteInputHistoryItem item = history[i];
+ if (item.getUri() != null) {
+ visitor.accept(item.getUri());
+ }
+ }
+ }
}
if (isStyle(MessagingStyle.class) && extras != null) {
@@ -2881,11 +2900,26 @@ public class Notification implements Parcelable
}
}
}
+
+ visitIconUri(visitor, extras.getParcelable(EXTRA_CONVERSATION_ICON));
+ }
+
+ if (isStyle(CallStyle.class) & extras != null) {
+ Person callPerson = extras.getParcelable(EXTRA_CALL_PERSON);
+ if (callPerson != null) {
+ visitor.accept(callPerson.getIconUri());
+ }
+ visitIconUri(visitor, extras.getParcelable(EXTRA_VERIFICATION_ICON));
}
if (mBubbleMetadata != null) {
visitIconUri(visitor, mBubbleMetadata.getIcon());
}
+
+ if (extras != null && extras.containsKey(WearableExtender.EXTRA_WEARABLE_EXTENSIONS)) {
+ WearableExtender extender = new WearableExtender(this);
+ extender.visitUris(visitor);
+ }
}
/**
@@ -11601,6 +11635,12 @@ public class Notification implements Parcelable
mFlags &= ~mask;
}
}
+
+ private void visitUris(@NonNull Consumer<Uri> visitor) {
+ for (Action action : mActions) {
+ action.visitUris(visitor);
+ }
+ }
}
/**
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 37a90de8d600..d6d3c7164c66 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -571,6 +571,12 @@ public class NotificationManager {
*/
public static final int BUBBLE_PREFERENCE_SELECTED = 2;
+ /**
+ * Maximum length of the component name of a registered NotificationListenerService.
+ * @hide
+ */
+ public static int MAX_SERVICE_COMPONENT_NAME_LENGTH = 500;
+
@UnsupportedAppUsage
private static INotificationManager sService;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 3ef89b8ca0ad..d98edaa2b7e5 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -12402,7 +12402,8 @@ public class DevicePolicyManager {
/**
* Called by a device admin to set the long support message. This will be displayed to the user
- * in the device administators settings screen.
+ * in the device administrators settings screen. If the message is longer than 20000 characters
+ * it may be truncated.
* <p>
* If the long support message needs to be localized, it is the responsibility of the
* {@link DeviceAdminReceiver} to listen to the {@link Intent#ACTION_LOCALE_CHANGED} broadcast
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 52774e354c90..c33390398400 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -283,6 +283,12 @@ public final class ShortcutInfo implements Parcelable {
*/
public static final int DISABLED_REASON_OTHER_RESTORE_ISSUE = 103;
+ /**
+ * The maximum length of Shortcut ID. IDs will be truncated at this limit.
+ * @hide
+ */
+ public static final int MAX_ID_LENGTH = 1000;
+
/** @hide */
@IntDef(prefix = { "DISABLED_REASON_" }, value = {
DISABLED_REASON_NOT_DISABLED,
@@ -475,8 +481,7 @@ public final class ShortcutInfo implements Parcelable {
private ShortcutInfo(Builder b) {
mUserId = b.mContext.getUserId();
-
- mId = Preconditions.checkStringNotEmpty(b.mId, "Shortcut ID must be provided");
+ mId = getSafeId(Preconditions.checkStringNotEmpty(b.mId, "Shortcut ID must be provided"));
// Note we can't do other null checks here because SM.updateShortcuts() takes partial
// information.
@@ -582,6 +587,14 @@ public final class ShortcutInfo implements Parcelable {
return ret;
}
+ @NonNull
+ private static String getSafeId(@NonNull String id) {
+ if (id.length() > MAX_ID_LENGTH) {
+ return id.substring(0, MAX_ID_LENGTH);
+ }
+ return id;
+ }
+
/**
* Throws if any of the mandatory fields is not set.
*
@@ -2336,7 +2349,8 @@ public final class ShortcutInfo implements Parcelable {
final ClassLoader cl = getClass().getClassLoader();
mUserId = source.readInt();
- mId = source.readString8();
+ mId = getSafeId(Preconditions.checkStringNotEmpty(source.readString8(),
+ "Shortcut ID must be provided"));
mPackageName = source.readString8();
mActivity = source.readParcelable(cl, android.content.ComponentName.class);
mFlags = source.readInt();
diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl
index 6a93b354f4da..45dad9861406 100644
--- a/core/java/android/permission/IPermissionManager.aidl
+++ b/core/java/android/permission/IPermissionManager.aidl
@@ -77,8 +77,7 @@ interface IPermissionManager {
List<SplitPermissionInfoParcelable> getSplitPermissions();
void startOneTimePermissionSession(String packageName, int userId, long timeout,
- long revokeAfterKilledDelay, int importanceToResetTimer,
- int importanceToKeepSessionAlive);
+ long revokeAfterKilledDelay);
void stopOneTimePermissionSession(String packageName, int userId);
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 6b540d72bba0..67699543131a 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -1371,8 +1371,7 @@ public final class PermissionManager {
@ActivityManager.RunningAppProcessInfo.Importance int importanceToKeepSessionAlive) {
try {
mPermissionManager.startOneTimePermissionSession(packageName, mContext.getUserId(),
- timeoutMillis, revokeAfterKilledDelayMillis, importanceToResetTimer,
- importanceToKeepSessionAlive);
+ timeoutMillis, revokeAfterKilledDelayMillis);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index a33906267736..74e863224625 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -726,6 +726,17 @@ public class RemoteViews implements Parcelable, Filter {
mActions.get(i).visitUris(visitor);
}
}
+ if (mSizedRemoteViews != null) {
+ for (int i = 0; i < mSizedRemoteViews.size(); i++) {
+ mSizedRemoteViews.get(i).visitUris(visitor);
+ }
+ }
+ if (mLandscape != null) {
+ mLandscape.visitUris(visitor);
+ }
+ if (mPortrait != null) {
+ mPortrait.visitUris(visitor);
+ }
}
private static void visitIconUri(Icon icon, @NonNull Consumer<Uri> visitor) {
@@ -1824,7 +1835,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public final void visitUris(@NonNull Consumer<Uri> visitor) {
+ public void visitUris(@NonNull Consumer<Uri> visitor) {
switch (this.type) {
case URI:
final Uri uri = (Uri) getParameterValue(null);
@@ -2287,6 +2298,14 @@ public class RemoteViews implements Parcelable, Filter {
public int getActionTag() {
return NIGHT_MODE_REFLECTION_ACTION_TAG;
}
+
+ @Override
+ public void visitUris(@NonNull Consumer<Uri> visitor) {
+ if (this.type == ICON) {
+ visitIconUri((Icon) mDarkValue, visitor);
+ visitIconUri((Icon) mLightValue, visitor);
+ }
+ }
}
/**
@@ -2577,6 +2596,11 @@ public class RemoteViews implements Parcelable, Filter {
public int getActionTag() {
return VIEW_GROUP_ACTION_ADD_TAG;
}
+
+ @Override
+ public final void visitUris(@NonNull Consumer<Uri> visitor) {
+ mNestedViews.visitUris(visitor);
+ }
}
/**
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index 00b3693c902b..a8f2b1d22aed 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -24,6 +24,10 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import android.app.ActivityOptions;
import android.app.PendingIntent;
@@ -33,6 +37,8 @@ import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.Looper;
@@ -57,7 +63,9 @@ import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
+import java.util.HashMap;
import java.util.concurrent.CountDownLatch;
+import java.util.function.Consumer;
/**
* Tests for RemoteViews.
@@ -690,4 +698,137 @@ public class RemoteViewsTest {
return null;
}
}
+
+ @Test
+ public void visitUris() {
+ RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
+
+ final Uri imageUri = Uri.parse("content://media/image");
+ final Icon icon1 = Icon.createWithContentUri("content://media/icon1");
+ final Icon icon2 = Icon.createWithContentUri("content://media/icon2");
+ final Icon icon3 = Icon.createWithContentUri("content://media/icon3");
+ final Icon icon4 = Icon.createWithContentUri("content://media/icon4");
+ views.setImageViewUri(R.id.image, imageUri);
+ views.setTextViewCompoundDrawables(R.id.text, icon1, icon2, icon3, icon4);
+
+ Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
+ views.visitUris(visitor);
+ verify(visitor, times(1)).accept(eq(imageUri));
+ verify(visitor, times(1)).accept(eq(icon1.getUri()));
+ verify(visitor, times(1)).accept(eq(icon2.getUri()));
+ verify(visitor, times(1)).accept(eq(icon3.getUri()));
+ verify(visitor, times(1)).accept(eq(icon4.getUri()));
+ }
+
+ @Test
+ public void visitUris_themedIcons() {
+ RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
+ final Icon iconLight = Icon.createWithContentUri("content://light/icon");
+ final Icon iconDark = Icon.createWithContentUri("content://dark/icon");
+ views.setIcon(R.id.layout, "setLargeIcon", iconLight, iconDark);
+
+ Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
+ views.visitUris(visitor);
+ verify(visitor, times(1)).accept(eq(iconLight.getUri()));
+ verify(visitor, times(1)).accept(eq(iconDark.getUri()));
+ }
+
+ @Test
+ public void visitUris_nestedViews() {
+ final RemoteViews outer = new RemoteViews(mPackage, R.layout.remote_views_test);
+
+ final RemoteViews inner = new RemoteViews(mPackage, 33);
+ final Uri imageUriI = Uri.parse("content://inner/image");
+ final Icon icon1 = Icon.createWithContentUri("content://inner/icon1");
+ final Icon icon2 = Icon.createWithContentUri("content://inner/icon2");
+ final Icon icon3 = Icon.createWithContentUri("content://inner/icon3");
+ final Icon icon4 = Icon.createWithContentUri("content://inner/icon4");
+ inner.setImageViewUri(R.id.image, imageUriI);
+ inner.setTextViewCompoundDrawables(R.id.text, icon1, icon2, icon3, icon4);
+
+ outer.addView(R.id.layout, inner);
+
+ Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
+ outer.visitUris(visitor);
+ verify(visitor, times(1)).accept(eq(imageUriI));
+ verify(visitor, times(1)).accept(eq(icon1.getUri()));
+ verify(visitor, times(1)).accept(eq(icon2.getUri()));
+ verify(visitor, times(1)).accept(eq(icon3.getUri()));
+ verify(visitor, times(1)).accept(eq(icon4.getUri()));
+ }
+
+ @Test
+ public void visitUris_separateOrientation() {
+ final RemoteViews landscape = new RemoteViews(mPackage, R.layout.remote_views_test);
+ final Uri imageUriL = Uri.parse("content://landscape/image");
+ final Icon icon1L = Icon.createWithContentUri("content://landscape/icon1");
+ final Icon icon2L = Icon.createWithContentUri("content://landscape/icon2");
+ final Icon icon3L = Icon.createWithContentUri("content://landscape/icon3");
+ final Icon icon4L = Icon.createWithContentUri("content://landscape/icon4");
+ landscape.setImageViewUri(R.id.image, imageUriL);
+ landscape.setTextViewCompoundDrawables(R.id.text, icon1L, icon2L, icon3L, icon4L);
+
+ final RemoteViews portrait = new RemoteViews(mPackage, 33);
+ final Uri imageUriP = Uri.parse("content://portrait/image");
+ final Icon icon1P = Icon.createWithContentUri("content://portrait/icon1");
+ final Icon icon2P = Icon.createWithContentUri("content://portrait/icon2");
+ final Icon icon3P = Icon.createWithContentUri("content://portrait/icon3");
+ final Icon icon4P = Icon.createWithContentUri("content://portrait/icon4");
+ portrait.setImageViewUri(R.id.image, imageUriP);
+ portrait.setTextViewCompoundDrawables(R.id.text, icon1P, icon2P, icon3P, icon4P);
+
+ RemoteViews views = new RemoteViews(landscape, portrait);
+
+ Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
+ views.visitUris(visitor);
+ verify(visitor, times(1)).accept(eq(imageUriL));
+ verify(visitor, times(1)).accept(eq(icon1L.getUri()));
+ verify(visitor, times(1)).accept(eq(icon2L.getUri()));
+ verify(visitor, times(1)).accept(eq(icon3L.getUri()));
+ verify(visitor, times(1)).accept(eq(icon4L.getUri()));
+ verify(visitor, times(1)).accept(eq(imageUriP));
+ verify(visitor, times(1)).accept(eq(icon1P.getUri()));
+ verify(visitor, times(1)).accept(eq(icon2P.getUri()));
+ verify(visitor, times(1)).accept(eq(icon3P.getUri()));
+ verify(visitor, times(1)).accept(eq(icon4P.getUri()));
+ }
+
+ @Test
+ public void visitUris_sizedViews() {
+ final RemoteViews large = new RemoteViews(mPackage, R.layout.remote_views_test);
+ final Uri imageUriL = Uri.parse("content://large/image");
+ final Icon icon1L = Icon.createWithContentUri("content://large/icon1");
+ final Icon icon2L = Icon.createWithContentUri("content://large/icon2");
+ final Icon icon3L = Icon.createWithContentUri("content://large/icon3");
+ final Icon icon4L = Icon.createWithContentUri("content://large/icon4");
+ large.setImageViewUri(R.id.image, imageUriL);
+ large.setTextViewCompoundDrawables(R.id.text, icon1L, icon2L, icon3L, icon4L);
+
+ final RemoteViews small = new RemoteViews(mPackage, 33);
+ final Uri imageUriS = Uri.parse("content://small/image");
+ final Icon icon1S = Icon.createWithContentUri("content://small/icon1");
+ final Icon icon2S = Icon.createWithContentUri("content://small/icon2");
+ final Icon icon3S = Icon.createWithContentUri("content://small/icon3");
+ final Icon icon4S = Icon.createWithContentUri("content://small/icon4");
+ small.setImageViewUri(R.id.image, imageUriS);
+ small.setTextViewCompoundDrawables(R.id.text, icon1S, icon2S, icon3S, icon4S);
+
+ HashMap<SizeF, RemoteViews> sizedViews = new HashMap<>();
+ sizedViews.put(new SizeF(300, 300), large);
+ sizedViews.put(new SizeF(100, 100), small);
+ RemoteViews views = new RemoteViews(sizedViews);
+
+ Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
+ views.visitUris(visitor);
+ verify(visitor, times(1)).accept(eq(imageUriL));
+ verify(visitor, times(1)).accept(eq(icon1L.getUri()));
+ verify(visitor, times(1)).accept(eq(icon2L.getUri()));
+ verify(visitor, times(1)).accept(eq(icon3L.getUri()));
+ verify(visitor, times(1)).accept(eq(icon4L.getUri()));
+ verify(visitor, times(1)).accept(eq(imageUriS));
+ verify(visitor, times(1)).accept(eq(icon1S.getUri()));
+ verify(visitor, times(1)).accept(eq(icon2S.getUri()));
+ verify(visitor, times(1)).accept(eq(icon3S.getUri()));
+ verify(visitor, times(1)).accept(eq(icon4S.getUri()));
+ }
}
diff --git a/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp
index 3527eeead1d5..2a6dc7b95c07 100644
--- a/libs/androidfw/CursorWindow.cpp
+++ b/libs/androidfw/CursorWindow.cpp
@@ -108,7 +108,7 @@ status_t CursorWindow::maybeInflate() {
{
// Migrate existing contents into new ashmem region
- uint32_t slotsSize = mSize - mSlotsOffset;
+ uint32_t slotsSize = sizeOfSlots();
uint32_t newSlotsOffset = mInflatedSize - slotsSize;
memcpy(static_cast<uint8_t*>(newData),
static_cast<uint8_t*>(mData), mAllocOffset);
@@ -216,11 +216,9 @@ status_t CursorWindow::writeToParcel(Parcel* parcel) {
if (parcel->writeDupFileDescriptor(mAshmemFd)) goto fail;
} else {
// Since we know we're going to be read-only on the remote side,
- // we can compact ourselves on the wire, with just enough padding
- // to ensure our slots stay aligned
- size_t slotsSize = mSize - mSlotsOffset;
- size_t compactedSize = mAllocOffset + slotsSize;
- compactedSize = (compactedSize + 3) & ~3;
+ // we can compact ourselves on the wire.
+ size_t slotsSize = sizeOfSlots();
+ size_t compactedSize = sizeInUse();
if (parcel->writeUint32(compactedSize)) goto fail;
if (parcel->writeBool(false)) goto fail;
void* dest = parcel->writeInplace(compactedSize);
diff --git a/libs/androidfw/include/androidfw/CursorWindow.h b/libs/androidfw/include/androidfw/CursorWindow.h
index 6e55a9a0eb8b..9ec026a19c4c 100644
--- a/libs/androidfw/include/androidfw/CursorWindow.h
+++ b/libs/androidfw/include/androidfw/CursorWindow.h
@@ -90,6 +90,9 @@ public:
inline uint32_t getNumRows() { return mNumRows; }
inline uint32_t getNumColumns() { return mNumColumns; }
+ inline size_t sizeOfSlots() const { return mSize - mSlotsOffset; }
+ inline size_t sizeInUse() const { return mAllocOffset + sizeOfSlots(); }
+
status_t clear();
status_t setNumColumns(uint32_t numColumns);
diff --git a/libs/androidfw/tests/CursorWindow_test.cpp b/libs/androidfw/tests/CursorWindow_test.cpp
index 15be80c48192..9ac427b66cb3 100644
--- a/libs/androidfw/tests/CursorWindow_test.cpp
+++ b/libs/androidfw/tests/CursorWindow_test.cpp
@@ -20,9 +20,16 @@
#include "TestHelpers.h"
+// Verify that the memory in use is a multiple of 4 bytes
+#define ASSERT_ALIGNED(w) \
+ ASSERT_EQ(((w)->sizeInUse() & 3), 0); \
+ ASSERT_EQ(((w)->freeSpace() & 3), 0); \
+ ASSERT_EQ(((w)->sizeOfSlots() & 3), 0)
+
#define CREATE_WINDOW_1K \
CursorWindow* w; \
- CursorWindow::create(String8("test"), 1 << 10, &w);
+ CursorWindow::create(String8("test"), 1 << 10, &w); \
+ ASSERT_ALIGNED(w);
#define CREATE_WINDOW_1K_3X3 \
CursorWindow* w; \
@@ -30,11 +37,13 @@
ASSERT_EQ(w->setNumColumns(3), OK); \
ASSERT_EQ(w->allocRow(), OK); \
ASSERT_EQ(w->allocRow(), OK); \
- ASSERT_EQ(w->allocRow(), OK);
+ ASSERT_EQ(w->allocRow(), OK); \
+ ASSERT_ALIGNED(w);
#define CREATE_WINDOW_2M \
CursorWindow* w; \
- CursorWindow::create(String8("test"), 1 << 21, &w);
+ CursorWindow::create(String8("test"), 1 << 21, &w); \
+ ASSERT_ALIGNED(w);
static constexpr const size_t kHalfInlineSize = 8192;
static constexpr const size_t kGiantSize = 1048576;
@@ -48,6 +57,7 @@ TEST(CursorWindowTest, Empty) {
ASSERT_EQ(w->getNumColumns(), 0);
ASSERT_EQ(w->size(), 1 << 10);
ASSERT_EQ(w->freeSpace(), 1 << 10);
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, SetNumColumns) {
@@ -59,6 +69,7 @@ TEST(CursorWindowTest, SetNumColumns) {
ASSERT_NE(w->setNumColumns(5), OK);
ASSERT_NE(w->setNumColumns(3), OK);
ASSERT_EQ(w->getNumColumns(), 4);
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, SetNumColumnsAfterRow) {
@@ -69,6 +80,7 @@ TEST(CursorWindowTest, SetNumColumnsAfterRow) {
ASSERT_EQ(w->allocRow(), OK);
ASSERT_NE(w->setNumColumns(4), OK);
ASSERT_EQ(w->getNumColumns(), 0);
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, AllocRow) {
@@ -82,14 +94,17 @@ TEST(CursorWindowTest, AllocRow) {
ASSERT_EQ(w->allocRow(), OK);
ASSERT_LT(w->freeSpace(), before);
ASSERT_EQ(w->getNumRows(), 1);
+ ASSERT_ALIGNED(w);
// Verify we can unwind
ASSERT_EQ(w->freeLastRow(), OK);
ASSERT_EQ(w->freeSpace(), before);
ASSERT_EQ(w->getNumRows(), 0);
+ ASSERT_ALIGNED(w);
// Can't unwind when no rows left
ASSERT_NE(w->freeLastRow(), OK);
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, AllocRowBounds) {
@@ -99,6 +114,7 @@ TEST(CursorWindowTest, AllocRowBounds) {
ASSERT_EQ(w->setNumColumns(60), OK);
ASSERT_EQ(w->allocRow(), OK);
ASSERT_NE(w->allocRow(), OK);
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, StoreNull) {
@@ -115,6 +131,7 @@ TEST(CursorWindowTest, StoreNull) {
auto field = w->getFieldSlot(0, 0);
ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_NULL);
}
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, StoreLong) {
@@ -133,6 +150,7 @@ TEST(CursorWindowTest, StoreLong) {
ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_INTEGER);
ASSERT_EQ(w->getFieldSlotValueLong(field), 0xcafe);
}
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, StoreString) {
@@ -154,6 +172,7 @@ TEST(CursorWindowTest, StoreString) {
auto actual = w->getFieldSlotValueString(field, &size);
ASSERT_EQ(std::string(actual), "cafe");
}
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, StoreBounds) {
@@ -174,6 +193,7 @@ TEST(CursorWindowTest, StoreBounds) {
ASSERT_EQ(w->getFieldSlot(-1, 0), nullptr);
ASSERT_EQ(w->getFieldSlot(0, -1), nullptr);
ASSERT_EQ(w->getFieldSlot(-1, -1), nullptr);
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, Inflate) {
@@ -233,6 +253,7 @@ TEST(CursorWindowTest, Inflate) {
ASSERT_NE(actual, buf);
ASSERT_EQ(memcmp(buf, actual, kHalfInlineSize), 0);
}
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, ParcelEmpty) {
@@ -248,10 +269,12 @@ TEST(CursorWindowTest, ParcelEmpty) {
ASSERT_EQ(w->getNumColumns(), 0);
ASSERT_EQ(w->size(), 0);
ASSERT_EQ(w->freeSpace(), 0);
+ ASSERT_ALIGNED(w);
// We can't mutate the window after parceling
ASSERT_NE(w->setNumColumns(4), OK);
ASSERT_NE(w->allocRow(), OK);
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, ParcelSmall) {
@@ -310,6 +333,7 @@ TEST(CursorWindowTest, ParcelSmall) {
ASSERT_EQ(actualSize, 0);
ASSERT_NE(actual, nullptr);
}
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, ParcelLarge) {
@@ -362,6 +386,7 @@ TEST(CursorWindowTest, ParcelLarge) {
ASSERT_EQ(actualSize, 0);
ASSERT_NE(actual, nullptr);
}
+ ASSERT_ALIGNED(w);
}
} // android
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 84ecc06d172f..09eff9e4e13a 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -297,9 +297,11 @@ public final class MediaSession {
* class that should receive media buttons. This allows restarting playback after the session
* has been stopped. If your app is started in this way an {@link Intent#ACTION_MEDIA_BUTTON}
* intent will be sent to the broadcast receiver.
- * <p>
- * Note: The given {@link android.content.BroadcastReceiver} should belong to the same package
- * as the context that was given when creating {@link MediaSession}.
+ *
+ * <p>Note: The given {@link android.content.BroadcastReceiver} should belong to the same
+ * package as the context that was given when creating {@link MediaSession}.
+ *
+ * <p>Calls with invalid or non-existent receivers will be ignored.
*
* @param broadcastReceiver the component name of the BroadcastReceiver class
*/
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index b5e4fa38d244..af06d7304160 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -243,7 +243,9 @@ public class RestrictedSwitchPreference extends SwitchPreference {
return mHelper != null ? mHelper.packageName : null;
}
- public void updateState(@NonNull String packageName, int uid, boolean isEnabled) {
+ /** Updates enabled state based on associated package. */
+ public void updateState(
+ @NonNull String packageName, int uid, boolean isEnableAllowed, boolean isEnabled) {
mHelper.updatePackageDetails(packageName, uid);
if (mAppOpsManager == null) {
mAppOpsManager = getContext().getSystemService(AppOpsManager.class);
@@ -254,7 +256,9 @@ public class RestrictedSwitchPreference extends SwitchPreference {
final boolean ecmEnabled = getContext().getResources().getBoolean(
com.android.internal.R.bool.config_enhancedConfirmationModeEnabled);
final boolean appOpsAllowed = !ecmEnabled || mode == AppOpsManager.MODE_ALLOWED;
- if (isEnabled) {
+ if (!isEnableAllowed && !isEnabled) {
+ setEnabled(false);
+ } else if (isEnabled) {
setEnabled(true);
} else if (appOpsAllowed && isDisabledByAppOps()) {
setEnabled(true);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 061bab8a7006..16299c7aff7b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -752,7 +752,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
case SimPuk:
// Shortcut for SIM PIN/PUK to go to directly to user's security screen or home
SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
- if (securityMode == SecurityMode.None && mLockPatternUtils.isLockScreenDisabled(
+ if (securityMode == SecurityMode.None || mLockPatternUtils.isLockScreenDisabled(
KeyguardUpdateMonitor.getCurrentUser())) {
finish = true;
eventSubtype = BOUNCER_DISMISS_SIM;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 15dceadcb7d2..a7fba6580e0d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -733,6 +733,13 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
}
}
+
+ @Override
+ public void onStrongAuthStateChanged(int userId) {
+ if (mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) {
+ doKeyguardLocked(null);
+ }
+ }
};
ViewMediatorCallback mViewMediatorCallback = new ViewMediatorCallback() {
@@ -1556,9 +1563,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
final ContentResolver cr = mContext.getContentResolver();
// From SecuritySettings
- final long lockAfterTimeout = Settings.Secure.getInt(cr,
+ final long lockAfterTimeout = Settings.Secure.getIntForUser(cr,
Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
- KEYGUARD_LOCK_AFTER_DELAY_DEFAULT);
+ KEYGUARD_LOCK_AFTER_DELAY_DEFAULT, userId);
// From DevicePolicyAdmin
final long policyTimeout = mLockPatternUtils.getDevicePolicyManager()
@@ -1570,8 +1577,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
timeout = lockAfterTimeout;
} else {
// From DisplaySettings
- long displayTimeout = Settings.System.getInt(cr, SCREEN_OFF_TIMEOUT,
- KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT);
+ long displayTimeout = Settings.System.getIntForUser(cr, SCREEN_OFF_TIMEOUT,
+ KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT, userId);
// policy in effect. Make sure we don't go beyond policy limit.
displayTimeout = Math.max(displayTimeout, 0); // ignore negative values
@@ -1941,7 +1948,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
*/
private void doKeyguardLocked(Bundle options) {
// if another app is disabling us, don't show
- if (!mExternallyEnabled) {
+ if (!mExternallyEnabled
+ && !mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) {
if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled");
mNeedToReshowWhenReenabled = true;
@@ -2403,7 +2411,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
private void playSound(int soundId) {
if (soundId == 0) return;
final ContentResolver cr = mContext.getContentResolver();
- if (Settings.System.getInt(cr, Settings.System.LOCKSCREEN_SOUNDS_ENABLED, 1) == 1) {
+ int lockscreenSoundsEnabled = Settings.System.getIntForUser(cr,
+ Settings.System.LOCKSCREEN_SOUNDS_ENABLED, 1,
+ KeyguardUpdateMonitor.getCurrentUser());
+ if (lockscreenSoundsEnabled == 1) {
mLockSounds.stop(mLockSoundStreamId);
// Init mAudioManager
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
index b0389b50cd7d..23ee00d88fdc 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
@@ -122,9 +122,9 @@ constructor(
Log.e(TAG, "Error getting package information", e)
}
- Log.d(TAG, "Adding resume controls $desc")
+ Log.d(TAG, "Adding resume controls for ${browser.userId}: $desc")
mediaDataManager.addResumptionControls(
- currentUserId,
+ browser.userId,
desc,
resumeAction,
token,
@@ -196,7 +196,11 @@ constructor(
}
resumeComponents.add(component to lastPlayed)
}
- Log.d(TAG, "loaded resume components ${resumeComponents.toArray().contentToString()}")
+ Log.d(
+ TAG,
+ "loaded resume components for $currentUserId: " +
+ "${resumeComponents.toArray().contentToString()}"
+ )
if (needsUpdate) {
// Save any missing times that we had to fill in
@@ -210,11 +214,21 @@ constructor(
return
}
+ val pm = context.packageManager
val now = systemClock.currentTimeMillis()
resumeComponents.forEach {
if (now.minus(it.second) <= RESUME_MEDIA_TIMEOUT) {
- val browser = mediaBrowserFactory.create(mediaBrowserCallback, it.first)
- browser.findRecentMedia()
+ // Verify that the service exists for this user
+ val intent = Intent(MediaBrowserService.SERVICE_INTERFACE)
+ intent.component = it.first
+ val inf = pm.resolveServiceAsUser(intent, 0, currentUserId)
+ if (inf != null) {
+ val browser =
+ mediaBrowserFactory.create(mediaBrowserCallback, it.first, currentUserId)
+ browser.findRecentMedia()
+ } else {
+ Log.d(TAG, "User $currentUserId does not have component ${it.first}")
+ }
}
}
}
@@ -244,7 +258,7 @@ constructor(
Log.d(TAG, "Checking for service component for " + data.packageName)
val pm = context.packageManager
val serviceIntent = Intent(MediaBrowserService.SERVICE_INTERFACE)
- val resumeInfo = pm.queryIntentServices(serviceIntent, 0)
+ val resumeInfo = pm.queryIntentServicesAsUser(serviceIntent, 0, currentUserId)
val inf = resumeInfo?.filter { it.serviceInfo.packageName == data.packageName }
if (inf != null && inf.size > 0) {
@@ -280,13 +294,17 @@ constructor(
browser: ResumeMediaBrowser
) {
// Since this is a test, just save the component for later
- Log.d(TAG, "Can get resumable media from $componentName")
+ Log.d(
+ TAG,
+ "Can get resumable media for ${browser.userId} from $componentName"
+ )
mediaDataManager.setResumeAction(key, getResumeAction(componentName))
updateResumptionList(componentName)
mediaBrowser = null
}
},
- componentName
+ componentName,
+ currentUserId
)
mediaBrowser?.testConnection()
}
@@ -326,7 +344,7 @@ constructor(
/** Get a runnable which will resume media playback */
private fun getResumeAction(componentName: ComponentName): Runnable {
return Runnable {
- mediaBrowser = mediaBrowserFactory.create(null, componentName)
+ mediaBrowser = mediaBrowserFactory.create(null, componentName, currentUserId)
mediaBrowser?.restart()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java
index d460b5b5d782..ceaccafd8f40 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java
@@ -17,6 +17,7 @@
package com.android.systemui.media.controls.resume;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
@@ -53,6 +54,7 @@ public class ResumeMediaBrowser {
private final ResumeMediaBrowserLogger mLogger;
private final ComponentName mComponentName;
private final MediaController.Callback mMediaControllerCallback = new SessionDestroyCallback();
+ @UserIdInt private final int mUserId;
private MediaBrowser mMediaBrowser;
@Nullable private MediaController mMediaController;
@@ -62,18 +64,21 @@ public class ResumeMediaBrowser {
* @param context the context
* @param callback used to report media items found
* @param componentName Component name of the MediaBrowserService this browser will connect to
+ * @param userId ID of the current user
*/
public ResumeMediaBrowser(
Context context,
@Nullable Callback callback,
ComponentName componentName,
MediaBrowserFactory browserFactory,
- ResumeMediaBrowserLogger logger) {
+ ResumeMediaBrowserLogger logger,
+ @UserIdInt int userId) {
mContext = context;
mCallback = callback;
mComponentName = componentName;
mBrowserFactory = browserFactory;
mLogger = logger;
+ mUserId = userId;
}
/**
@@ -285,6 +290,14 @@ public class ResumeMediaBrowser {
}
/**
+ * Get the ID of the user associated with this broswer
+ * @return the user ID
+ */
+ public @UserIdInt int getUserId() {
+ return mUserId;
+ }
+
+ /**
* Get the media session token
* @return the token, or null if the MediaBrowser is null or disconnected
*/
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserFactory.java b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserFactory.java
index c558227df0b5..e37419127f5b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserFactory.java
@@ -16,6 +16,7 @@
package com.android.systemui.media.controls.resume;
+import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Context;
@@ -42,10 +43,12 @@ public class ResumeMediaBrowserFactory {
*
* @param callback will be called on connection or error, and addTrack when media item found
* @param componentName component to browse
+ * @param userId ID of the current user
* @return
*/
public ResumeMediaBrowser create(ResumeMediaBrowser.Callback callback,
- ComponentName componentName) {
- return new ResumeMediaBrowser(mContext, callback, componentName, mBrowserFactory, mLogger);
+ ComponentName componentName, @UserIdInt int userId) {
+ return new ResumeMediaBrowser(mContext, callback, componentName, mBrowserFactory, mLogger,
+ userId);
}
}
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 4a3c56328006..159932f44ed1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
@@ -16,6 +16,7 @@
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 com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE;
@@ -223,7 +224,12 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
return;
}
mSelectedCard = cards.get(selectedIndex);
- mCardViewDrawable = mSelectedCard.getCardImage().loadDrawable(mContext);
+ android.graphics.drawable.Icon cardImageIcon = mSelectedCard.getCardImage();
+ if (cardImageIcon.getType() == TYPE_URI) {
+ mCardViewDrawable = null;
+ } else {
+ mCardViewDrawable = mSelectedCard.getCardImage().loadDrawable(mContext);
+ }
refreshState();
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index bf5fbd223186..baf765d5571c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -141,7 +141,12 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
// Since Quick Share target recommendation does not rely on image URL, it is
// queried and surfaced before image compress/export. Action intent would not be
// used, because it does not contain image URL.
- queryQuickShareAction(image, user);
+ Notification.Action quickShare =
+ queryQuickShareAction(mScreenshotId, image, user, null);
+ if (quickShare != null) {
+ mQuickShareData.quickShareAction = quickShare;
+ mParams.mQuickShareActionsReadyListener.onActionsReady(mQuickShareData);
+ }
}
// Call synchronously here since already on a background thread.
@@ -180,9 +185,10 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
smartActionsEnabled);
mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri,
smartActionsEnabled);
- mImageData.quickShareAction = createQuickShareAction(mContext,
- mQuickShareData.quickShareAction, uri);
- mImageData.subject = getSubjectString();
+ mImageData.quickShareAction = createQuickShareAction(
+ mQuickShareData.quickShareAction, mScreenshotId, uri, mImageTime, image,
+ user);
+ mImageData.subject = getSubjectString(mImageTime);
mParams.mActionsReadyListener.onActionsReady(mImageData);
if (DEBUG_CALLBACK) {
@@ -257,7 +263,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}),
new ClipData.Item(uri));
sharingIntent.setClipData(clipdata);
- sharingIntent.putExtra(Intent.EXTRA_SUBJECT, getSubjectString());
+ sharingIntent.putExtra(Intent.EXTRA_SUBJECT, getSubjectString(mImageTime));
sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
@@ -423,60 +429,73 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
}
/**
- * Populate image uri into intent of Quick Share action.
+ * Wrap the quickshare intent and populate the fillin intent with the URI
*/
@VisibleForTesting
- private Notification.Action createQuickShareAction(Context context, Notification.Action action,
- Uri uri) {
- if (action == null) {
+ Notification.Action createQuickShareAction(
+ Notification.Action quickShare, String screenshotId, Uri uri, long imageTime,
+ Bitmap image, UserHandle user) {
+ if (quickShare == null) {
return null;
+ } else if (quickShare.actionIntent.isImmutable()) {
+ Notification.Action quickShareWithUri =
+ queryQuickShareAction(screenshotId, image, user, uri);
+ if (quickShareWithUri == null
+ || !quickShareWithUri.title.toString().contentEquals(quickShare.title)) {
+ return null;
+ }
+ quickShare = quickShareWithUri;
}
- // Populate image URI into Quick Share chip intent
- Intent sharingIntent = action.actionIntent.getIntent();
- sharingIntent.setType("image/png");
- sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
- String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
- String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
- sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
- // Include URI in ClipData also, so that grantPermission picks it up.
- // We don't use setData here because some apps interpret this as "to:".
- ClipData clipdata = new ClipData(new ClipDescription("content",
- new String[]{"image/png"}),
- new ClipData.Item(uri));
- sharingIntent.setClipData(clipdata);
- sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- PendingIntent updatedPendingIntent = PendingIntent.getActivity(
- context, 0, sharingIntent,
- PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
-
- // Proxy smart actions through {@link SmartActionsReceiver} for logging smart actions.
- Bundle extras = action.getExtras();
+
+ Intent wrappedIntent = new Intent(mContext, SmartActionsReceiver.class)
+ .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, quickShare.actionIntent)
+ .putExtra(ScreenshotController.EXTRA_ACTION_INTENT_FILLIN,
+ createFillInIntent(uri, imageTime))
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ Bundle extras = quickShare.getExtras();
String actionType = extras.getString(
ScreenshotNotificationSmartActionsProvider.ACTION_TYPE,
ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE);
- Intent intent = new Intent(context, SmartActionsReceiver.class)
- .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, updatedPendingIntent)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
// We only query for quick share actions when smart actions are enabled, so we can assert
// that it's true here.
- addIntentExtras(mScreenshotId, intent, actionType, true /* smartActionsEnabled */);
- PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
- mRandom.nextInt(),
- intent,
- PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
- return new Notification.Action.Builder(action.getIcon(), action.title,
- broadcastIntent).setContextual(true).addExtras(extras).build();
+ addIntentExtras(screenshotId, wrappedIntent, actionType, true /* smartActionsEnabled */);
+ PendingIntent broadcastIntent =
+ PendingIntent.getBroadcast(mContext, mRandom.nextInt(), wrappedIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ return new Notification.Action.Builder(quickShare.getIcon(), quickShare.title,
+ broadcastIntent)
+ .setContextual(true)
+ .addExtras(extras)
+ .build();
+ }
+
+ private Intent createFillInIntent(Uri uri, long imageTime) {
+ Intent fillIn = new Intent();
+ fillIn.setType("image/png");
+ fillIn.putExtra(Intent.EXTRA_STREAM, uri);
+ fillIn.putExtra(Intent.EXTRA_SUBJECT, getSubjectString(imageTime));
+ // Include URI in ClipData also, so that grantPermission picks it up.
+ // We don't use setData here because some apps interpret this as "to:".
+ ClipData clipData = new ClipData(
+ new ClipDescription("content", new String[]{"image/png"}),
+ new ClipData.Item(uri));
+ fillIn.setClipData(clipData);
+ fillIn.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ return fillIn;
}
/**
* Query and surface Quick Share chip if it is available. Action intent would not be used,
* because it does not contain image URL which would be populated in {@link
- * #createQuickShareAction(Context, Notification.Action, Uri)}
+ * #createQuickShareAction(Notification.Action, String, Uri, long, Bitmap, UserHandle)}
*/
- private void queryQuickShareAction(Bitmap image, UserHandle user) {
+
+ @VisibleForTesting
+ Notification.Action queryQuickShareAction(
+ String screenshotId, Bitmap image, UserHandle user, Uri uri) {
CompletableFuture<List<Notification.Action>> quickShareActionsFuture =
mScreenshotSmartActions.getSmartActionsFuture(
- mScreenshotId, null, image, mSmartActionsProvider,
+ screenshotId, uri, image, mSmartActionsProvider,
ScreenshotSmartActionType.QUICK_SHARE_ACTION,
true /* smartActionsEnabled */, user);
int timeoutMs = DeviceConfig.getInt(
@@ -485,17 +504,17 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
500);
List<Notification.Action> quickShareActions =
mScreenshotSmartActions.getSmartActions(
- mScreenshotId, quickShareActionsFuture, timeoutMs,
+ screenshotId, quickShareActionsFuture, timeoutMs,
mSmartActionsProvider,
ScreenshotSmartActionType.QUICK_SHARE_ACTION);
if (!quickShareActions.isEmpty()) {
- mQuickShareData.quickShareAction = quickShareActions.get(0);
- mParams.mQuickShareActionsReadyListener.onActionsReady(mQuickShareData);
+ return quickShareActions.get(0);
}
+ return null;
}
- private String getSubjectString() {
- String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
+ private static String getSubjectString(long imageTime) {
+ String subjectDate = DateFormat.getDateTimeInstance().format(new Date(imageTime));
return String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 9f4929aceedf..c291f87566c0 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -246,6 +246,7 @@ public class ScreenshotController {
static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled";
static final String EXTRA_OVERRIDE_TRANSITION = "android:screenshot_override_transition";
static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";
+ static final String EXTRA_ACTION_INTENT_FILLIN = "android:screenshot_action_intent_fillin";
static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id";
static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification";
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
index 68b46d2b7525..ca713feafe80 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
@@ -30,7 +30,6 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Log;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -61,7 +60,6 @@ public class ScreenshotSmartActions {
screenshotNotificationSmartActionsProviderProvider;
}
- @VisibleForTesting
CompletableFuture<List<Notification.Action>> getSmartActionsFuture(
String screenshotId, Uri screenshotUri, Bitmap image,
ScreenshotNotificationSmartActionsProvider smartActionsProvider,
@@ -83,7 +81,7 @@ public class ScreenshotSmartActions {
if (image.getConfig() != Bitmap.Config.HARDWARE) {
if (DEBUG_ACTIONS) {
Log.d(TAG, String.format("Bitmap expected: Hardware, Bitmap found: %s. "
- + "Returning empty list.", image.getConfig()));
+ + "Returning empty list.", image.getConfig()));
}
return CompletableFuture.completedFuture(Collections.emptyList());
}
@@ -112,7 +110,6 @@ public class ScreenshotSmartActions {
return smartActionsFuture;
}
- @VisibleForTesting
List<Notification.Action> getSmartActions(String screenshotId,
CompletableFuture<List<Notification.Action>> smartActionsFuture, int timeoutMs,
ScreenshotNotificationSmartActionsProvider smartActionsProvider,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
index 45af1874e9db..9761f5931193 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
@@ -18,6 +18,7 @@ package com.android.systemui.screenshot;
import static com.android.systemui.screenshot.LogConfig.DEBUG_ACTIONS;
import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_INTENT;
+import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_INTENT_FILLIN;
import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_TYPE;
import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ID;
@@ -46,7 +47,9 @@ public class SmartActionsReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- PendingIntent pendingIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT);
+ PendingIntent pendingIntent =
+ intent.getParcelableExtra(EXTRA_ACTION_INTENT, PendingIntent.class);
+ Intent fillIn = intent.getParcelableExtra(EXTRA_ACTION_INTENT_FILLIN, Intent.class);
String actionType = intent.getStringExtra(EXTRA_ACTION_TYPE);
if (DEBUG_ACTIONS) {
Log.d(TAG, "Executing smart action [" + actionType + "]:" + pendingIntent.getIntent());
@@ -54,7 +57,7 @@ public class SmartActionsReceiver extends BroadcastReceiver {
ActivityOptions opts = ActivityOptions.makeBasic();
try {
- pendingIntent.send(context, 0, null, null, null, null, opts.toBundle());
+ pendingIntent.send(context, 0, fillIn, null, null, null, opts.toBundle());
} catch (PendingIntent.CanceledException e) {
Log.e(TAG, "Pending intent canceled", e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
index f98c6770ae59..403716b9e10c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
@@ -106,9 +106,7 @@ class PrivacyEvent(override val showAnimation: Boolean = true) : StatusEvent {
}
override fun shouldUpdateFromEvent(other: StatusEvent?): Boolean {
- return other is PrivacyEvent && (other.privacyItems != privacyItems ||
- other.contentDescription != contentDescription ||
- (other.forceVisible && !forceVisible))
+ return other is PrivacyEvent && other.forceVisible && !forceVisible
}
override fun updateFromEvent(other: StatusEvent?) {
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 492f2318fec6..81d04d4458c0 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
@@ -338,7 +338,12 @@ public class WalletScreenController implements
*/
QAWalletCardViewInfo(Context context, WalletCard walletCard) {
mWalletCard = walletCard;
- mCardDrawable = mWalletCard.getCardImage().loadDrawable(context);
+ Icon cardImageIcon = mWalletCard.getCardImage();
+ if (cardImageIcon.getType() == Icon.TYPE_URI) {
+ mCardDrawable = null;
+ } else {
+ mCardDrawable = mWalletCard.getCardImage().loadDrawable(context);
+ }
Icon icon = mWalletCard.getCardIcon();
mIconDrawable = icon == null ? null : icon.loadDrawable(context);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt
index 9ab728949e40..530b86eb4978 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt
@@ -98,6 +98,8 @@ class MediaResumeListenerTest : SysuiTestCase() {
@Captor lateinit var callbackCaptor: ArgumentCaptor<ResumeMediaBrowser.Callback>
@Captor lateinit var actionCaptor: ArgumentCaptor<Runnable>
@Captor lateinit var componentCaptor: ArgumentCaptor<String>
+ @Captor lateinit var userIdCaptor: ArgumentCaptor<Int>
+ @Captor lateinit var userCallbackCaptor: ArgumentCaptor<UserTracker.Callback>
private lateinit var executor: FakeExecutor
private lateinit var data: MediaData
@@ -124,7 +126,7 @@ class MediaResumeListenerTest : SysuiTestCase() {
)
Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 1)
- whenever(resumeBrowserFactory.create(capture(callbackCaptor), any()))
+ whenever(resumeBrowserFactory.create(capture(callbackCaptor), any(), capture(userIdCaptor)))
.thenReturn(resumeBrowser)
// resume components are stored in sharedpreferences
@@ -334,6 +336,7 @@ class MediaResumeListenerTest : SysuiTestCase() {
@Test
fun testOnUserUnlock_loadsTracks() {
// Set up mock service to successfully find valid media
+ setUpMbsWithValidResolveInfo()
val description = MediaDescription.Builder().setTitle(TITLE).build()
val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
whenever(resumeBrowser.token).thenReturn(token)
@@ -417,6 +420,7 @@ class MediaResumeListenerTest : SysuiTestCase() {
@Test
fun testLoadComponents_recentlyPlayed_adds() {
// Set up browser to return successfully
+ setUpMbsWithValidResolveInfo()
val description = MediaDescription.Builder().setTitle(TITLE).build()
val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
whenever(resumeBrowser.token).thenReturn(token)
@@ -600,7 +604,7 @@ class MediaResumeListenerTest : SysuiTestCase() {
// Set up our factory to return a new browser so we can verify we disconnected the old one
val newResumeBrowser = mock(ResumeMediaBrowser::class.java)
- whenever(resumeBrowserFactory.create(capture(callbackCaptor), any()))
+ whenever(resumeBrowserFactory.create(capture(callbackCaptor), any(), anyInt()))
.thenReturn(newResumeBrowser)
// When the resume action is run
@@ -610,6 +614,66 @@ class MediaResumeListenerTest : SysuiTestCase() {
verify(resumeBrowser).disconnect()
}
+ @Test
+ fun testUserUnlocked_userChangeWhileQuerying() {
+ val firstUserId = 1
+ val secondUserId = 2
+ val description = MediaDescription.Builder().setTitle(TITLE).build()
+ val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
+
+ setUpMbsWithValidResolveInfo()
+ whenever(resumeBrowser.token).thenReturn(token)
+ whenever(resumeBrowser.appIntent).thenReturn(pendingIntent)
+
+ val unlockIntent =
+ Intent(Intent.ACTION_USER_UNLOCKED).apply {
+ putExtra(Intent.EXTRA_USER_HANDLE, firstUserId)
+ }
+ verify(userTracker).addCallback(capture(userCallbackCaptor), any())
+
+ // When the first user unlocks and we query their recent media
+ userCallbackCaptor.value.onUserChanged(firstUserId, context)
+ resumeListener.userUnlockReceiver.onReceive(context, unlockIntent)
+ whenever(resumeBrowser.userId).thenReturn(userIdCaptor.value)
+ verify(resumeBrowser, times(3)).findRecentMedia()
+
+ // And the user changes before the MBS response is received
+ userCallbackCaptor.value.onUserChanged(secondUserId, context)
+ callbackCaptor.value.addTrack(description, component, resumeBrowser)
+
+ // Then the loaded media is correctly associated with the first user
+ verify(mediaDataManager)
+ .addResumptionControls(
+ eq(firstUserId),
+ eq(description),
+ any(),
+ eq(token),
+ eq(PACKAGE_NAME),
+ eq(pendingIntent),
+ eq(PACKAGE_NAME)
+ )
+ }
+
+ @Test
+ fun testUserUnlocked_noComponent_doesNotQuery() {
+ // Set up a valid MBS, but user does not have the service available
+ setUpMbsWithValidResolveInfo()
+ val pm = mock(PackageManager::class.java)
+ whenever(mockContext.packageManager).thenReturn(pm)
+ whenever(pm.resolveServiceAsUser(any(), anyInt(), anyInt())).thenReturn(null)
+
+ val unlockIntent =
+ Intent(Intent.ACTION_USER_UNLOCKED).apply {
+ putExtra(Intent.EXTRA_USER_HANDLE, context.userId)
+ }
+
+ // When the user is unlocked, but does not have the component installed
+ resumeListener.userUnlockReceiver.onReceive(context, unlockIntent)
+
+ // Then we never attempt to connect to it
+ verify(resumeBrowser, never()).findRecentMedia()
+ }
+
/** Sets up mocks to successfully find a MBS that returns valid media. */
private fun setUpMbsWithValidResolveInfo() {
val pm = mock(PackageManager::class.java)
@@ -620,6 +684,8 @@ class MediaResumeListenerTest : SysuiTestCase() {
resolveInfo.serviceInfo = serviceInfo
resolveInfo.serviceInfo.name = CLASS_NAME
val resumeInfo = listOf(resolveInfo)
- whenever(pm.queryIntentServices(any(), anyInt())).thenReturn(resumeInfo)
+ whenever(pm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(resumeInfo)
+ whenever(pm.resolveServiceAsUser(any(), anyInt(), anyInt())).thenReturn(resolveInfo)
+ whenever(pm.getApplicationLabel(any())).thenReturn(PACKAGE_NAME)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserTest.kt
index a04cfd46588b..b45e66bfc31b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserTest.kt
@@ -93,7 +93,8 @@ public class ResumeMediaBrowserTest : SysuiTestCase() {
component,
browserFactory,
logger,
- mediaController
+ mediaController,
+ context.userId,
)
}
@@ -381,8 +382,9 @@ public class ResumeMediaBrowserTest : SysuiTestCase() {
componentName: ComponentName,
browserFactory: MediaBrowserFactory,
logger: ResumeMediaBrowserLogger,
- private val fakeController: MediaController
- ) : ResumeMediaBrowser(context, callback, componentName, browserFactory, logger) {
+ private val fakeController: MediaController,
+ userId: Int,
+ ) : ResumeMediaBrowser(context, callback, componentName, browserFactory, logger, userId) {
override fun createMediaController(token: MediaSession.Token): MediaController {
return fakeController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
index 596df7856ee1..f671e8f8d370 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
@@ -94,6 +94,8 @@ public class QuickAccessWalletTileTest extends SysuiTestCase {
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 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)
@@ -120,6 +122,8 @@ public class QuickAccessWalletTileTest extends SysuiTestCase {
private SecureSettings mSecureSettings;
@Mock
private QuickAccessWalletController mController;
+ @Mock
+ private Icon mCardImage;
@Captor
ArgumentCaptor<QuickAccessWalletClient.OnWalletCardsRetrievedCallback> mCallbackCaptor;
@@ -143,6 +147,8 @@ public class QuickAccessWalletTileTest extends SysuiTestCase {
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,
@@ -382,6 +388,28 @@ public class QuickAccessWalletTileTest extends SysuiTestCase {
}
@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_noCards_notUpdateSideViewDrawable() {
setUpWalletCard(/* hasCard= */ false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt
new file mode 100644
index 000000000000..fbb77cdc3049
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2023 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.screenshot
+
+import android.app.Notification
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.drawable.Icon
+import android.net.Uri
+import android.os.UserHandle
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.screenshot.ScreenshotController.SaveImageInBackgroundData
+import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import java.util.concurrent.CompletableFuture
+import java.util.function.Supplier
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class SaveImageInBackgroundTaskTest : SysuiTestCase() {
+ private val imageExporter = mock<ImageExporter>()
+ private val smartActions = mock<ScreenshotSmartActions>()
+ private val smartActionsProvider = mock<ScreenshotNotificationSmartActionsProvider>()
+ private val saveImageData = SaveImageInBackgroundData()
+ private val sharedTransitionSupplier =
+ mock<Supplier<ScreenshotController.SavedImageData.ActionTransition>>()
+ private val testScreenshotId: String = "testScreenshotId"
+ private val testBitmap = mock<Bitmap>()
+ private val testUser = UserHandle.getUserHandleForUid(0)
+ private val testIcon = mock<Icon>()
+ private val testImageTime = 1234.toLong()
+ private val flags = FakeFeatureFlags()
+
+ private val smartActionsUriFuture = mock<CompletableFuture<List<Notification.Action>>>()
+ private val smartActionsFuture = mock<CompletableFuture<List<Notification.Action>>>()
+
+ private val testUri: Uri = Uri.parse("testUri")
+ private val intent =
+ Intent(Intent.ACTION_SEND)
+ .setComponent(
+ ComponentName.unflattenFromString(
+ "com.google.android.test/com.google.android.test.TestActivity"
+ )
+ )
+ private val immutablePendingIntent =
+ PendingIntent.getBroadcast(
+ mContext,
+ 0,
+ intent,
+ PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+ private val mutablePendingIntent =
+ PendingIntent.getBroadcast(
+ mContext,
+ 0,
+ intent,
+ PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE
+ )
+
+ private val saveImageTask =
+ SaveImageInBackgroundTask(
+ mContext,
+ flags,
+ imageExporter,
+ smartActions,
+ saveImageData,
+ sharedTransitionSupplier,
+ smartActionsProvider,
+ )
+
+ @Before
+ fun setup() {
+ whenever(
+ smartActions.getSmartActionsFuture(
+ eq(testScreenshotId),
+ any(Uri::class.java),
+ eq(testBitmap),
+ eq(smartActionsProvider),
+ any(ScreenshotSmartActionType::class.java),
+ any(Boolean::class.java),
+ eq(testUser)
+ )
+ )
+ .thenReturn(smartActionsUriFuture)
+ whenever(
+ smartActions.getSmartActionsFuture(
+ eq(testScreenshotId),
+ eq(null),
+ eq(testBitmap),
+ eq(smartActionsProvider),
+ any(ScreenshotSmartActionType::class.java),
+ any(Boolean::class.java),
+ eq(testUser)
+ )
+ )
+ .thenReturn(smartActionsFuture)
+ }
+
+ @Test
+ fun testQueryQuickShare_noAction() {
+ whenever(
+ smartActions.getSmartActions(
+ eq(testScreenshotId),
+ eq(smartActionsFuture),
+ any(Int::class.java),
+ eq(smartActionsProvider),
+ eq(ScreenshotSmartActionType.QUICK_SHARE_ACTION)
+ )
+ )
+ .thenReturn(ArrayList<Notification.Action>())
+
+ val quickShareAction =
+ saveImageTask.queryQuickShareAction(testScreenshotId, testBitmap, testUser, testUri)
+
+ assertNull(quickShareAction)
+ }
+
+ @Test
+ fun testQueryQuickShare_withActions() {
+ val actions = ArrayList<Notification.Action>()
+ actions.add(constructAction("Action One", mutablePendingIntent))
+ actions.add(constructAction("Action Two", mutablePendingIntent))
+ whenever(
+ smartActions.getSmartActions(
+ eq(testScreenshotId),
+ eq(smartActionsUriFuture),
+ any(Int::class.java),
+ eq(smartActionsProvider),
+ eq(ScreenshotSmartActionType.QUICK_SHARE_ACTION)
+ )
+ )
+ .thenReturn(actions)
+
+ val quickShareAction =
+ saveImageTask.queryQuickShareAction(testScreenshotId, testBitmap, testUser, testUri)!!
+
+ assertEquals("Action One", quickShareAction.title)
+ assertEquals(mutablePendingIntent, quickShareAction.actionIntent)
+ }
+
+ @Test
+ fun testCreateQuickShareAction_originalWasNull_returnsNull() {
+ val quickShareAction =
+ saveImageTask.createQuickShareAction(
+ null,
+ testScreenshotId,
+ testUri,
+ testImageTime,
+ testBitmap,
+ testUser
+ )
+
+ assertNull(quickShareAction)
+ }
+
+ @Test
+ fun testCreateQuickShareAction_immutableIntentDifferentAction_returnsNull() {
+ val actions = ArrayList<Notification.Action>()
+ actions.add(constructAction("New Test Action", immutablePendingIntent))
+ whenever(
+ smartActions.getSmartActions(
+ eq(testScreenshotId),
+ eq(smartActionsUriFuture),
+ any(Int::class.java),
+ eq(smartActionsProvider),
+ eq(ScreenshotSmartActionType.QUICK_SHARE_ACTION)
+ )
+ )
+ .thenReturn(actions)
+ val origAction = constructAction("Old Test Action", immutablePendingIntent)
+
+ val quickShareAction =
+ saveImageTask.createQuickShareAction(
+ origAction,
+ testScreenshotId,
+ testUri,
+ testImageTime,
+ testBitmap,
+ testUser,
+ )
+
+ assertNull(quickShareAction)
+ }
+
+ @Test
+ fun testCreateQuickShareAction_mutableIntent_returnsSafeIntent() {
+ val actions = ArrayList<Notification.Action>()
+ val action = constructAction("Action One", mutablePendingIntent)
+ actions.add(action)
+ whenever(
+ smartActions.getSmartActions(
+ eq(testScreenshotId),
+ eq(smartActionsUriFuture),
+ any(Int::class.java),
+ eq(smartActionsProvider),
+ eq(ScreenshotSmartActionType.QUICK_SHARE_ACTION)
+ )
+ )
+ .thenReturn(actions)
+
+ val quickShareAction =
+ saveImageTask.createQuickShareAction(
+ constructAction("Test Action", mutablePendingIntent),
+ testScreenshotId,
+ testUri,
+ testImageTime,
+ testBitmap,
+ testUser
+ )
+ val quickSharePendingIntent =
+ quickShareAction.actionIntent.intent.extras!!.getParcelable(
+ ScreenshotController.EXTRA_ACTION_INTENT,
+ PendingIntent::class.java
+ )
+
+ assertEquals("Test Action", quickShareAction.title)
+ assertEquals(mutablePendingIntent, quickSharePendingIntent)
+ }
+
+ @Test
+ fun testCreateQuickShareAction_immutableIntent_returnsSafeIntent() {
+ val actions = ArrayList<Notification.Action>()
+ val action = constructAction("Test Action", immutablePendingIntent)
+ actions.add(action)
+ whenever(
+ smartActions.getSmartActions(
+ eq(testScreenshotId),
+ eq(smartActionsUriFuture),
+ any(Int::class.java),
+ eq(smartActionsProvider),
+ eq(ScreenshotSmartActionType.QUICK_SHARE_ACTION)
+ )
+ )
+ .thenReturn(actions)
+
+ val quickShareAction =
+ saveImageTask.createQuickShareAction(
+ constructAction("Test Action", immutablePendingIntent),
+ testScreenshotId,
+ testUri,
+ testImageTime,
+ testBitmap,
+ testUser,
+ )!!
+
+ assertEquals("Test Action", quickShareAction.title)
+ assertEquals(
+ immutablePendingIntent,
+ quickShareAction.actionIntent.intent.extras!!.getParcelable(
+ ScreenshotController.EXTRA_ACTION_INTENT,
+ PendingIntent::class.java
+ )
+ )
+ }
+
+ private fun constructAction(title: String, intent: PendingIntent): Notification.Action {
+ return Notification.Action.Builder(testIcon, title, intent).build()
+ }
+}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index d3cabd1e2501..269946e38ad0 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -4939,7 +4939,10 @@ public class AccountManagerService
p.setDataPosition(0);
Bundle simulateBundle = p.readBundle();
p.recycle();
- Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT, Intent.class);
+ Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT);
+ if (intent != null && intent.getClass() != Intent.class) {
+ return false;
+ }
Intent simulateIntent = simulateBundle.getParcelable(AccountManager.KEY_INTENT,
Intent.class);
if (intent == null) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index e6f2b9bd68f5..3e53bcff792a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -6880,7 +6880,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mActivityTaskManager.unhandledBack();
}
- // TODO: Move to ContentProviderHelper?
+ // TODO: Replace this method with one that returns a bound IContentProvider.
public ParcelFileDescriptor openContentUri(String uriString) throws RemoteException {
enforceNotIsolatedCaller("openContentUri");
final int userId = UserHandle.getCallingUserId();
@@ -6909,6 +6909,16 @@ public class ActivityManagerService extends IActivityManager.Stub
Log.e(TAG, "Cannot find package for uid: " + uid);
return null;
}
+
+ final ApplicationInfo appInfo = mPackageManagerInt.getApplicationInfo(
+ androidPackage.getPackageName(), /*flags*/0, Process.SYSTEM_UID,
+ UserHandle.USER_SYSTEM);
+ if (!appInfo.isVendor() && !appInfo.isSystemApp() && !appInfo.isSystemExt()
+ && !appInfo.isProduct()) {
+ Log.e(TAG, "openContentUri may only be used by vendor/system/product.");
+ return null;
+ }
+
final AttributionSource attributionSource = new AttributionSource(
Binder.getCallingUid(), androidPackage.getPackageName(), null);
pfd = cph.provider.openFile(attributionSource, uri, "r", null);
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 1bd50632ccbf..b459cfe6b44e 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -16,12 +16,19 @@
package com.android.server.media;
+import android.Manifest;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.app.PendingIntent;
import android.content.ComponentName;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
+import android.content.pm.ResolveInfo;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioSystem;
@@ -52,11 +59,15 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
import android.view.KeyEvent;
+import com.android.server.LocalServices;
+import com.android.server.uri.UriGrantsManagerInternal;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -72,6 +83,10 @@ import java.util.concurrent.CopyOnWriteArrayList;
// TODO(jaewan): Do not call service method directly -- introduce listener instead.
public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionRecordImpl {
private static final String TAG = "MediaSessionRecord";
+ private static final String[] ART_URIS = new String[] {
+ MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
+ MediaMetadata.METADATA_KEY_ART_URI,
+ MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI};
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
/**
@@ -125,6 +140,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
private final SessionStub mSession;
private final SessionCb mSessionCb;
private final MediaSessionService mService;
+ private final UriGrantsManagerInternal mUgmInternal;
private final Context mContext;
private final boolean mVolumeAdjustmentForRemoteGroupSessions;
@@ -186,6 +202,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
mAudioAttrs = DEFAULT_ATTRIBUTES;
mPolicies = policies;
+ mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
mVolumeAdjustmentForRemoteGroupSessions = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions);
@@ -884,6 +901,22 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
}
};
+ @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
+ private static boolean componentNameExists(
+ @NonNull ComponentName componentName, @NonNull Context context, int userId) {
+ Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+ mediaButtonIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ mediaButtonIntent.setComponent(componentName);
+
+ UserHandle userHandle = UserHandle.of(userId);
+ PackageManager pm = context.getPackageManager();
+
+ List<ResolveInfo> resolveInfos =
+ pm.queryBroadcastReceiversAsUser(
+ mediaButtonIntent, /* flags */ 0, userHandle);
+ return !resolveInfos.isEmpty();
+ }
+
private final class SessionStub extends ISession.Stub {
@Override
public void destroySession() throws RemoteException {
@@ -954,6 +987,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
}
@Override
+ @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
public void setMediaButtonBroadcastReceiver(ComponentName receiver) throws RemoteException {
final long token = Binder.clearCallingIdentity();
try {
@@ -969,6 +1003,16 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
!= 0) {
return;
}
+
+ if (!componentNameExists(receiver, mContext, mUserId)) {
+ Log.w(
+ TAG,
+ "setMediaButtonBroadcastReceiver(): "
+ + "Ignoring invalid component name="
+ + receiver);
+ return;
+ }
+
mMediaButtonReceiverHolder = MediaButtonReceiverHolder.create(mUserId, receiver);
mService.onMediaButtonReceiverChanged(MediaSessionRecord.this);
} finally {
@@ -985,21 +1029,45 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
public void setMetadata(MediaMetadata metadata, long duration, String metadataDescription)
throws RemoteException {
synchronized (mLock) {
- MediaMetadata temp = metadata == null ? null : new MediaMetadata.Builder(metadata)
- .build();
- // This is to guarantee that the underlying bundle is unparceled
- // before we set it to prevent concurrent reads from throwing an
- // exception
- if (temp != null) {
- temp.size();
- }
- mMetadata = temp;
mDuration = duration;
mMetadataDescription = metadataDescription;
+ mMetadata = sanitizeMediaMetadata(metadata);
}
mHandler.post(MessageHandler.MSG_UPDATE_METADATA);
}
+ private MediaMetadata sanitizeMediaMetadata(MediaMetadata metadata) {
+ if (metadata == null) {
+ return null;
+ }
+ MediaMetadata.Builder metadataBuilder = new MediaMetadata.Builder(metadata);
+ for (String key: ART_URIS) {
+ String uriString = metadata.getString(key);
+ if (TextUtils.isEmpty(uriString)) {
+ continue;
+ }
+ Uri uri = Uri.parse(uriString);
+ if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
+ continue;
+ }
+ try {
+ mUgmInternal.checkGrantUriPermission(getUid(),
+ getPackageName(),
+ ContentProvider.getUriWithoutUserId(uri),
+ Intent.FLAG_GRANT_READ_URI_PERMISSION,
+ ContentProvider.getUserIdFromUri(uri, getUserId()));
+ } catch (SecurityException e) {
+ metadataBuilder.putString(key, null);
+ }
+ }
+ MediaMetadata sanitizedMetadata = metadataBuilder.build();
+ // sanitizedMetadata.size() guarantees that the underlying bundle is unparceled
+ // before we set it to prevent concurrent reads from throwing an
+ // exception
+ sanitizedMetadata.size();
+ return sanitizedMetadata;
+ }
+
@Override
public void setPlaybackState(PlaybackState state) throws RemoteException {
int oldState = mPlaybackState == null
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 4df34cafc38b..d0ae7d59a784 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5484,6 +5484,11 @@ public class NotificationManagerService extends SystemService {
boolean granted, boolean userSet) {
Objects.requireNonNull(listener);
checkNotificationListenerAccess();
+ if (granted && listener.flattenToString().length()
+ > NotificationManager.MAX_SERVICE_COMPONENT_NAME_LENGTH) {
+ throw new IllegalArgumentException(
+ "Component name too long: " + listener.flattenToString());
+ }
if (!userSet && isNotificationListenerAccessUserSet(listener)) {
// Don't override user's choice
return;
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 7db15f938854..254d503e937f 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2130,9 +2130,25 @@ final class InstallPackageHelper {
// The caller explicitly specified INSTALL_ALL_USERS flag.
// Thus, updating the settings to install the app for all users.
for (int currentUserId : allUsers) {
- ps.setInstalled(true, currentUserId);
- ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId,
- installerPackageName);
+ // If the app is already installed for the currentUser,
+ // keep it as installed as we might be updating the app at this place.
+ // If not currently installed, check if the currentUser is restricted by
+ // DISALLOW_INSTALL_APPS or DISALLOW_DEBUGGING_FEATURES device policy.
+ // Install / update the app if the user isn't restricted. Skip otherwise.
+ final boolean installedForCurrentUser = ArrayUtils.contains(
+ installedForUsers, currentUserId);
+ final boolean restrictedByPolicy =
+ mPm.isUserRestricted(currentUserId,
+ UserManager.DISALLOW_INSTALL_APPS)
+ || mPm.isUserRestricted(currentUserId,
+ UserManager.DISALLOW_DEBUGGING_FEATURES);
+ if (installedForCurrentUser || !restrictedByPolicy) {
+ ps.setInstalled(true, currentUserId);
+ ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, currentUserId,
+ installerPackageName);
+ } else {
+ ps.setInstalled(false, currentUserId);
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
index 881f8707fdd8..d28048ce74c7 100644
--- a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
+++ b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
@@ -16,23 +16,26 @@
package com.android.server.pm.permission;
-import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
-
import android.annotation.NonNull;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
import android.app.AlarmManager;
+import android.app.IActivityManager;
+import android.app.IUidObserver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Handler;
+import android.os.RemoteException;
import android.permission.PermissionControllerManager;
import android.provider.DeviceConfig;
import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.server.LocalServices;
/**
* Class that handles one-time permissions for a user
@@ -47,7 +50,8 @@ public class OneTimePermissionUserManager {
"one_time_permissions_killed_delay_millis";
private final @NonNull Context mContext;
- private final @NonNull ActivityManager mActivityManager;
+ private final @NonNull IActivityManager mIActivityManager;
+ private final @NonNull ActivityManagerInternal mActivityManagerInternal;
private final @NonNull AlarmManager mAlarmManager;
private final @NonNull PermissionControllerManager mPermissionControllerManager;
@@ -77,49 +81,15 @@ public class OneTimePermissionUserManager {
OneTimePermissionUserManager(@NonNull Context context) {
mContext = context;
- mActivityManager = context.getSystemService(ActivityManager.class);
+ mIActivityManager = ActivityManager.getService();
+ mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
mAlarmManager = context.getSystemService(AlarmManager.class);
mPermissionControllerManager = context.getSystemService(PermissionControllerManager.class);
mHandler = context.getMainThreadHandler();
}
- /**
- * Starts a one-time permission session for a given package. A one-time permission session is
- * ended if app becomes inactive. Inactivity is defined as the package's uid importance level
- * staying > importanceToResetTimer for timeoutMillis milliseconds. If the package's uid
- * importance level goes <= importanceToResetTimer then the timer is reset and doesn't start
- * until going > importanceToResetTimer.
- * <p>
- * When this timeoutMillis is reached if the importance level is <= importanceToKeepSessionAlive
- * then the session is extended until either the importance goes above
- * importanceToKeepSessionAlive which will end the session or <= importanceToResetTimer which
- * will continue the session and reset the timer.
- * </p>
- * <p>
- * Importance levels are defined in {@link android.app.ActivityManager.RunningAppProcessInfo}.
- * </p>
- * <p>
- * Once the session ends PermissionControllerService#onNotifyOneTimePermissionSessionTimeout
- * is invoked.
- * </p>
- * <p>
- * Note that if there is currently an active session for a package a new one isn't created and
- * the existing one isn't changed.
- * </p>
- * @param packageName The package to start a one-time permission session for
- * @param timeoutMillis Number of milliseconds for an app to be in an inactive state
- * @param revokeAfterKilledDelayMillis Number of milliseconds to wait after the process dies
- * before ending the session. Set to -1 to use default value
- * for the device.
- * @param importanceToResetTimer The least important level to uid must be to reset the timer
- * @param importanceToKeepSessionAlive The least important level the uid must be to keep the
- * session alive
- *
- * @hide
- */
void startPackageOneTimeSession(@NonNull String packageName, long timeoutMillis,
- long revokeAfterKilledDelayMillis, int importanceToResetTimer,
- int importanceToKeepSessionAlive) {
+ long revokeAfterKilledDelayMillis) {
int uid;
try {
uid = mContext.getPackageManager().getPackageUid(packageName, 0);
@@ -131,13 +101,11 @@ public class OneTimePermissionUserManager {
synchronized (mLock) {
PackageInactivityListener listener = mListeners.get(uid);
if (listener != null) {
- listener.updateSessionParameters(timeoutMillis, revokeAfterKilledDelayMillis,
- importanceToResetTimer, importanceToKeepSessionAlive);
+ listener.updateSessionParameters(timeoutMillis, revokeAfterKilledDelayMillis);
return;
}
listener = new PackageInactivityListener(uid, packageName, timeoutMillis,
- revokeAfterKilledDelayMillis, importanceToResetTimer,
- importanceToKeepSessionAlive);
+ revokeAfterKilledDelayMillis);
mListeners.put(uid, listener);
}
}
@@ -182,34 +150,58 @@ public class OneTimePermissionUserManager {
private static final long TIMER_INACTIVE = -1;
+ private static final int STATE_GONE = 0;
+ private static final int STATE_TIMER = 1;
+ private static final int STATE_ACTIVE = 2;
+
private final int mUid;
private final @NonNull String mPackageName;
private long mTimeout;
private long mRevokeAfterKilledDelay;
- private int mImportanceToResetTimer;
- private int mImportanceToKeepSessionAlive;
private boolean mIsAlarmSet;
private boolean mIsFinished;
private long mTimerStart = TIMER_INACTIVE;
- private final ActivityManager.OnUidImportanceListener mStartTimerListener;
- private final ActivityManager.OnUidImportanceListener mSessionKillableListener;
- private final ActivityManager.OnUidImportanceListener mGoneListener;
-
private final Object mInnerLock = new Object();
private final Object mToken = new Object();
+ private final IUidObserver.Stub mObserver = new IUidObserver.Stub() {
+ @Override
+ public void onUidGone(int uid, boolean disabled) {
+ if (uid == mUid) {
+ PackageInactivityListener.this.updateUidState(STATE_GONE);
+ }
+ }
- private PackageInactivityListener(int uid, @NonNull String packageName, long timeout,
- long revokeAfterkilledDelay, int importanceToResetTimer,
- int importanceToKeepSessionAlive) {
+ @Override
+ public void onUidStateChanged(int uid, int procState, long procStateSeq,
+ int capability) {
+ if (uid == mUid) {
+ if (procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+ && procState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
+ PackageInactivityListener.this.updateUidState(STATE_TIMER);
+ } else {
+ PackageInactivityListener.this.updateUidState(STATE_ACTIVE);
+ }
+ }
+ }
+
+ public void onUidActive(int uid) {
+ }
+ public void onUidIdle(int uid, boolean disabled) {
+ }
+ public void onUidProcAdjChanged(int uid) {
+ }
+ public void onUidCachedChanged(int uid, boolean cached) {
+ }
+ };
+ private PackageInactivityListener(int uid, @NonNull String packageName, long timeout,
+ long revokeAfterkilledDelay) {
Log.i(LOG_TAG,
"Start tracking " + packageName + ". uid=" + uid + " timeout=" + timeout
- + " killedDelay=" + revokeAfterkilledDelay
- + " importanceToResetTimer=" + importanceToResetTimer
- + " importanceToKeepSessionAlive=" + importanceToKeepSessionAlive);
+ + " killedDelay=" + revokeAfterkilledDelay);
mUid = uid;
mPackageName = packageName;
@@ -219,27 +211,24 @@ public class OneTimePermissionUserManager {
DeviceConfig.NAMESPACE_PERMISSIONS, PROPERTY_KILLED_DELAY_CONFIG_KEY,
DEFAULT_KILLED_DELAY_MILLIS)
: revokeAfterkilledDelay;
- mImportanceToResetTimer = importanceToResetTimer;
- mImportanceToKeepSessionAlive = importanceToKeepSessionAlive;
-
- mStartTimerListener =
- (changingUid, importance) -> onImportanceChanged(changingUid, importance);
- mSessionKillableListener =
- (changingUid, importance) -> onImportanceChanged(changingUid, importance);
- mGoneListener =
- (changingUid, importance) -> onImportanceChanged(changingUid, importance);
-
- mActivityManager.addOnUidImportanceListener(mStartTimerListener,
- importanceToResetTimer);
- mActivityManager.addOnUidImportanceListener(mSessionKillableListener,
- importanceToKeepSessionAlive);
- mActivityManager.addOnUidImportanceListener(mGoneListener, IMPORTANCE_CACHED);
-
- onImportanceChanged(mUid, mActivityManager.getPackageImportance(packageName));
+
+ try {
+ mIActivityManager.registerUidObserver(mObserver,
+ ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_PROCSTATE,
+ ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE,
+ null);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Couldn't check uid proc state", e);
+ // Can't register uid observer, just revoke immediately
+ synchronized (mInnerLock) {
+ onPackageInactiveLocked();
+ }
+ }
+
+ updateUidState();
}
- public void updateSessionParameters(long timeoutMillis, long revokeAfterKilledDelayMillis,
- int importanceToResetTimer, int importanceToKeepSessionAlive) {
+ public void updateSessionParameters(long timeoutMillis, long revokeAfterKilledDelayMillis) {
synchronized (mInnerLock) {
mTimeout = Math.min(mTimeout, timeoutMillis);
mRevokeAfterKilledDelay = Math.min(mRevokeAfterKilledDelay,
@@ -248,63 +237,74 @@ public class OneTimePermissionUserManager {
DeviceConfig.NAMESPACE_PERMISSIONS,
PROPERTY_KILLED_DELAY_CONFIG_KEY, DEFAULT_KILLED_DELAY_MILLIS)
: revokeAfterKilledDelayMillis);
- mImportanceToResetTimer = Math.min(importanceToResetTimer, mImportanceToResetTimer);
- mImportanceToKeepSessionAlive = Math.min(importanceToKeepSessionAlive,
- mImportanceToKeepSessionAlive);
Log.v(LOG_TAG,
"Updated params for " + mPackageName + ". timeout=" + mTimeout
- + " killedDelay=" + mRevokeAfterKilledDelay
- + " importanceToResetTimer=" + mImportanceToResetTimer
- + " importanceToKeepSessionAlive=" + mImportanceToKeepSessionAlive);
- onImportanceChanged(mUid, mActivityManager.getPackageImportance(mPackageName));
+ + " killedDelay=" + mRevokeAfterKilledDelay);
+ updateUidState();
}
}
- private void onImportanceChanged(int uid, int importance) {
- if (uid != mUid) {
- return;
+ private int getCurrentState() {
+ return getStateFromProcState(mActivityManagerInternal.getUidProcessState(mUid));
+ }
+
+ private int getStateFromProcState(int procState) {
+ if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
+ return STATE_GONE;
+ } else {
+ if (procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+ return STATE_TIMER;
+ } else {
+ return STATE_ACTIVE;
+ }
}
+ }
- Log.v(LOG_TAG, "Importance changed for " + mPackageName + " (" + mUid + ")."
- + " importance=" + importance);
+ private void updateUidState() {
+ updateUidState(getCurrentState());
+ }
+
+ private void updateUidState(int state) {
+ Log.v(LOG_TAG, "Updating state for " + mPackageName + " (" + mUid + ")."
+ + " state=" + state);
synchronized (mInnerLock) {
// Remove any pending inactivity callback
mHandler.removeCallbacksAndMessages(mToken);
- if (importance > IMPORTANCE_CACHED) {
+ if (state == STATE_GONE) {
if (mRevokeAfterKilledDelay == 0) {
onPackageInactiveLocked();
return;
}
// Delay revocation in case app is restarting
mHandler.postDelayed(() -> {
- int imp = mActivityManager.getUidImportance(mUid);
- if (imp > IMPORTANCE_CACHED) {
- onPackageInactiveLocked();
- } else {
- if (DEBUG) {
- Log.d(LOG_TAG, "No longer gone after delayed revocation. "
- + "Rechecking for " + mPackageName + " (" + mUid + ").");
+ int currentState;
+ synchronized (mInnerLock) {
+ currentState = getCurrentState();
+ if (currentState == STATE_GONE) {
+ onPackageInactiveLocked();
+ return;
}
- onImportanceChanged(mUid, imp);
}
+ if (DEBUG) {
+ Log.d(LOG_TAG, "No longer gone after delayed revocation. "
+ + "Rechecking for " + mPackageName + " (" + mUid
+ + ").");
+ }
+ updateUidState(currentState);
}, mToken, mRevokeAfterKilledDelay);
return;
- }
- if (importance > mImportanceToResetTimer) {
+ } else if (state == STATE_TIMER) {
if (mTimerStart == TIMER_INACTIVE) {
if (DEBUG) {
Log.d(LOG_TAG, "Start the timer for "
+ mPackageName + " (" + mUid + ").");
}
mTimerStart = System.currentTimeMillis();
+ setAlarmLocked();
}
- } else {
+ } else if (state == STATE_ACTIVE) {
mTimerStart = TIMER_INACTIVE;
- }
- if (importance > mImportanceToKeepSessionAlive) {
- setAlarmLocked();
- } else {
cancelAlarmLocked();
}
}
@@ -318,19 +318,9 @@ public class OneTimePermissionUserManager {
mIsFinished = true;
cancelAlarmLocked();
try {
- mActivityManager.removeOnUidImportanceListener(mStartTimerListener);
- } catch (IllegalArgumentException e) {
- Log.e(LOG_TAG, "Could not remove start timer listener", e);
- }
- try {
- mActivityManager.removeOnUidImportanceListener(mSessionKillableListener);
- } catch (IllegalArgumentException e) {
- Log.e(LOG_TAG, "Could not remove session killable listener", e);
- }
- try {
- mActivityManager.removeOnUidImportanceListener(mGoneListener);
- } catch (IllegalArgumentException e) {
- Log.e(LOG_TAG, "Could not remove gone listener", e);
+ mIActivityManager.unregisterUidObserver(mObserver);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Unable to unregister uid observer.", e);
}
}
}
@@ -394,9 +384,11 @@ public class OneTimePermissionUserManager {
mPermissionControllerManager.notifyOneTimePermissionSessionTimeout(
mPackageName);
});
- mActivityManager.removeOnUidImportanceListener(mStartTimerListener);
- mActivityManager.removeOnUidImportanceListener(mSessionKillableListener);
- mActivityManager.removeOnUidImportanceListener(mGoneListener);
+ try {
+ mIActivityManager.unregisterUidObserver(mObserver);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Unable to unregister uid observer.", e);
+ }
synchronized (mLock) {
mListeners.remove(mUid);
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index d648d6fc663d..fd48cdaeebf2 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -386,8 +386,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {
@Override
public void startOneTimePermissionSession(String packageName, @UserIdInt int userId,
- long timeoutMillis, long revokeAfterKilledDelayMillis, int importanceToResetTimer,
- int importanceToKeepSessionAlive) {
+ long timeoutMillis, long revokeAfterKilledDelayMillis) {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS,
"Must hold " + Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS
@@ -397,8 +396,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {
final long token = Binder.clearCallingIdentity();
try {
getOneTimePermissionUserManager(userId).startPackageOneTimeSession(packageName,
- timeoutMillis, revokeAfterKilledDelayMillis, importanceToResetTimer,
- importanceToKeepSessionAlive);
+ timeoutMillis, revokeAfterKilledDelayMillis);
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 9ed5aa7158ab..8dadd3190ac3 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -2784,29 +2784,55 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
+ pkg.getPackageName());
}
- if ((bp.isNormal() && shouldGrantNormalPermission)
- || (bp.isSignature()
- && (!bp.isPrivileged() || CollectionUtils.contains(
- isPrivilegedPermissionAllowlisted, permName))
- && (CollectionUtils.contains(shouldGrantSignaturePermission,
- permName)
- || (((bp.isPrivileged() && CollectionUtils.contains(
- shouldGrantPrivilegedPermissionIfWasGranted,
- permName)) || bp.isDevelopment() || bp.isRole())
- && origState.isPermissionGranted(permName))))
- || (bp.isInternal()
- && (!bp.isPrivileged() || CollectionUtils.contains(
- isPrivilegedPermissionAllowlisted, permName))
- && (CollectionUtils.contains(shouldGrantInternalPermission,
- permName)
- || (((bp.isPrivileged() && CollectionUtils.contains(
- shouldGrantPrivilegedPermissionIfWasGranted,
- permName)) || bp.isDevelopment() || bp.isRole())
- && origState.isPermissionGranted(permName))))) {
- // Grant an install permission.
- if (uidState.grantPermission(bp)) {
- changedInstallPermission = true;
+ if (bp.isNormal() || bp.isSignature() || bp.isInternal()) {
+ if ((bp.isNormal() && shouldGrantNormalPermission)
+ || (bp.isSignature()
+ && (!bp.isPrivileged() || CollectionUtils.contains(
+ isPrivilegedPermissionAllowlisted, permName))
+ && (CollectionUtils.contains(shouldGrantSignaturePermission,
+ permName)
+ || (((bp.isPrivileged() && CollectionUtils.contains(
+ shouldGrantPrivilegedPermissionIfWasGranted,
+ permName)) || bp.isDevelopment()
+ || bp.isRole())
+ && origState.isPermissionGranted(
+ permName))))
+ || (bp.isInternal()
+ && (!bp.isPrivileged() || CollectionUtils.contains(
+ isPrivilegedPermissionAllowlisted, permName))
+ && (CollectionUtils.contains(shouldGrantInternalPermission,
+ permName)
+ || (((bp.isPrivileged() && CollectionUtils.contains(
+ shouldGrantPrivilegedPermissionIfWasGranted,
+ permName)) || bp.isDevelopment()
+ || bp.isRole())
+ && origState.isPermissionGranted(
+ permName))))) {
+ // Grant an install permission.
+ if (uidState.grantPermission(bp)) {
+ changedInstallPermission = true;
+ }
+ } else {
+ if (DEBUG_PERMISSIONS) {
+ boolean wasGranted = uidState.isPermissionGranted(bp.getName());
+ if (wasGranted || bp.isAppOp()) {
+ Slog.i(TAG, (wasGranted ? "Un-granting" : "Not granting")
+ + " permission " + perm
+ + " from package " + friendlyName
+ + " (protectionLevel=" + bp.getProtectionLevel()
+ + " flags=0x"
+ + Integer.toHexString(PackageInfoUtils.appInfoFlags(pkg,
+ ps))
+ + ")");
+ }
+ }
+ if (uidState.revokePermission(bp)) {
+ changedInstallPermission = true;
+ }
}
+ PermissionState origPermState = origState.getPermissionState(perm);
+ int flags = origPermState != null ? origPermState.getFlags() : 0;
+ uidState.updatePermissionFlags(bp, MASK_PERMISSION_FLAGS_ALL, flags);
} else if (bp.isRuntime()) {
boolean hardRestricted = bp.isHardRestricted();
boolean softRestricted = bp.isSoftRestricted();
@@ -2930,22 +2956,8 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
uidState.updatePermissionFlags(bp, MASK_PERMISSION_FLAGS_ALL,
flags);
} else {
- if (DEBUG_PERMISSIONS) {
- boolean wasGranted = uidState.isPermissionGranted(bp.getName());
- if (wasGranted || bp.isAppOp()) {
- Slog.i(TAG, (wasGranted ? "Un-granting" : "Not granting")
- + " permission " + perm
- + " from package " + friendlyName
- + " (protectionLevel=" + bp.getProtectionLevel()
- + " flags=0x"
- + Integer.toHexString(PackageInfoUtils.appInfoFlags(pkg,
- ps))
- + ")");
- }
- }
- if (uidState.removePermissionState(bp.getName())) {
- changedInstallPermission = true;
- }
+ Slog.wtf(LOG_TAG, "Unknown permission protection " + bp.getProtection()
+ + " for permission " + bp.getName());
}
}
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index b296ef2a1443..1ff01a6c70bf 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -1049,7 +1049,11 @@ public class VrManagerService extends SystemService
for (ComponentName c : possibleServices) {
if (Objects.equals(c.getPackageName(), pkg)) {
- nm.setNotificationListenerAccessGrantedForUser(c, userId, true);
+ try {
+ nm.setNotificationListenerAccessGrantedForUser(c, userId, true);
+ } catch (Exception e) {
+ Slog.w(TAG, "Could not grant NLS access to package " + pkg, e);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 5ad04b3c3638..6e904f24fa51 100755
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1713,7 +1713,8 @@ class ActivityStarter {
// If Activity's launching into PiP, move the mStartActivity immediately to pinned mode.
// Note that mStartActivity and source should be in the same Task at this point.
if (mOptions != null && mOptions.isLaunchIntoPip()
- && sourceRecord != null && sourceRecord.getTask() == mStartActivity.getTask()) {
+ && sourceRecord != null && sourceRecord.getTask() == mStartActivity.getTask()
+ && balCode != BAL_BLOCK) {
mRootWindowContainer.moveActivityToPinnedRootTask(mStartActivity,
sourceRecord, "launch-into-pip");
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 95fea0ee22f5..66213cc6403d 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3596,12 +3596,20 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// apps won't always be considered as foreground state.
// Exclude private presentations as they can only be shown on private virtual displays and
// shouldn't be the cause of an app be considered foreground.
- if (mAttrs.type >= FIRST_SYSTEM_WINDOW && mAttrs.type != TYPE_TOAST
- && mAttrs.type != TYPE_PRIVATE_PRESENTATION) {
+ // Exclude presentations on virtual displays as they are not actually visible.
+ if (mAttrs.type >= FIRST_SYSTEM_WINDOW
+ && mAttrs.type != TYPE_TOAST
+ && mAttrs.type != TYPE_PRIVATE_PRESENTATION
+ && !(mAttrs.type == TYPE_PRESENTATION && isOnVirtualDisplay())
+ ) {
mWmService.mAtmService.mActiveUids.onNonAppSurfaceVisibilityChanged(mOwnerUid, shown);
}
}
+ private boolean isOnVirtualDisplay() {
+ return getDisplayContent().mDisplay.getType() == Display.TYPE_VIRTUAL;
+ }
+
private void logExclusionRestrictions(int side) {
if (!logsGestureExclusionRestrictions(this)
|| SystemClock.uptimeMillis() < mLastExclusionLogUptimeMillis[side]
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 27d5eb4541e5..c07b058bb2ca 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -397,6 +397,7 @@ import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.DateFormat;
import java.time.LocalDate;
+import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -408,6 +409,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
+import java.util.Queue;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
@@ -439,7 +441,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
private static final int REQUEST_PROFILE_OFF_DEADLINE = 5572;
+ // Binary XML serializer doesn't support longer strings
+ private static final int MAX_POLICY_STRING_LENGTH = 65535;
+ // FrameworkParsingPackageUtils#MAX_FILE_NAME_SIZE, Android packages are used in dir names.
+ private static final int MAX_PACKAGE_NAME_LENGTH = 223;
+
private static final int MAX_PROFILE_NAME_LENGTH = 200;
+ private static final int MAX_LONG_SUPPORT_MESSAGE_LENGTH = 20000;
+ private static final int MAX_SHORT_SUPPORT_MESSAGE_LENGTH = 200;
+ private static final int MAX_ORG_NAME_LENGTH = 200;
private static final long MS_PER_DAY = TimeUnit.DAYS.toMillis(1);
@@ -10053,6 +10063,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
Objects.requireNonNull(admin, "admin is null");
Objects.requireNonNull(agent, "agent is null");
+ enforceMaxPackageNameLength(agent.getPackageName());
+ final String agentAsString = agent.flattenToString();
+ enforceMaxStringLength(agentAsString, "agent name");
+ if (args != null) {
+ enforceMaxStringLength(args, "args");
+ }
final int userHandle = UserHandle.getCallingUserId();
synchronized (getLockObject()) {
ActiveAdmin ap = getActiveAdminForCallerLocked(admin,
@@ -10294,6 +10310,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final CallerIdentity caller = getCallerIdentity(who);
if (packageList != null) {
+ for (String pkg : packageList) {
+ enforceMaxPackageNameLength(pkg);
+ }
+
int userId = caller.getUserId();
final List<AccessibilityServiceInfo> enabledServices;
long id = mInjector.binderClearCallingIdentity();
@@ -10463,6 +10483,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
if (packageList != null) {
+ for (String pkg : packageList) {
+ enforceMaxPackageNameLength(pkg);
+ }
+
List<InputMethodInfo> enabledImes = mInjector.binderWithCleanCallingIdentity(() ->
InputMethodManagerInternal.get().getEnabledInputMethodListAsUser(userId));
if (enabledImes != null) {
@@ -11793,6 +11817,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return;
}
Objects.requireNonNull(who, "ComponentName is null");
+ enforceMaxStringLength(accountType, "account type");
+
final CallerIdentity caller = getCallerIdentity(who);
synchronized (getLockObject()) {
/*
@@ -12214,6 +12240,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
throws SecurityException {
Objects.requireNonNull(who, "ComponentName is null");
Objects.requireNonNull(packages, "packages is null");
+ for (String pkg : packages) {
+ enforceMaxPackageNameLength(pkg);
+ }
+
final CallerIdentity caller = getCallerIdentity(who);
synchronized (getLockObject()) {
@@ -14333,6 +14363,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return;
}
Objects.requireNonNull(who, "ComponentName is null");
+ message = truncateIfLonger(message, MAX_SHORT_SUPPORT_MESSAGE_LENGTH);
+
final CallerIdentity caller = getCallerIdentity(who);
synchronized (getLockObject()) {
ActiveAdmin admin = getActiveAdminForUidLocked(who, caller.getUid());
@@ -14365,6 +14397,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (!mHasFeature) {
return;
}
+
+ message = truncateIfLonger(message, MAX_LONG_SUPPORT_MESSAGE_LENGTH);
+
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
synchronized (getLockObject()) {
@@ -14514,6 +14549,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
+ text = truncateIfLonger(text, MAX_ORG_NAME_LENGTH);
+
synchronized (getLockObject()) {
ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
if (!TextUtils.equals(admin.organizationName, text)) {
@@ -14786,9 +14823,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
throw new IllegalArgumentException("ids must not be null");
}
for (String id : ids) {
- if (TextUtils.isEmpty(id)) {
- throw new IllegalArgumentException("ids must not contain empty string");
- }
+ Preconditions.checkArgument(!TextUtils.isEmpty(id), "ids must not have empty string");
+ enforceMaxStringLength(id, "affiliation id");
}
final Set<String> affiliationIds = new ArraySet<>(ids);
@@ -16099,6 +16135,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
"Provided administrator and target are the same object.");
Preconditions.checkArgument(!admin.getPackageName().equals(target.getPackageName()),
"Provided administrator and target have the same package name.");
+ if (bundle != null) {
+ enforceMaxStringLength(bundle, "bundle");
+ }
final CallerIdentity caller = getCallerIdentity(admin);
Preconditions.checkCallAuthorization(
@@ -18900,4 +18939,51 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return result;
});
}
+
+ /**
+ * Truncates char sequence to maximum length, nulls are ignored.
+ */
+ private static CharSequence truncateIfLonger(CharSequence input, int maxLength) {
+ return input == null || input.length() <= maxLength
+ ? input
+ : input.subSequence(0, maxLength);
+ }
+
+ /**
+ * Throw if string argument is too long to be serialized.
+ */
+ private static void enforceMaxStringLength(String str, String argName) {
+ Preconditions.checkArgument(
+ str.length() <= MAX_POLICY_STRING_LENGTH, argName + " loo long");
+ }
+
+ private static void enforceMaxPackageNameLength(String pkg) {
+ Preconditions.checkArgument(
+ pkg.length() <= MAX_PACKAGE_NAME_LENGTH, "Package name too long");
+ }
+
+ /**
+ * Throw if persistable bundle contains any string that we can't serialize.
+ */
+ private static void enforceMaxStringLength(PersistableBundle bundle, String argName) {
+ // Persistable bundles can have other persistable bundles as values, traverse with a queue.
+ Queue<PersistableBundle> queue = new ArrayDeque<>();
+ queue.add(bundle);
+ while (!queue.isEmpty()) {
+ PersistableBundle current = queue.remove();
+ for (String key : current.keySet()) {
+ enforceMaxStringLength(key, "key in " + argName);
+ Object value = current.get(key);
+ if (value instanceof String) {
+ enforceMaxStringLength((String) value, "string value in " + argName);
+ } else if (value instanceof String[]) {
+ for (String str : (String[]) value) {
+ enforceMaxStringLength(str, "string value in " + argName);
+ }
+ } else if (value instanceof PersistableBundle) {
+ queue.add((PersistableBundle) value);
+ }
+ }
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
index c78678431dac..86d4655e9d3a 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -228,6 +228,15 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
});
}
+ public void testShortcutIdTruncated() {
+ ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(),
+ "s".repeat(Short.MAX_VALUE)).build();
+
+ assertTrue(
+ "id must be truncated to MAX_ID_LENGTH",
+ si.getId().length() <= ShortcutInfo.MAX_ID_LENGTH);
+ }
+
public void testShortcutInfoParcel() {
setCaller(CALLING_PACKAGE_1, USER_10);
ShortcutInfo si = parceled(new ShortcutInfo.Builder(mClientContext)
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 874846d333cf..bd74c2ea8262 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -84,6 +84,7 @@ import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
+import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyLong;
@@ -128,6 +129,7 @@ import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Person;
import android.app.RemoteInput;
+import android.app.RemoteInputHistoryItem;
import android.app.StatsManager;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.usage.UsageStatsManagerInternal;
@@ -3847,6 +3849,30 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testSetListenerAccessForUser_grantWithNameTooLong_throws() {
+ UserHandle user = UserHandle.of(mContext.getUserId() + 10);
+ ComponentName c = new ComponentName("com.example.package",
+ com.google.common.base.Strings.repeat("Blah", 150));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mBinderService.setNotificationListenerAccessGrantedForUser(
+ c, user.getIdentifier(), /* enabled= */ true, true));
+ }
+
+ @Test
+ public void testSetListenerAccessForUser_revokeWithNameTooLong_okay() throws Exception {
+ UserHandle user = UserHandle.of(mContext.getUserId() + 10);
+ ComponentName c = new ComponentName("com.example.package",
+ com.google.common.base.Strings.repeat("Blah", 150));
+
+ mBinderService.setNotificationListenerAccessGrantedForUser(
+ c, user.getIdentifier(), /* enabled= */ false, true);
+
+ verify(mListeners).setPackageOrComponentEnabled(
+ c.flattenToString(), user.getIdentifier(), true, /* enabled= */ false, true);
+ }
+
+ @Test
public void testSetAssistantAccessForUser() throws Exception {
UserInfo ui = new UserInfo();
ui.id = mContext.getUserId() + 10;
@@ -5373,14 +5399,43 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void testVisitUris() throws Exception {
final Uri audioContents = Uri.parse("content://com.example/audio");
final Uri backgroundImage = Uri.parse("content://com.example/background");
+ final Icon smallIcon = Icon.createWithContentUri("content://media/small/icon");
+ final Icon largeIcon = Icon.createWithContentUri("content://media/large/icon");
+ final Icon personIcon1 = Icon.createWithContentUri("content://media/person1");
+ final Icon personIcon2 = Icon.createWithContentUri("content://media/person2");
+ final Icon personIcon3 = Icon.createWithContentUri("content://media/person3");
+ final Person person1 = new Person.Builder()
+ .setName("Messaging Person")
+ .setIcon(personIcon1)
+ .build();
+ final Person person2 = new Person.Builder()
+ .setName("People List Person 1")
+ .setIcon(personIcon2)
+ .build();
+ final Person person3 = new Person.Builder()
+ .setName("People List Person 2")
+ .setIcon(personIcon3)
+ .build();
+ final Uri historyUri1 = Uri.parse("content://com.example/history1");
+ final Uri historyUri2 = Uri.parse("content://com.example/history2");
+ final RemoteInputHistoryItem historyItem1 = new RemoteInputHistoryItem(null, historyUri1,
+ "a");
+ final RemoteInputHistoryItem historyItem2 = new RemoteInputHistoryItem(null, historyUri2,
+ "b");
Bundle extras = new Bundle();
extras.putParcelable(Notification.EXTRA_AUDIO_CONTENTS_URI, audioContents);
extras.putString(Notification.EXTRA_BACKGROUND_IMAGE_URI, backgroundImage.toString());
+ extras.putParcelable(Notification.EXTRA_MESSAGING_PERSON, person1);
+ extras.putParcelableArrayList(Notification.EXTRA_PEOPLE_LIST,
+ new ArrayList<>(Arrays.asList(person2, person3)));
+ extras.putParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
+ new RemoteInputHistoryItem[]{historyItem1, historyItem2});
Notification n = new Notification.Builder(mContext, "a")
.setContentTitle("notification with uris")
- .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setSmallIcon(smallIcon)
+ .setLargeIcon(largeIcon)
.addExtras(extras)
.build();
@@ -5388,6 +5443,33 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
n.visitUris(visitor);
verify(visitor, times(1)).accept(eq(audioContents));
verify(visitor, times(1)).accept(eq(backgroundImage));
+ verify(visitor, times(1)).accept(eq(smallIcon.getUri()));
+ verify(visitor, times(1)).accept(eq(largeIcon.getUri()));
+ verify(visitor, times(1)).accept(eq(personIcon1.getUri()));
+ verify(visitor, times(1)).accept(eq(personIcon2.getUri()));
+ verify(visitor, times(1)).accept(eq(personIcon3.getUri()));
+ verify(visitor, times(1)).accept(eq(historyUri1));
+ verify(visitor, times(1)).accept(eq(historyUri2));
+ }
+
+ @Test
+ public void testVisitUris_publicVersion() throws Exception {
+ final Icon smallIconPublic = Icon.createWithContentUri("content://media/small/icon");
+ final Icon largeIconPrivate = Icon.createWithContentUri("content://media/large/icon");
+
+ Notification publicVersion = new Notification.Builder(mContext, "a")
+ .setContentTitle("notification with uris")
+ .setSmallIcon(smallIconPublic)
+ .build();
+ Notification n = new Notification.Builder(mContext, "a")
+ .setLargeIcon(largeIconPrivate)
+ .setPublicVersion(publicVersion)
+ .build();
+
+ Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
+ n.visitUris(visitor);
+ verify(visitor, times(1)).accept(eq(smallIconPublic.getUri()));
+ verify(visitor, times(1)).accept(eq(largeIconPrivate.getUri()));
}
@Test
@@ -5409,6 +5491,93 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testVisitUris_messagingStyle() {
+ final Icon personIcon1 = Icon.createWithContentUri("content://media/person1");
+ final Icon personIcon2 = Icon.createWithContentUri("content://media/person2");
+ final Icon personIcon3 = Icon.createWithContentUri("content://media/person3");
+ final Person person1 = new Person.Builder()
+ .setName("Messaging Person 1")
+ .setIcon(personIcon1)
+ .build();
+ final Person person2 = new Person.Builder()
+ .setName("Messaging Person 2")
+ .setIcon(personIcon2)
+ .build();
+ final Person person3 = new Person.Builder()
+ .setName("Messaging Person 3")
+ .setIcon(personIcon3)
+ .build();
+ Icon shortcutIcon = Icon.createWithContentUri("content://media/shortcut");
+
+ Notification.Builder builder = new Notification.Builder(mContext, "a")
+ .setCategory(Notification.CATEGORY_MESSAGE)
+ .setContentTitle("new message!")
+ .setContentText("Conversation Notification")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon);
+ Notification.MessagingStyle.Message message1 = new Notification.MessagingStyle.Message(
+ "Marco?", System.currentTimeMillis(), person2);
+ Notification.MessagingStyle.Message message2 = new Notification.MessagingStyle.Message(
+ "Polo!", System.currentTimeMillis(), person3);
+ Notification.MessagingStyle style = new Notification.MessagingStyle(person1)
+ .addMessage(message1)
+ .addMessage(message2)
+ .setShortcutIcon(shortcutIcon);
+ builder.setStyle(style);
+ Notification n = builder.build();
+
+ Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
+ n.visitUris(visitor);
+
+ verify(visitor, times(1)).accept(eq(shortcutIcon.getUri()));
+ verify(visitor, times(1)).accept(eq(personIcon1.getUri()));
+ verify(visitor, times(1)).accept(eq(personIcon2.getUri()));
+ verify(visitor, times(1)).accept(eq(personIcon3.getUri()));
+ }
+
+ @Test
+ public void testVisitUris_callStyle() {
+ Icon personIcon = Icon.createWithContentUri("content://media/person");
+ Icon verificationIcon = Icon.createWithContentUri("content://media/verification");
+ Person callingPerson = new Person.Builder().setName("Someone")
+ .setIcon(personIcon)
+ .build();
+ PendingIntent hangUpIntent = PendingIntent.getActivity(mContext, 0, new Intent(),
+ PendingIntent.FLAG_IMMUTABLE);
+ Notification n = new Notification.Builder(mContext, "a")
+ .setStyle(Notification.CallStyle.forOngoingCall(callingPerson, hangUpIntent)
+ .setVerificationIcon(verificationIcon))
+ .setContentTitle("Calling...")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .build();
+
+ Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
+ n.visitUris(visitor);
+
+ verify(visitor, times(1)).accept(eq(personIcon.getUri()));
+ verify(visitor, times(1)).accept(eq(verificationIcon.getUri()));
+ }
+
+ @Test
+ public void testVisitUris_wearableExtender() {
+ Icon actionIcon = Icon.createWithContentUri("content://media/action");
+ Icon wearActionIcon = Icon.createWithContentUri("content://media/wearAction");
+ PendingIntent intent = PendingIntent.getActivity(mContext, 0, new Intent(),
+ PendingIntent.FLAG_IMMUTABLE);
+ Notification n = new Notification.Builder(mContext, "a")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .addAction(new Notification.Action.Builder(actionIcon, "Hey!", intent).build())
+ .extend(new Notification.WearableExtender().addAction(
+ new Notification.Action.Builder(wearActionIcon, "Wear!", intent).build()))
+ .build();
+
+ Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
+ n.visitUris(visitor);
+
+ verify(visitor).accept(eq(actionIcon.getUri()));
+ verify(visitor).accept(eq(wearActionIcon.getUri()));
+ }
+
+ @Test
public void testSetNotificationPolicy_preP_setOldFields() {
ZenModeHelper mZenModeHelper = mock(ZenModeHelper.class);
mService.mZenModeHelper = mZenModeHelper;
diff --git a/telecomm/java/android/telecom/ParcelableConference.java b/telecomm/java/android/telecom/ParcelableConference.java
index e57c833e930e..6dcfa6d56ef3 100644
--- a/telecomm/java/android/telecom/ParcelableConference.java
+++ b/telecomm/java/android/telecom/ParcelableConference.java
@@ -21,12 +21,12 @@ import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.telecom.IVideoProvider;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import com.android.internal.telecom.IVideoProvider;
-
/**
* A parcelable representation of a conference connection.
* @hide
@@ -287,6 +287,14 @@ public final class ParcelableConference implements Parcelable {
return mCallDirection;
}
+ public String getCallerDisplayName() {
+ return mCallerDisplayName;
+ }
+
+ public int getCallerDisplayNamePresentation() {
+ return mCallerDisplayNamePresentation;
+ }
+
public static final @android.annotation.NonNull Parcelable.Creator<ParcelableConference> CREATOR =
new Parcelable.Creator<ParcelableConference> () {
@Override
diff --git a/telecomm/java/android/telecom/StatusHints.java b/telecomm/java/android/telecom/StatusHints.java
index 2faecc2e3468..5f0c8d729e74 100644
--- a/telecomm/java/android/telecom/StatusHints.java
+++ b/telecomm/java/android/telecom/StatusHints.java
@@ -16,14 +16,19 @@
package android.telecom;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
+import android.os.Binder;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
+
+import com.android.internal.annotations.VisibleForTesting;
import java.util.Objects;
@@ -33,7 +38,7 @@ import java.util.Objects;
public final class StatusHints implements Parcelable {
private final CharSequence mLabel;
- private final Icon mIcon;
+ private Icon mIcon;
private final Bundle mExtras;
/**
@@ -48,11 +53,31 @@ public final class StatusHints implements Parcelable {
public StatusHints(CharSequence label, Icon icon, Bundle extras) {
mLabel = label;
- mIcon = icon;
+ mIcon = validateAccountIconUserBoundary(icon, Binder.getCallingUserHandle());
mExtras = extras;
}
/**
+ * @param icon
+ * @hide
+ */
+ @VisibleForTesting
+ public StatusHints(@Nullable Icon icon) {
+ mLabel = null;
+ mExtras = null;
+ mIcon = icon;
+ }
+
+ /**
+ *
+ * @param icon
+ * @hide
+ */
+ public void setIcon(@Nullable Icon icon) {
+ mIcon = icon;
+ }
+
+ /**
* @return A package used to load the icon.
*
* @hide
@@ -112,6 +137,30 @@ public final class StatusHints implements Parcelable {
return 0;
}
+ /**
+ * Validates the StatusHints image icon to see if it's not in the calling user space.
+ * Invalidates the icon if so, otherwise returns back the original icon.
+ *
+ * @param icon
+ * @return icon (validated)
+ * @hide
+ */
+ public static Icon validateAccountIconUserBoundary(Icon icon, UserHandle callingUserHandle) {
+ // Refer to Icon#getUriString for context. The URI string is invalid for icons of
+ // 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;
+ }
+ }
+ return icon;
+ }
+
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeCharSequence(mLabel);