diff options
author | Matt Lee <matthewhlee@google.com> | 2023-08-11 14:59:02 -0700 |
---|---|---|
committer | Matt Lee <matthewhlee@google.com> | 2023-08-11 15:51:05 -0700 |
commit | da4e6b0612a340b211f987fecb5db178aff14400 (patch) | |
tree | 02017cefc380f7f7fcb46a0a9bafc624fa8faf05 | |
parent | 9ac2cdc622c906dfc9447c0ab63b730658f8a15a (diff) | |
parent | 502a47c26e38f898478c569ccd422f02f8e68a48 (diff) |
Merge t-mpr-2023-08
Change-Id: Id53ca628b754f072352fa9323ceec5c249b0267f
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); |