From 2539e724119f401973da60a0ab204e2e9b23c6b2 Mon Sep 17 00:00:00 2001 From: Yi Jiang Date: Thu, 20 Apr 2023 14:50:43 -0700 Subject: Gates RotationResolverService with a config flag Bug: 266824653 Test: atest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:8dd4fb802129344cd93314421552a72f37ab34ee) Merged-In: I7bc69ddf1c1742bd800eea4068ce268dfd96d0e3 Change-Id: I7bc69ddf1c1742bd800eea4068ce268dfd96d0e3 --- core/res/res/values/config.xml | 3 +++ core/res/res/values/symbols.xml | 1 + services/core/java/com/android/server/wm/DisplayRotation.java | 6 +++++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 514083cefca9..9891409a08bb 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -572,6 +572,9 @@ docked if the dock is configured to enable the accelerometer. --> true + + true + false diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 17a8a76bd590..01c6b5509cb7 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1754,6 +1754,7 @@ + diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index 0a1e29ace045..2cff3545cc1c 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -123,6 +123,7 @@ public class DisplayRotation { public final boolean isDefaultDisplay; private final boolean mSupportAutoRotation; + private final boolean mAllowRotationResolver; private final int mLidOpenRotation; private final int mCarDockRotation; private final int mDeskDockRotation; @@ -265,6 +266,8 @@ public class DisplayRotation { mSupportAutoRotation = mContext.getResources().getBoolean(R.bool.config_supportAutoRotation); + mAllowRotationResolver = + mContext.getResources().getBoolean(R.bool.config_allowRotationResolver); mLidOpenRotation = readRotation(R.integer.config_lidOpenRotation); mCarDockRotation = readRotation(R.integer.config_carDockRotation); mDeskDockRotation = readRotation(R.integer.config_deskDockRotation); @@ -1992,7 +1995,8 @@ public class DisplayRotation { @Override public boolean isRotationResolverEnabled() { - return mUserRotationMode == WindowManagerPolicy.USER_ROTATION_FREE + return mAllowRotationResolver + && mUserRotationMode == WindowManagerPolicy.USER_ROTATION_FREE && mCameraRotationMode == CAMERA_ROTATION_ENABLED && !mService.mPowerManager.isPowerSaveMode(); } -- cgit v1.2.3 From 0aa5db8bc51433177da5448f51d3a3810ec2e04a Mon Sep 17 00:00:00 2001 From: Tony Huang Date: Tue, 11 Apr 2023 08:55:45 +0000 Subject: Update the timing of clearing SplitRequest We clean splitRequest after enter split and use it as a flag to do some function such as active split by child adjacent task if split invisible. However, in some cases, such as a task start another new task and finish it immediately. In this case, split active finished and split request reset but split still didn't invisible and new child task appeared, and it trigger function mentioned above then cause bug. To avoid such case, we should update the timing of clearing SplitRequest to cover trampoline tasks launch so it won't evict child tasks unexpectedly while entering split. Fix: 277556204 Test: manual Test: pass existing tests (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:7ae38cb0c1c6d1da6b2c45e6b809199ca8900235) Merged-In: Ib3240c0b5037a3a20692c83e2daa8cd858125ca7 Change-Id: Ib3240c0b5037a3a20692c83e2daa8cd858125ca7 --- .../android/wm/shell/splitscreen/StageCoordinator.java | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 77939c7c6964..65be1b0550d6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -1034,7 +1034,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private void onRemoteAnimationFinishedOrCancelled(WindowContainerTransaction evictWct) { mIsDividerRemoteAnimating = false; mShouldUpdateRecents = true; - mSplitRequest = null; + clearRequestIfPresented(); // If any stage has no child after animation finished, it means that split will display // nothing, such status will happen if task and intent is same app but not support // multi-instance, we should exit split and expand that app as full screen. @@ -1054,7 +1054,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private void onRemoteAnimationFinished(RemoteAnimationTarget[] apps) { mIsDividerRemoteAnimating = false; mShouldUpdateRecents = true; - mSplitRequest = null; + clearRequestIfPresented(); // If any stage has no child after finished animation, that side of the split will display // nothing. This might happen if starting the same app on the both sides while not // supporting multi-instance. Exit the split screen and expand that app to full screen. @@ -1320,6 +1320,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, }); mShouldUpdateRecents = false; mIsDividerRemoteAnimating = false; + mSplitRequest = null; mSplitLayout.getInvisibleBounds(mTempRect1); if (childrenToTop == null || childrenToTop.getTopVisibleChildTaskId() == INVALID_TASK_ID) { @@ -1412,6 +1413,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } + private void clearRequestIfPresented() { + if (mSideStageListener.mVisible && mSideStageListener.mHasChildren + && mMainStageListener.mVisible && mSideStageListener.mHasChildren) { + mSplitRequest = null; + } + } + /** * Returns whether the split pair in the recent tasks list should be broken. */ @@ -1776,6 +1784,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, true /* setReparentLeafTaskIfRelaunch */); setRootForceTranslucent(true, wct); } else { + clearRequestIfPresented(); wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, false /* setReparentLeafTaskIfRelaunch */); setRootForceTranslucent(false, wct); @@ -1926,7 +1935,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) { mShouldUpdateRecents = true; - mSplitRequest = null; + clearRequestIfPresented(); updateRecentTasksSplitPair(); if (!mLogger.hasStartedSession()) { @@ -2565,6 +2574,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } }); mShouldUpdateRecents = false; + mSplitRequest = null; // Update local states. setSplitsVisible(false); -- cgit v1.2.3 From a93851004aa808cb973644cd529ec00081de8459 Mon Sep 17 00:00:00 2001 From: Tony Huang Date: Mon, 17 Apr 2023 18:00:19 +0800 Subject: Fix split cannot active if app trampoline launch new task This reverts commit 2a47e6a6ed4d5fdc812e9c1ce3f60b5ff226f31c and commit 76ab53a384b16fbf07ffd4c11952ef687bd9bcc3. Both CL cause regression which lead to users cannot active split by some apps use trampoline launch new task on launcher icon. So fix it by revert both CLs. Other changes are also for fixing this case. There is a timing issue and we cannot ensure the old trampoline still remain under split root when we get remote animation callback. Fix it by only checking any children tasks are under split root or not. Bug: 278172065 Test: manual Test: pass existing tests (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:6e29d4ddb8ab2cb9b66539c0b9bc3e15efe8b885) Merged-In: Ibd6d9a8377e4b5327a473f8b9adf4e569e826172 Change-Id: Ibd6d9a8377e4b5327a473f8b9adf4e569e826172 --- .../android/wm/shell/common/split/SplitLayout.java | 4 -- .../wm/shell/splitscreen/StageCoordinator.java | 44 +++------------------- .../wm/shell/splitscreen/StageTaskListener.java | 19 ++++------ .../shell/splitscreen/StageTaskListenerTests.java | 6 +++ 4 files changed, 19 insertions(+), 54 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index b4acd6046182..ffc56b6f6106 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -727,10 +727,6 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange getRefBounds2(mTempRect); t.setPosition(leash2, mTempRect.left, mTempRect.top) .setWindowCrop(leash2, mTempRect.width(), mTempRect.height()); - // Make right or bottom side surface always higher than left or top side to avoid weird - // animation when dismiss split. e.g. App surface fling above on decor surface. - t.setLayer(leash1, 1); - t.setLayer(leash2, 2); if (mImePositionProcessor.adjustSurfaceLayoutForIme( t, dividerLeash, leash1, leash2, dimLayer1, dimLayer2)) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 65be1b0550d6..fbeff255b45d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -468,26 +468,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, final IRemoteAnimationFinishedCallback finishedCallback) { - boolean openingToSide = false; - if (apps != null) { - for (int i = 0; i < apps.length; ++i) { - if (apps[i].mode == MODE_OPENING - && mSideStage.containsTask(apps[i].taskId)) { - openingToSide = true; - break; - } - } - } else if (mSideStage.getChildCount() != 0) { - // There are chances the entering app transition got canceled by performing - // rotation transition. Checks if there is any child task existed in split - // screen before fallback to cancel entering flow. - openingToSide = true; - } - - if (isEnteringSplit && !openingToSide) { + if (isEnteringSplit && mSideStage.getChildCount() == 0) { mMainExecutor.execute(() -> exitSplitScreen( - mSideStage.getChildCount() == 0 ? mMainStage : mSideStage, - EXIT_REASON_UNKNOWN)); + null /* childrenToTop */, EXIT_REASON_UNKNOWN)); + mSplitUnsupportedToast.show(); } if (finishedCallback != null) { @@ -572,26 +556,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback, SurfaceControl.Transaction t) { - boolean openingToSide = false; - if (apps != null) { - for (int i = 0; i < apps.length; ++i) { - if (apps[i].mode == MODE_OPENING - && mSideStage.containsTask(apps[i].taskId)) { - openingToSide = true; - break; - } - } - } else if (mSideStage.getChildCount() != 0) { - // There are chances the entering app transition got canceled by performing - // rotation transition. Checks if there is any child task existed in split - // screen before fallback to cancel entering flow. - openingToSide = true; - } - - if (isEnteringSplit && !openingToSide && apps != null) { + if (isEnteringSplit && mSideStage.getChildCount() == 0) { mMainExecutor.execute(() -> exitSplitScreen( - mSideStage.getChildCount() == 0 ? mMainStage : mSideStage, - EXIT_REASON_UNKNOWN)); + null /* childrenToTop */, EXIT_REASON_UNKNOWN)); + mSplitUnsupportedToast.show(); } if (apps != null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index ead0bcd15c73..a841b7f96d3c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -220,20 +220,12 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { mCallbacks.onNoLongerSupportMultiWindow(); return; } - if (taskInfo.topActivity == null && mChildrenTaskInfo.contains(taskInfo.taskId) - && mChildrenTaskInfo.get(taskInfo.taskId).topActivity != null) { - // If top activity become null, it means the task is about to vanish, we use this - // signal to remove it from children list earlier for smooth dismiss transition. - mChildrenTaskInfo.remove(taskInfo.taskId); - mChildrenLeashes.remove(taskInfo.taskId); - } else { - mChildrenTaskInfo.put(taskInfo.taskId, taskInfo); - } + mChildrenTaskInfo.put(taskInfo.taskId, taskInfo); mCallbacks.onChildTaskStatusChanged(taskInfo.taskId, true /* present */, taskInfo.isVisible); - if (!ENABLE_SHELL_TRANSITIONS && mChildrenLeashes.contains(taskInfo.taskId)) { - updateChildTaskSurface(taskInfo, mChildrenLeashes.get(taskInfo.taskId), - false /* firstAppeared */); + if (!ENABLE_SHELL_TRANSITIONS) { + updateChildTaskSurface( + taskInfo, mChildrenLeashes.get(taskInfo.taskId), false /* firstAppeared */); } } else { throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo @@ -267,6 +259,9 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { return; } sendStatusChanged(); + } else { + throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo + + "\n mRootTaskInfo: " + mRootTaskInfo); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java index b31e20fcc438..5ee8bf3006a3 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java @@ -126,6 +126,12 @@ public final class StageTaskListenerTests extends ShellTestCase { verify(mCallbacks).onStatusChanged(eq(mRootTask.isVisible), eq(true)); } + @Test(expected = IllegalArgumentException.class) + public void testUnknownTaskVanished() { + final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build(); + mStageTaskListener.onTaskVanished(task); + } + @Test public void testTaskVanished() { // With shell transitions, the transition manages status changes, so skip this test. -- cgit v1.2.3 From 7885cd9a195e785263c9f7bff62f4209706b78a8 Mon Sep 17 00:00:00 2001 From: wilsonshih Date: Thu, 27 Apr 2023 07:46:48 +0000 Subject: [RESTRICT AUTOMERGE] Always set last report configuration for starting window. The windows of an activity can be request to resize if the last report configration didn't set during relayout window before activity request visible. So there should excludes starting window since the starting window won't restart activity from configuration change. Bug: 276356396 Test: manual do quick switch on large screen device. (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:b6b5861513f372b32982df55052241ad3883a13a) Merged-In: Ie12843d00c5336e5a472624205e4e4b9ec881cc5 Change-Id: Ie12843d00c5336e5a472624205e4e4b9ec881cc5 --- services/core/java/com/android/server/wm/WindowState.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 95fea0ee22f5..399156090594 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -3867,7 +3867,11 @@ class WindowState extends WindowContainer implements WindowManagerP // configuration update when the window has requested to be hidden. Doing so can lead to // the client erroneously accepting a configuration that would have otherwise caused an // activity restart. We instead hand back the last reported {@link MergedConfiguration}. + // Also note since starting window isn't a window of activity, it won't make activity + // restart, so here should allow starting window to set the last reported configuration + // during relayout, which could happen before activity request visible. if (useLatestConfig || (relayoutVisible && (mActivityRecord == null + || mAttrs.type == TYPE_APPLICATION_STARTING || mActivityRecord.isVisibleRequested()))) { final Configuration globalConfig = getProcessGlobalConfiguration(); final Configuration overrideConfig = getMergedOverrideConfiguration(); -- cgit v1.2.3 From eef3898084508a90ca7b27fc0c0bf591d1c1271e Mon Sep 17 00:00:00 2001 From: Louis Chang Date: Fri, 5 May 2023 10:24:40 +0000 Subject: Unfreeze the surface when the transition is done The surface was not unfreeze if the WindowContainer is closing and changing after [1]. Always check and unfreeze the surface if needed when the transition is done. [1] d19ad2aa09fd27ec11e626ab239babb929c32fd6 Bug: 280754787 Test: click web link on chat while having a Meet call (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:f3a5c5892402916ca861c41319f21acb83bd1f83) Merged-In: I98618477a828eb72b2173af6988e804471139e81 Change-Id: I98618477a828eb72b2173af6988e804471139e81 --- services/core/java/com/android/server/wm/WindowContainer.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 02d3af6da326..6ec08dc08c51 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -1396,6 +1396,9 @@ class WindowContainer extends ConfigurationContainer< } void onAppTransitionDone() { + if (mSurfaceFreezer.hasLeash()) { + mSurfaceFreezer.unfreeze(getSyncTransaction()); + } for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer wc = mChildren.get(i); wc.onAppTransitionDone(); -- cgit v1.2.3 From 3642ff875847de2f89a9982190d0803d87a6f813 Mon Sep 17 00:00:00 2001 From: Louis Chang Date: Tue, 7 Mar 2023 10:35:42 +0000 Subject: Skip letterboxing if the activity below is embedded Embedded activities are resize-able and won't be letterboxed Bug: 270681405 fixes: 280898266 Test: SizeCompatTests Test: manual - repro step in bug (cherry picked from commit b828087bbc1bfead31a9a4e2f54b162d04a10172) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:ad7d89c22bf53fbcfd1f8d5a1ae2c611c376e9b3) Merged-In: Ic7b1c4b40960fd04de9efbf4f6d7abee45c93025 Change-Id: Ic7b1c4b40960fd04de9efbf4f6d7abee45c93025 --- .../com/android/server/wm/LetterboxUiController.java | 4 ++-- .../src/com/android/server/wm/SizeCompatTests.java | 20 +++++++++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 684e787ec4cf..86bc6eeecf1f 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -1595,9 +1595,9 @@ final class LetterboxUiController { FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE /* callback */, mActivityRecord /* boundary */, false /* includeBoundary */, true /* traverseTopToBottom */); - if (firstOpaqueActivityBeneath == null) { + if (firstOpaqueActivityBeneath == null || firstOpaqueActivityBeneath.isEmbedded()) { // We skip letterboxing if the translucent activity doesn't have any opaque - // activities beneath + // activities beneath or the activity below is embedded which never has letterbox. return; } inheritConfiguration(firstOpaqueActivityBeneath); diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index d93871e59b37..e5ad01a5b70c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -475,7 +475,25 @@ public class SizeCompatTests extends WindowTestsBase { } @Test - public void testTranslucentActivitiesDontGoInSizeCompatMode() { + public void testNotApplyStrategyToTranslucentActivitiesOverEmbeddedActivities() { + mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); + setUpDisplaySizeWithApp(2000, 1000); + mActivity.info.screenOrientation = SCREEN_ORIENTATION_PORTRAIT; + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + // Mock the activity as embedded without additional TaskFragment layer in the task for + // simplicity. + doReturn(true).when(mActivity).isEmbedded(); + // Translucent Activity + final ActivityRecord translucentActivity = new ActivityBuilder(mAtm).build(); + doReturn(false).when(translucentActivity).matchParentBounds(); + doReturn(false).when(translucentActivity).fillsParent(); + mTask.addChild(translucentActivity); + // Check the strategy has not being applied + assertFalse(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior()); + } + + @Test + public void testTranslucentActivitiesDontGoInSizeCompactMode() { mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); setUpDisplaySizeWithApp(2800, 1400); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); -- cgit v1.2.3 From 1ccf8fc695e37b0ee32a715a7b5b8757282ced0f Mon Sep 17 00:00:00 2001 From: John Reck Date: Thu, 4 May 2023 11:29:45 -0400 Subject: Invalidate buffers on transform change Fixes: 271419600 Test: repro steps on bug (cherry picked from commit 61c64472417a272ec2aa2fd38e0d1874dbf16319) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:5545b1cc36fd47bb33d87875b451f9f5c870d8bf) Merged-In: Ib4eef40a0f59512c669b069532e55d36293f9e1c Change-Id: Ib4eef40a0f59512c669b069532e55d36293f9e1c --- libs/hwui/renderthread/VulkanSurface.cpp | 12 ++++++++++++ libs/hwui/renderthread/VulkanSurface.h | 1 + 2 files changed, 13 insertions(+) diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp index 666f32939206..cbee104aa3b0 100644 --- a/libs/hwui/renderthread/VulkanSurface.cpp +++ b/libs/hwui/renderthread/VulkanSurface.cpp @@ -368,6 +368,14 @@ void VulkanSurface::releaseBuffers() { } } +void VulkanSurface::invalidateBuffers() { + for (uint32_t i = 0; i < mWindowInfo.bufferCount; i++) { + VulkanSurface::NativeBufferInfo& bufferInfo = mNativeBuffers[i]; + bufferInfo.hasValidContents = false; + bufferInfo.lastPresentedCount = 0; + } +} + VulkanSurface::NativeBufferInfo* VulkanSurface::dequeueNativeBuffer() { // Set the mCurrentBufferInfo to invalid in case of error and only reset it to the correct // value at the end of the function if everything dequeued correctly. @@ -400,6 +408,10 @@ VulkanSurface::NativeBufferInfo* VulkanSurface::dequeueNativeBuffer() { // new NativeBufferInfo storage will be populated lazily as we dequeue each new buffer. mWindowInfo.actualSize = actualSize; releaseBuffers(); + } else { + // A change in transform means we need to repaint the entire buffer area as the damage + // rects have just moved about. + invalidateBuffers(); } if (transformHint != mWindowInfo.transform) { diff --git a/libs/hwui/renderthread/VulkanSurface.h b/libs/hwui/renderthread/VulkanSurface.h index b8ccf7810b5d..ff328b25d195 100644 --- a/libs/hwui/renderthread/VulkanSurface.h +++ b/libs/hwui/renderthread/VulkanSurface.h @@ -113,6 +113,7 @@ private: WindowInfo* outWindowInfo); static bool UpdateWindow(ANativeWindow* window, const WindowInfo& windowInfo); void releaseBuffers(); + void invalidateBuffers(); // TODO: This number comes from ui/BufferQueueDefs. We're not pulling the // header in so that we don't need to depend on libui, but we should share -- cgit v1.2.3 From c98b289a6c1ba635d573bdfd3c45a9ddeca199e6 Mon Sep 17 00:00:00 2001 From: Massimo Carli Date: Wed, 26 Apr 2023 11:38:19 +0000 Subject: Improve first opaque activity candidate detection To catch the right candidate as first opaque activity beneath a translucent one and handle normal permission dialog behaviour and smart links. Fixes: 278661668 Test: Run `atest WmTests:SizeCompatTests` (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:bcf680d064ea8921091bfc4620b6958adf79e8a2) Merged-In: I62e829555c43136080ee4909f7dcf8c388165e9f Change-Id: I62e829555c43136080ee4909f7dcf8c388165e9f --- .../java/com/android/server/wm/ActivityRecord.java | 2 +- .../android/server/wm/LetterboxUiController.java | 81 +++++++----- .../src/com/android/server/wm/SizeCompatTests.java | 142 ++++++++++++++------- 3 files changed, 147 insertions(+), 78 deletions(-) diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index d872ada1fd0c..a6236251635a 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1612,7 +1612,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A newParent.setResumedActivity(this, "onParentChanged"); mImeInsetsFrozenUntilStartInput = false; } - mLetterboxUiController.onActivityParentChanged(newParent); + mLetterboxUiController.updateInheritedLetterbox(); } if (rootTask != null && rootTask.topRunningActivity() == this) { diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 86bc6eeecf1f..d69542c42548 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -112,6 +112,8 @@ import com.android.internal.statusbar.LetterboxDetails; import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; import java.util.function.BooleanSupplier; import java.util.function.Consumer; @@ -126,8 +128,7 @@ import java.util.function.Predicate; final class LetterboxUiController { private static final Predicate FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE = - activityRecord -> activityRecord.fillsParent() && !activityRecord.isFinishing() - && activityRecord.nowVisible; + activityRecord -> activityRecord.fillsParent() && !activityRecord.isFinishing(); private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxUiController" : TAG_ATM; @@ -185,6 +186,10 @@ final class LetterboxUiController { // Corresponds to OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS private final boolean mIsOverrideEnableCompatFakeFocusEnabled; + // The list of observers for the destroy event of candidate opaque activities + // when dealing with translucent activities. + private final List mDestroyListeners = new ArrayList<>(); + @Nullable private final Boolean mBooleanPropertyAllowOrientationOverride; @Nullable @@ -198,6 +203,10 @@ final class LetterboxUiController { @Nullable private WindowContainerListener mLetterboxConfigListener; + @Nullable + @VisibleForTesting + ActivityRecord mFirstOpaqueActivityBeneath; + private boolean mShowWallpaperForLetterboxBackground; // In case of transparent activities we might need to access the aspectRatio of the @@ -361,6 +370,10 @@ final class LetterboxUiController { mLetterbox.destroy(); mLetterbox = null; } + for (int i = mDestroyListeners.size() - 1; i >= 0; i--) { + mDestroyListeners.get(i).updateInheritedLetterbox(); + } + mDestroyListeners.clear(); if (mLetterboxConfigListener != null) { mLetterboxConfigListener.onRemoved(); mLetterboxConfigListener = null; @@ -1577,7 +1590,11 @@ final class LetterboxUiController { * first opaque activity beneath. * @param parent The parent container. */ - void onActivityParentChanged(WindowContainer parent) { + void updateInheritedLetterbox() { + final WindowContainer parent = mActivityRecord.getParent(); + if (parent == null) { + return; + } if (!mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) { return; } @@ -1587,27 +1604,28 @@ final class LetterboxUiController { } // In case mActivityRecord.hasCompatDisplayInsetsWithoutOverride() we don't apply the // opaque activity constraints because we're expecting the activity is already letterboxed. - if (mActivityRecord.getTask() == null || mActivityRecord.fillsParent() - || mActivityRecord.hasCompatDisplayInsetsWithoutInheritance()) { - return; - } - final ActivityRecord firstOpaqueActivityBeneath = mActivityRecord.getTask().getActivity( + mFirstOpaqueActivityBeneath = mActivityRecord.getTask().getActivity( FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE /* callback */, mActivityRecord /* boundary */, false /* includeBoundary */, true /* traverseTopToBottom */); - if (firstOpaqueActivityBeneath == null || firstOpaqueActivityBeneath.isEmbedded()) { + if (mFirstOpaqueActivityBeneath == null || mFirstOpaqueActivityBeneath.isEmbedded()) { // We skip letterboxing if the translucent activity doesn't have any opaque // activities beneath or the activity below is embedded which never has letterbox. + mActivityRecord.recomputeConfiguration(); return; } - inheritConfiguration(firstOpaqueActivityBeneath); + if (mActivityRecord.getTask() == null || mActivityRecord.fillsParent() + || mActivityRecord.hasCompatDisplayInsetsWithoutInheritance()) { + return; + } + mFirstOpaqueActivityBeneath.mLetterboxUiController.mDestroyListeners.add(this); + inheritConfiguration(mFirstOpaqueActivityBeneath); mLetterboxConfigListener = WindowContainer.overrideConfigurationPropagation( - mActivityRecord, firstOpaqueActivityBeneath, - (opaqueConfig, transparentConfig) -> { - final Configuration mutatedConfiguration = - fromOriginalTranslucentConfig(transparentConfig); + mActivityRecord, mFirstOpaqueActivityBeneath, + (opaqueConfig, transparentOverrideConfig) -> { + resetTranslucentOverrideConfig(transparentOverrideConfig); final Rect parentBounds = parent.getWindowConfiguration().getBounds(); - final Rect bounds = mutatedConfiguration.windowConfiguration.getBounds(); + final Rect bounds = transparentOverrideConfig.windowConfiguration.getBounds(); final Rect letterboxBounds = opaqueConfig.windowConfiguration.getBounds(); // We cannot use letterboxBounds directly here because the position relies on // letterboxing. Using letterboxBounds directly, would produce a double offset. @@ -1616,9 +1634,9 @@ final class LetterboxUiController { parentBounds.top + letterboxBounds.height()); // We need to initialize appBounds to avoid NPE. The actual value will // be set ahead when resolving the Configuration for the activity. - mutatedConfiguration.windowConfiguration.setAppBounds(new Rect()); - inheritConfiguration(firstOpaqueActivityBeneath); - return mutatedConfiguration; + transparentOverrideConfig.windowConfiguration.setAppBounds(new Rect()); + inheritConfiguration(mFirstOpaqueActivityBeneath); + return transparentOverrideConfig; }); } @@ -1691,26 +1709,19 @@ final class LetterboxUiController { if (!hasInheritedLetterboxBehavior() || mActivityRecord.getTask() == null) { return Optional.empty(); } - return Optional.ofNullable(mActivityRecord.getTask().getActivity( - FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE /* callback */, - mActivityRecord /* boundary */, false /* includeBoundary */, - true /* traverseTopToBottom */)); + return Optional.ofNullable(mFirstOpaqueActivityBeneath); } - // When overriding translucent activities configuration we need to keep some of the - // original properties - private Configuration fromOriginalTranslucentConfig(Configuration translucentConfig) { - final Configuration configuration = new Configuration(translucentConfig); + /** Resets the screen size related fields so they can be resolved by requested bounds later. */ + private static void resetTranslucentOverrideConfig(Configuration config) { // The values for the following properties will be defined during the configuration // resolution in {@link ActivityRecord#resolveOverrideConfiguration} using the // properties inherited from the first not finishing opaque activity beneath. - configuration.orientation = ORIENTATION_UNDEFINED; - configuration.screenWidthDp = configuration.compatScreenWidthDp = SCREEN_WIDTH_DP_UNDEFINED; - configuration.screenHeightDp = - configuration.compatScreenHeightDp = SCREEN_HEIGHT_DP_UNDEFINED; - configuration.smallestScreenWidthDp = - configuration.compatSmallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; - return configuration; + config.orientation = ORIENTATION_UNDEFINED; + config.screenWidthDp = config.compatScreenWidthDp = SCREEN_WIDTH_DP_UNDEFINED; + config.screenHeightDp = config.compatScreenHeightDp = SCREEN_HEIGHT_DP_UNDEFINED; + config.smallestScreenWidthDp = config.compatSmallestScreenWidthDp = + SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; } private void inheritConfiguration(ActivityRecord firstOpaque) { @@ -1729,6 +1740,10 @@ final class LetterboxUiController { } private void clearInheritedConfig() { + if (mFirstOpaqueActivityBeneath != null) { + mFirstOpaqueActivityBeneath.mLetterboxUiController.mDestroyListeners.remove(this); + } + mFirstOpaqueActivityBeneath = null; mLetterboxConfigListener = null; mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO; mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO; diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index e5ad01a5b70c..637ec7e92a7d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -174,44 +174,6 @@ public class SizeCompatTests extends WindowTestsBase { setUpApp(builder.build()); } - @Test - public void testActivityInHistoryAndNotVisibleIsNotUsedAsOpaqueForTranslucentActivities() { - mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); - setUpDisplaySizeWithApp(2000, 1000); - prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.nowVisible = false; - // Translucent Activity - final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) - .setLaunchedFromUid(mActivity.getUid()) - .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) - .build(); - doReturn(false).when(translucentActivity).fillsParent(); - - mTask.addChild(translucentActivity); - - assertFalse(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior()); - } - - @Test - public void testActivityInHistoryAndVisibleIsUsedAsOpaqueForTranslucentActivities() { - mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); - setUpDisplaySizeWithApp(2000, 1000); - prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.nowVisible = true; - // Translucent Activity - final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) - .setLaunchedFromUid(mActivity.getUid()) - .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) - .build(); - doReturn(false).when(translucentActivity).fillsParent(); - - mTask.addChild(translucentActivity); - - assertTrue(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior()); - } - @Test public void testCleanLetterboxConfigListenerWhenTranslucentIsDestroyed() { mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); @@ -236,7 +198,6 @@ public class SizeCompatTests extends WindowTestsBase { public void testHorizontalReachabilityEnabledForTranslucentActivities() { setUpDisplaySizeWithApp(2500, 1000); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.nowVisible = true; final LetterboxConfiguration config = mWm.mLetterboxConfiguration; config.setTranslucentLetterboxingOverrideEnabled(true); config.setLetterboxHorizontalPositionMultiplier(0.5f); @@ -312,7 +273,6 @@ public class SizeCompatTests extends WindowTestsBase { public void testVerticalReachabilityEnabledForTranslucentActivities() { setUpDisplaySizeWithApp(1000, 2500); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.nowVisible = true; final LetterboxConfiguration config = mWm.mLetterboxConfiguration; config.setTranslucentLetterboxingOverrideEnabled(true); config.setLetterboxVerticalPositionMultiplier(0.5f); @@ -384,6 +344,104 @@ public class SizeCompatTests extends WindowTestsBase { checkIsCentered.run(); } + @Test + public void testApplyStrategyAgainWhenOpaqueIsDestroyed() { + mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); + setUpDisplaySizeWithApp(2000, 1000); + prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + // Launch another opaque activity + final ActivityRecord opaqueActivity = new ActivityBuilder(mAtm) + .setLaunchedFromUid(mActivity.getUid()) + .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) + .build(); + mTask.addChild(opaqueActivity); + // Transparent activity strategy not applied + assertFalse(opaqueActivity.mLetterboxUiController.hasInheritedLetterboxBehavior()); + + // Launch translucent Activity + final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) + .setLaunchedFromUid(mActivity.getUid()) + .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) + .build(); + doReturn(false).when(translucentActivity).fillsParent(); + mTask.addChild(translucentActivity); + // Transparent strategy applied + assertTrue(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior()); + + spyOn(translucentActivity.mLetterboxUiController); + clearInvocations(translucentActivity.mLetterboxUiController); + + // We destroy the first opaque activity + opaqueActivity.setState(DESTROYED, "testing"); + opaqueActivity.removeImmediately(); + + // Check that updateInheritedLetterbox() is invoked again + verify(translucentActivity.mLetterboxUiController).updateInheritedLetterbox(); + } + + @Test + public void testResetOpaqueReferenceWhenOpaqueIsDestroyed() { + mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); + setUpDisplaySizeWithApp(2000, 1000); + prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + + // Launch translucent Activity + final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) + .setLaunchedFromUid(mActivity.getUid()) + .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) + .build(); + doReturn(false).when(translucentActivity).fillsParent(); + mTask.addChild(translucentActivity); + // Transparent strategy applied + assertTrue(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior()); + assertNotNull(translucentActivity.mLetterboxUiController.mFirstOpaqueActivityBeneath); + + spyOn(translucentActivity.mLetterboxUiController); + clearInvocations(translucentActivity.mLetterboxUiController); + + // We destroy the first opaque activity + mActivity.setState(DESTROYED, "testing"); + mActivity.removeImmediately(); + + // Check that updateInheritedLetterbox() is invoked again + verify(translucentActivity.mLetterboxUiController).updateInheritedLetterbox(); + assertNull(translucentActivity.mLetterboxUiController.mFirstOpaqueActivityBeneath); + } + + @Test + public void testNotApplyStrategyAgainWhenOpaqueIsNotDestroyed() { + mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); + setUpDisplaySizeWithApp(2000, 1000); + prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + // Launch another opaque activity + final ActivityRecord opaqueActivity = new ActivityBuilder(mAtm) + .setLaunchedFromUid(mActivity.getUid()) + .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) + .build(); + mTask.addChild(opaqueActivity); + // Transparent activity strategy not applied + assertFalse(opaqueActivity.mLetterboxUiController.hasInheritedLetterboxBehavior()); + + // Launch translucent Activity + final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) + .setLaunchedFromUid(mActivity.getUid()) + .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) + .build(); + doReturn(false).when(translucentActivity).fillsParent(); + mTask.addChild(translucentActivity); + // Transparent strategy applied + assertTrue(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior()); + + spyOn(translucentActivity.mLetterboxUiController); + clearInvocations(translucentActivity.mLetterboxUiController); + + // Check that updateInheritedLetterbox() is invoked again + verify(translucentActivity.mLetterboxUiController, never()).updateInheritedLetterbox(); + } + @Test public void testApplyStrategyToTranslucentActivities() { mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); @@ -391,7 +449,6 @@ public class SizeCompatTests extends WindowTestsBase { prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); mActivity.info.setMinAspectRatio(1.2f); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.nowVisible = true; // Translucent Activity final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) .setLaunchedFromUid(mActivity.getUid()) @@ -448,7 +505,6 @@ public class SizeCompatTests extends WindowTestsBase { prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); mActivity.info.setMinAspectRatio(1.2f); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.nowVisible = true; // Translucent Activity final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) .setLaunchedFromUid(mActivity.getUid()) @@ -542,7 +598,6 @@ public class SizeCompatTests extends WindowTestsBase { true /* ignoreOrientationRequest */); mActivity.mWmService.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier( 1.0f /*letterboxVerticalPositionMultiplier*/); - mActivity.nowVisible = true; prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); // We launch a transparent activity final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) @@ -575,7 +630,6 @@ public class SizeCompatTests extends WindowTestsBase { mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); setUpDisplaySizeWithApp(2800, 1400); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.nowVisible = true; prepareUnresizable(mActivity, -1f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); // Rotate to put activity in size compat mode. rotateDisplay(mActivity.mDisplayContent, ROTATION_90); -- cgit v1.2.3 From e4abc4583d9b4ec504d9d96b031af0abf538b767 Mon Sep 17 00:00:00 2001 From: Jerry Chang Date: Wed, 19 Apr 2023 06:37:09 +0000 Subject: Prevent force showing system bars for TaskView Update the condition of force showing system bars to check adjacent tasks instead of checking multi-window windowing mode so it can distinguish from TaskView. Bug: 273495037 Test: atest TaskDisplayAreaTests DisplayPolicyTests InsetsPolicyTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:2bd0fe76a6b4ef566386e1949e4db0cf0db16430) Merged-In: I1c51c6f66cd6967651068de1ffc2e6e8566f5a46 Change-Id: I1c51c6f66cd6967651068de1ffc2e6e8566f5a46 --- .../core/java/com/android/server/wm/DisplayPolicy.java | 9 ++++----- services/core/java/com/android/server/wm/Task.java | 16 ++++++++++++++++ .../src/com/android/server/wm/InsetsPolicyTest.java | 8 +++++--- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 988e98f28d10..0da178b2bcd0 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -17,7 +17,6 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; -import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.view.Display.TYPE_INTERNAL; import static android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES; import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT; @@ -2382,16 +2381,16 @@ public class DisplayPolicy { private int updateSystemBarsLw(WindowState win, int disableFlags) { final TaskDisplayArea defaultTaskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea(); - final boolean multiWindowTaskVisible = + final boolean adjacentTasksVisible = defaultTaskDisplayArea.getRootTask(task -> task.isVisible() - && task.getTopLeafTask().getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) + && task.getAdjacentTask() != null) != null; final boolean freeformRootTaskVisible = defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_FREEFORM); // We need to force showing system bars when the multi-window or freeform root task is // visible. - mForceShowSystemBars = multiWindowTaskVisible || freeformRootTaskVisible; + mForceShowSystemBars = adjacentTasksVisible || freeformRootTaskVisible; // We need to force the consumption of the system bars if they are force shown or if they // are controlled by a remote insets controller. mForceConsumeSystemBars = mForceShowSystemBars @@ -2412,7 +2411,7 @@ public class DisplayPolicy { int appearance = APPEARANCE_OPAQUE_NAVIGATION_BARS | APPEARANCE_OPAQUE_STATUS_BARS; appearance = configureStatusBarOpacity(appearance); - appearance = configureNavBarOpacity(appearance, multiWindowTaskVisible, + appearance = configureNavBarOpacity(appearance, adjacentTasksVisible, freeformRootTaskVisible); final boolean requestHideNavBar = !win.getRequestedVisibility(ITYPE_NAVIGATION_BAR); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index f5c44d94160b..1ddc3e8c19b3 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -2385,6 +2385,22 @@ class Task extends TaskFragment { return parentTask == null ? null : parentTask.getCreatedByOrganizerTask(); } + /** @return the first adjacent task of this task or its parent. */ + @Nullable + Task getAdjacentTask() { + final TaskFragment adjacentTaskFragment = getAdjacentTaskFragment(); + if (adjacentTaskFragment != null && adjacentTaskFragment.asTask() != null) { + return adjacentTaskFragment.asTask(); + } + + final WindowContainer parent = getParent(); + if (parent == null || parent.asTask() == null) { + return null; + } + + return parent.asTask().getAdjacentTask(); + } + // TODO(task-merge): Figure out what's the right thing to do for places that used it. boolean isRootTask() { return getRootTask() == this; diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java index d3282b97a6b8..9f376ad6edab 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java @@ -81,12 +81,14 @@ public class InsetsPolicyTest extends WindowTestsBase { } @Test - public void testControlsForDispatch_multiWindowTaskVisible() { + public void testControlsForDispatch_adjacentTasksVisible() { addWindow(TYPE_STATUS_BAR, "statusBar"); addWindow(TYPE_NAVIGATION_BAR, "navBar"); - final WindowState win = createWindow(null, WINDOWING_MODE_MULTI_WINDOW, - ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app"); + final Task task1 = createTask(mDisplayContent); + final Task task2 = createTask(mDisplayContent); + task1.setAdjacentTaskFragment(task2); + final WindowState win = createAppWindow(task1, WINDOWING_MODE_MULTI_WINDOW, "app"); final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win); // The app must not control any system bars. -- cgit v1.2.3 From 2a6f371473cf12e9c4b44820b7903116be13e525 Mon Sep 17 00:00:00 2001 From: Shawn Lee Date: Tue, 18 Apr 2023 14:39:39 -0700 Subject: DO NOT MERGE Adding tests for updated EmptyShadeView logic Tests for ag/22688474 Bug: 267060171 Test: tests run (cherry picked from commit 6a19675b120b497618259cbcdedde4ffb68389c8) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:e6fe1a0ef1ebb9123f4ed943c8470a873efbaada) Merged-In: I35ba4652a125c8c83e18138f0fb0a51f3ef65b73 Change-Id: I35ba4652a125c8c83e18138f0fb0a51f3ef65b73 --- ...otificationStackScrollLayoutControllerTest.java | 28 ++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index 45ae96c10345..ac60626ba768 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -294,6 +294,34 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { /* notifVisibleInShade= */ false); } + @Test + public void testUpdateEmptyShadeView_bouncerShowing_hideEmptyView() { + when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false); + mController.attach(mNotificationStackScrollLayout); + + when(mCentralSurfaces.isBouncerShowing()).thenReturn(true); + setupShowEmptyShadeViewState(true); + reset(mNotificationStackScrollLayout); + mController.updateShowEmptyShadeView(); + verify(mNotificationStackScrollLayout).updateEmptyShadeView( + /* visible= */ false, + /* areNotificationsHiddenInShade= */ false); + } + + @Test + public void testUpdateEmptyShadeView_bouncerNotShowing_showEmptyView() { + when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false); + mController.attach(mNotificationStackScrollLayout); + + when(mCentralSurfaces.isBouncerShowing()).thenReturn(false); + setupShowEmptyShadeViewState(true); + reset(mNotificationStackScrollLayout); + mController.updateShowEmptyShadeView(); + verify(mNotificationStackScrollLayout).updateEmptyShadeView( + /* visible= */ true, + /* areNotificationsHiddenInShade= */ false); + } + @Test public void testOnUserChange_verifySensitiveProfile() { when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true); -- cgit v1.2.3 From 8618d83eef0b35db14eefcae99849b6b36af0cbb Mon Sep 17 00:00:00 2001 From: Shawn Lee Date: Mon, 17 Apr 2023 15:05:23 -0700 Subject: DO NOT MERGE Hide EmptyShadeView when on bouncer Added a check to make sure EmptyShadeView is hidden when bouncer is showing. Bug: 267060171 Test: manually verified EmptyShadeView visibility (cherry picked from commit bef2be6757eec92391189c106e89b63176cd16c0) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:2e640447d16c4e893927e2f76bda8b1073bb18f8) Merged-In: I2bfdc7801cec1b3aaa44f841d8a821214c6cb801 Change-Id: I2bfdc7801cec1b3aaa44f841d8a821214c6cb801 --- .../com/android/systemui/shade/NotificationPanelViewController.java | 1 + .../notification/stack/NotificationStackScrollLayoutController.java | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 7388c27ed313..6cd0aee62551 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -2843,6 +2843,7 @@ public final class NotificationPanelViewController implements Dumpable { /** Set whether the bouncer is showing. */ public void setBouncerShowing(boolean bouncerShowing) { mBouncerShowing = bouncerShowing; + mNotificationStackScrollLayoutController.updateShowEmptyShadeView(); updateVisibility(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 906b9592e3dd..f6c260a875ea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -1240,7 +1240,8 @@ public class NotificationStackScrollLayoutController { // Hide empty shade view when in transition to Keyguard. // That avoids "No Notifications" to blink when transitioning to AOD. // For more details, see: b/228790482 - && !isInTransitionToKeyguard(); + && !isInTransitionToKeyguard() + && !mCentralSurfaces.isBouncerShowing(); mView.updateEmptyShadeView(shouldShow, mZenModeController.areNotificationsHiddenInShade()); -- cgit v1.2.3 From 15c57882875d46eca14959eb31e97711a2d76e49 Mon Sep 17 00:00:00 2001 From: Anton Potapov Date: Thu, 25 May 2023 12:39:01 +0100 Subject: Remove ControlsProviderSelectorActivity finish on ControlsFavoritingActivity start When ControlsProviderSelectorActivity starts ControlsFavouritingActivity it uses ControlsActivity instance for transitions. ControlsActivity interacts with ControlsBindingController to get a list of controls. This interaction prevents load from happening because ControlsBindingController has a global state, and prevents users from adding a second Controls app. Removing finishing ControlsProviderSelectorActivity prevents ControlsActivity from being started. Test: manual with the steps from the bug Test: atest ControlsProviderSelectorActivityTest Fixes: 283070266 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:442c1002a8563c291c149d18e143e1880a82a27e) Merged-In: I02ddaa5e823379510e9c81a8d803d5269e6d85ad Change-Id: I02ddaa5e823379510e9c81a8d803d5269e6d85ad --- .../systemui/controls/management/ControlsProviderSelectorActivity.kt | 1 - .../controls/management/ControlsProviderSelectorActivityTest.kt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt index 3808e73ca085..5a3d21eacc14 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt @@ -214,7 +214,6 @@ open class ControlsProviderSelectorActivity @Inject constructor( putExtra(ControlsFavoritingActivity.EXTRA_FROM_PROVIDER_SELECTOR, true) } startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle()) - animateExitAndFinish() } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt index 8dfd22378a14..82aaccdbbf0a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt @@ -144,7 +144,7 @@ class ControlsProviderSelectorActivityTest : SysuiTestCase() { assertThat(activityRule.activity.lastStartedActivity?.component?.className) .isEqualTo(ControlsFavoritingActivity::class.java.name) - assertThat(activityRule.activity.triedToFinish).isTrue() + assertThat(activityRule.activity.triedToFinish).isFalse() } @Test -- cgit v1.2.3 From bf75a05886e1234ce5edfcf4fc0f53abe95d1087 Mon Sep 17 00:00:00 2001 From: jiabin Date: Tue, 25 Apr 2023 17:39:04 +0000 Subject: USB: update logic for reporting playback and capture capability of USB devices. Report USB devices support audio capture when there is a terminal whose subtype is ACI_OUTPUT_TERMINAL and terminal type is USB streaming and there is another terminal whose subtype is ACI_INPUT_TERMINAL and terminal type is not USB streaming. Report USB devices support audio playback when there is a terminal whose subtype is ACI_INPUT_TERMINAL and terminal type is USB streaming and there is another terminal whose subtype is ACI_OUTPUT_TERMINAL and terminal type is not USB streaming. Bug: 279151646 Bug: 278603582 Test: connect usb headset, dumpsys audio policy (cherry picked from commit 9a5cab13b4b31ba9bf66e90ef07312959aa391d3) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:e8e0134c04822f8f08a6dfad1a0cb9441c95c361) Merged-In: If1c14cc0a4e3fbdfbed2c105d37ece9a866f18ed Change-Id: If1c14cc0a4e3fbdfbed2c105d37ece9a866f18ed --- .../usb/descriptors/UsbDescriptorParser.java | 42 +++++++++++++++++----- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java index 626ce8927158..55419a80b604 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java @@ -584,15 +584,34 @@ public final class UsbDescriptorParser { } /** + * Returns true only if there is a terminal whose subtype and terminal type are the same as + * the given values. * @hide */ - public boolean hasAudioTerminal(int subType) { + public boolean hasAudioTerminal(int subType, int terminalType) { for (UsbDescriptor descriptor : mDescriptors) { - if (descriptor instanceof UsbACInterface) { - if (((UsbACInterface) descriptor).getSubclass() - == UsbDescriptor.AUDIO_AUDIOCONTROL - && ((UsbACInterface) descriptor).getSubtype() - == subType) { + if (descriptor instanceof UsbACTerminal) { + if (((UsbACTerminal) descriptor).getSubclass() == UsbDescriptor.AUDIO_AUDIOCONTROL + && ((UsbACTerminal) descriptor).getSubtype() == subType + && ((UsbACTerminal) descriptor).getTerminalType() == terminalType) { + return true; + } + } + } + return false; + } + + /** + * Returns true only if there is an interface whose subtype is the same as the given one and + * terminal type is different from the given one. + * @hide + */ + public boolean hasAudioTerminalExcludeType(int subType, int excludedTerminalType) { + for (UsbDescriptor descriptor : mDescriptors) { + if (descriptor instanceof UsbACTerminal) { + if (((UsbACTerminal) descriptor).getSubclass() == UsbDescriptor.AUDIO_AUDIOCONTROL + && ((UsbACTerminal) descriptor).getSubtype() == subType + && ((UsbACTerminal) descriptor).getTerminalType() != excludedTerminalType) { return true; } } @@ -604,14 +623,21 @@ public final class UsbDescriptorParser { * @hide */ public boolean hasAudioPlayback() { - return hasAudioTerminal(UsbACInterface.ACI_OUTPUT_TERMINAL); + return hasAudioTerminalExcludeType( + UsbACInterface.ACI_OUTPUT_TERMINAL, UsbTerminalTypes.TERMINAL_USB_STREAMING) + && hasAudioTerminal( + UsbACInterface.ACI_INPUT_TERMINAL, UsbTerminalTypes.TERMINAL_USB_STREAMING); } /** * @hide */ public boolean hasAudioCapture() { - return hasAudioTerminal(UsbACInterface.ACI_INPUT_TERMINAL); + return hasAudioTerminalExcludeType( + UsbACInterface.ACI_INPUT_TERMINAL, UsbTerminalTypes.TERMINAL_USB_STREAMING) + && hasAudioTerminal( + UsbACInterface.ACI_OUTPUT_TERMINAL, + UsbTerminalTypes.TERMINAL_USB_STREAMING); } /** -- cgit v1.2.3 From 19a0fdb34c49e6d0d702447ba44d2a38371d257d Mon Sep 17 00:00:00 2001 From: Austin Borger Date: Sat, 18 Mar 2023 12:56:12 -0700 Subject: ActivityManagerService: Allow openContentUri from vendor/system/product. Apps should not have direct access to this entry point. Check that the caller is a vendor, system, or product package. Test: Ran PoC app and CtsMediaPlayerTestCases. Bug: 236688380 (cherry picked from commit d0ba7467c2cb2815f94f6651cbb1c2f405e8e9c7) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:e37820e47c383aecf9d1173a0676c27e6a59ce4f) Merged-In: I0335496d28fa5fc3bfe1fecd4be90040b0b3687f Change-Id: I0335496d28fa5fc3bfe1fecd4be90040b0b3687f --- .../java/com/android/server/am/ActivityManagerService.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index f0dac2607a4e..ba0aaa1b7d8c 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -6792,7 +6792,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(); @@ -6821,6 +6821,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); -- cgit v1.2.3 From 704d238393c3f7d9a6e9690732699059d69d5fef Mon Sep 17 00:00:00 2001 From: Silin Huang Date: Wed, 12 Apr 2023 17:22:11 +0000 Subject: Do not load drawable for wallet card if the card image icon iscreated with content URI. This prevents the primary user from accessing the secondary user's photos for QAW card images. Test: manually, atest Bug: 272020068 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:ff753ae693065685d85bbda6af2953905fdf434c) Merged-In: I6932c5131b3c795bac4ea9b537938e7ef4f3ea4e Change-Id: I6932c5131b3c795bac4ea9b537938e7ef4f3ea4e --- .../systemui/qs/tiles/QuickAccessWalletTile.java | 8 ++++++- .../systemui/wallet/ui/WalletScreenController.java | 7 +++++- .../qs/tiles/QuickAccessWalletTileTest.java | 28 ++++++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) 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 { 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/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/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 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, @@ -381,6 +387,28 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { assertNotNull(mTile.getState().sideViewCustomDrawable); } + @Test + public void testQueryCards_notCurrentUser_hasCards_noSideViewDrawable() { + when(mKeyguardStateController.isUnlocked()).thenReturn(true); + + PendingIntent pendingIntent = + PendingIntent.getActivity(mContext, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE); + WalletCard walletCard = + new WalletCard.Builder( + CARD_ID, mCardImage, CARD_DESCRIPTION, pendingIntent).build(); + GetWalletCardsResponse response = + new GetWalletCardsResponse(Collections.singletonList(walletCard), 0); + + mTile.handleSetListening(true); + + verify(mController).queryWalletCards(mCallbackCaptor.capture()); + + mCallbackCaptor.getValue().onWalletCardsRetrieved(response); + mTestableLooper.processAllMessages(); + + assertNull(mTile.getState().sideViewCustomDrawable); + } + @Test public void testQueryCards_noCards_notUpdateSideViewDrawable() { setUpWalletCard(/* hasCard= */ false); -- cgit v1.2.3 From 132c85c4c17066bf091a998b34a46f7ce65a9a4b Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Thu, 27 Apr 2023 14:55:28 +0000 Subject: Verify URI permissions for notification shortcutIcon. Bug: 277593270 Test: atest NotificationManagerServiceTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:75d1ae1c2d49404c4be16ed7b50480fc16c0e4c4) Merged-In: Iaf2a9a82f18e018e60e6cdc020da6ebf7267e8b1 Change-Id: Iaf2a9a82f18e018e60e6cdc020da6ebf7267e8b1 --- core/java/android/app/Notification.java | 2 + .../NotificationManagerServiceTest.java | 87 +++++++++++++++++----- 2 files changed, 70 insertions(+), 19 deletions(-) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index de8fb509bde9..2c3359231bf5 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2892,6 +2892,8 @@ public class Notification implements Parcelable } } } + + visitIconUri(visitor, extras.getParcelable(EXTRA_CONVERSATION_ICON)); } if (isStyle(CallStyle.class) & extras != null) { 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 8f8b1c50891a..6ea0b56eeae1 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -5374,6 +5374,8 @@ 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"); @@ -5407,7 +5409,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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(); @@ -5415,6 +5418,8 @@ 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())); @@ -5422,6 +5427,68 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(visitor, times(1)).accept(eq(historyUri2)); } + @Test + public void testVisitUris_audioContentsString() throws Exception { + final Uri audioContents = Uri.parse("content://com.example/audio"); + + Bundle extras = new Bundle(); + extras.putString(Notification.EXTRA_AUDIO_CONTENTS_URI, audioContents.toString()); + + Notification n = new Notification.Builder(mContext, "a") + .setContentTitle("notification with uris") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .addExtras(extras) + .build(); + + Consumer visitor = (Consumer) spy(Consumer.class); + n.visitUris(visitor); + verify(visitor, times(1)).accept(eq(audioContents)); + } + + @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 visitor = (Consumer) 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"); @@ -5445,24 +5512,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(visitor, times(1)).accept(eq(verificationIcon.getUri())); } - @Test - public void testVisitUris_audioContentsString() throws Exception { - final Uri audioContents = Uri.parse("content://com.example/audio"); - - Bundle extras = new Bundle(); - extras.putString(Notification.EXTRA_AUDIO_CONTENTS_URI, audioContents.toString()); - - Notification n = new Notification.Builder(mContext, "a") - .setContentTitle("notification with uris") - .setSmallIcon(android.R.drawable.sym_def_app_icon) - .addExtras(extras) - .build(); - - Consumer visitor = (Consumer) spy(Consumer.class); - n.visitUris(visitor); - verify(visitor, times(1)).accept(eq(audioContents)); - } - @Test public void testSetNotificationPolicy_preP_setOldFields() { ZenModeHelper mZenModeHelper = mock(ZenModeHelper.class); -- cgit v1.2.3 From dadd7c98eb9ca58200d1cf73b3889d2e31ba502d Mon Sep 17 00:00:00 2001 From: Pavel Grafov Date: Wed, 5 Apr 2023 15:15:41 +0000 Subject: Ensure policy has no absurdly long strings The following APIs now enforce limits and throw IllegalArgumentException when limits are violated: * DPM.setTrustAgentConfiguration() limits agent packgage name, component name, and strings within configuration bundle. * DPM.setPermittedAccessibilityServices() limits package names. * DPM.setPermittedInputMethods() limits package names. * DPM.setAccountManagementDisabled() limits account name. * DPM.setLockTaskPackages() limits package names. * DPM.setAffiliationIds() limits id. * DPM.transferOwnership() limits strings inside the bundle. Package names are limited at 223, because they become directory names and it is a filesystem restriction, see FrameworkParsingPackageUtils. All other strings are limited at 65535, because longer ones break binary XML serializer. The following APIs silently truncate strings that are long beyond reason: * DPM.setShortSupportMessage() truncates message at 200. * DPM.setLongSupportMessage() truncates message at 20000. * DPM.setOrganizationName() truncates org name at 200. Bug: 260729089 Test: atest com.android.server.devicepolicy (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:456dbcae5ae4bc0d8f73f8cb97b57c8de6958f9c) Merged-In: Idcf54e408722f164d16bf2f24a00cd1f5b626d23 Change-Id: Idcf54e408722f164d16bf2f24a00cd1f5b626d23 --- .../android/app/admin/DevicePolicyManager.java | 3 +- .../devicepolicy/DevicePolicyManagerService.java | 92 +++++++++++++++++++++- 2 files changed, 91 insertions(+), 4 deletions(-) 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. *

* 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/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 enabledServices; long id = mInjector.binderClearCallingIdentity(); @@ -10463,6 +10483,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } if (packageList != null) { + for (String pkg : packageList) { + enforceMaxPackageNameLength(pkg); + } + List 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 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 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); + } + } + } + } } -- cgit v1.2.3 From 5f15f790df505da40a56ce8a7728b4cb863f4b1a Mon Sep 17 00:00:00 2001 From: Beverly Date: Mon, 8 May 2023 16:33:12 +0000 Subject: On device lockdown, always show the keyguard Manual test steps: 1. Enable app pinning and disable "Ask for PIN before unpinning" setting 2. Pin an app (ie: Settings) 3. Lockdown from the power menu Observe: user is brought to the keyguard, primary auth is required to enter the device. After entering credential, the device is still in app pinning mode. Test: atest KeyguardViewMediatorTest Test: manual steps outlined above Bug: 218495634 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:100ae42365d7fc8ba7d241e8c9a7ef6aa0cdb961) Merged-In: I9a7c5e1acadabd4484e58573331f98dba895f2a2 Change-Id: I9a7c5e1acadabd4484e58573331f98dba895f2a2 --- .../com/android/systemui/keyguard/KeyguardViewMediator.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 8bd9673d4e87..76543c9c67aa 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -720,6 +720,13 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } } } + + @Override + public void onStrongAuthStateChanged(int userId) { + if (mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) { + doKeyguardLocked(null); + } + } }; ViewMediatorCallback mViewMediatorCallback = new ViewMediatorCallback() { @@ -1926,7 +1933,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; -- cgit v1.2.3 From f72b614d470f982940fc0a5766f5a28d2f86c973 Mon Sep 17 00:00:00 2001 From: Hai Zhang Date: Wed, 17 May 2023 01:30:20 -0700 Subject: Preserve flags for non-runtime permissions upon package update. PermissionManagerServiceImpl.restorePermissionState() creates a new UID permission state for non-shared-UID packages that have been updated (i.e. replaced), however the existing logic for non-runtime permission never carried over the flags from the old state. This wasn't an issue for much older platforms because permission flags weren't used for non-runtime permissions, however since we are starting to use them for role protected permissions (ROLE_GRANTED) and app op permissions (USER_SET), we do need to preserver the permission flags. This change merges the logic for granting and revoking a non-runtime permission in restorePermissionState() into a single if branch, and appends the logic to copy the flag from the old state in that branch. Bug: 283006437 Test: PermissionFlagsTest#nonRuntimePermissionFlagsPreservedAfterReinstall (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:98f78c885d2c9e5d88a4636b1ed36d723ca9261f) Merged-In: Iea3c66710e7d28c6fc730b1939da64f1172b08db Change-Id: Iea3c66710e7d28c6fc730b1939da64f1172b08db --- .../permission/PermissionManagerServiceImpl.java | 88 ++++++++++++---------- 1 file changed, 50 insertions(+), 38 deletions(-) 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()); } } -- cgit v1.2.3 From 40659fabbd69dd0b757c7cfac09f443f63aea280 Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Mon, 15 May 2023 16:15:55 +0000 Subject: Check URIs in notification public version. Bug: 276294099 Test: atest NotificationManagerServiceTest NotificationVisitUrisTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:67cd169d073486c7c047b80ab83843cdee69bf53) Merged-In: I670198b213abb2cb29a9865eb9d1e897700508b4 Change-Id: I670198b213abb2cb29a9865eb9d1e897700508b4 --- core/java/android/app/Notification.java | 4 ++++ .../notification/NotificationManagerServiceTest.java | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 2c3359231bf5..8a730fb0deaa 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2807,6 +2807,10 @@ public class Notification implements Parcelable * @hide */ public void visitUris(@NonNull Consumer visitor) { + if (publicVersion != null) { + publicVersion.visitUris(visitor); + } + visitor.accept(sound); if (tickerView != null) tickerView.visitUris(visitor); 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 6ea0b56eeae1..2fa14a7c93c6 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -5427,6 +5427,26 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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 visitor = (Consumer) spy(Consumer.class); + n.visitUris(visitor); + verify(visitor, times(1)).accept(eq(smallIconPublic.getUri())); + verify(visitor, times(1)).accept(eq(largeIconPrivate.getUri())); + } + @Test public void testVisitUris_audioContentsString() throws Exception { final Uri audioContents = Uri.parse("content://com.example/audio"); -- cgit v1.2.3 From e2ee0f86ab8c2c0c2ac34709282b9d52a6344be5 Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Fri, 12 May 2023 15:41:09 +0000 Subject: Implement visitUris for RemoteViews ViewGroupActionAdd. This is to prevent a vulnerability where notifications can show resources belonging to other users, since the URI in the nested views was not being checked. Bug: 277740082 Test: atest RemoteViewsTest NotificationVisitUrisTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:850fd984e5f346645b5a941ed7307387c7e4c4de) Merged-In: I5c71f0bad0a6f6361eb5ceffe8d1e47e936d78f8 Change-Id: I5c71f0bad0a6f6361eb5ceffe8d1e47e936d78f8 --- core/java/android/widget/RemoteViews.java | 5 +++++ .../src/android/widget/RemoteViewsTest.java | 24 ++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index c985ae82307c..0b85cbe1cd52 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -2583,6 +2583,11 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return VIEW_GROUP_ACTION_ADD_TAG; } + + @Override + public final void visitUris(@NonNull Consumer 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 a9d3ce51b2c3..350b7fc2926f 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java @@ -719,6 +719,30 @@ public class RemoteViewsTest { verify(visitor, times(1)).accept(eq(icon4.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 visitor = (Consumer) 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); -- cgit v1.2.3 From de0b6c36d30d7fa7196b0f8a28d855c270a8fd63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Budnik?= Date: Tue, 4 Apr 2023 17:58:26 +0000 Subject: Validate ComponentName for MediaButtonBroadcastReceiver This is a security fix for b/270049379. Bug: 270049379 Test: atest CtsMediaMiscTestCases (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:c573c83a2aa36ca022302f675d705518dd723a3c) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:ba546a306217389a8ff9e5e948612651fd496081) Merged-In: I05626f7abf1efef86c9e01ee3f077d7177d7f662 Change-Id: I05626f7abf1efef86c9e01ee3f077d7177d7f662 --- media/java/android/media/session/MediaSession.java | 8 ++++-- .../android/server/media/MediaSessionRecord.java | 33 ++++++++++++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) 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. - *

- * Note: The given {@link android.content.BroadcastReceiver} should belong to the same package - * as the context that was given when creating {@link MediaSession}. + * + *

Note: The given {@link android.content.BroadcastReceiver} should belong to the same + * package as the context that was given when creating {@link MediaSession}. + * + *

Calls with invalid or non-existent receivers will be ignored. * * @param broadcastReceiver the component name of the BroadcastReceiver class */ diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 1bd50632ccbf..cc4895ffaf24 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -16,12 +16,17 @@ 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.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,6 +57,7 @@ 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; @@ -884,6 +890,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 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 +976,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 +992,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 { -- cgit v1.2.3 From c8b220a6287829d0e67afd7f79c4d436600591e1 Mon Sep 17 00:00:00 2001 From: Hani Kazmi Date: Mon, 22 May 2023 15:19:10 +0000 Subject: Update Pip launches to not enter pinned task if in background. Addresses a BAL bypass where Pip could be started without the launcher being visible. Bug: 271576718 Test: atest CtsWindowManagerDeviceTestCases:PinnedStackTests Test: atest android.server.wm.BackgroundActivityLaunchTest#testPipCannotStartFromBackground (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:b263d3beed7a412ac342c63956f213b70d6e2679) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:291d124257f6e46862a813305892b02b9a8f851c) Merged-In: Icfe0a17d7f6f127acaae8400a97e8bdc53fcc9ad Change-Id: Icfe0a17d7f6f127acaae8400a97e8bdc53fcc9ad --- services/core/java/com/android/server/wm/ActivityStarter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index d1a5ead78af2..4d5238a961f4 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1709,7 +1709,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"); } -- cgit v1.2.3 From 06882dc7db67801fe13aec6e420d5baf1da8d250 Mon Sep 17 00:00:00 2001 From: Johannes Gallmann Date: Mon, 22 May 2023 10:21:02 +0200 Subject: Fix PrivacyChip not visible issue Bug: 281807669 Test: Manual, i.e. posting the following sequence of events (within few milliseconds) to the scheduler and observe the behaviour with and without the fix: Mic in use -> Mic not in use -> Mic in use (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:a45e1d045770eaabfdbf0e1212c9eb84caf1d565) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:20ea049a4a52dbc8d4e5ed957a2b6b9aa02a2f34) Merged-In: I9851e6ed4cb956d0459ef56251eb0ef3210764b8 Change-Id: I9851e6ed4cb956d0459ef56251eb0ef3210764b8 --- .../SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 43f78c3166e4..91a74988a778 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt @@ -107,9 +107,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) + return other is PrivacyEvent } override fun updateFromEvent(other: StatusEvent?) { -- cgit v1.2.3 From e6bdf8e9cb29c287de0f6949e503a31d00df6488 Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Tue, 23 May 2023 16:26:41 +0000 Subject: Check URIs in sized remote views. Bug: 277741109 Test: atest RemoteViewsTest (cherry picked from commit ae0d45137b0f8ea49a085bbce4d39f901685c4a5) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:902f020bc81e5b584d5cb0276568b888a728fc4a) Merged-In: Iceb33606da3a49b9638ab21aeae17a168c1b411a Change-Id: Iceb33606da3a49b9638ab21aeae17a168c1b411a --- core/java/android/widget/RemoteViews.java | 5 +++ .../src/android/widget/RemoteViewsTest.java | 40 ++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 0b85cbe1cd52..adcb04f946cc 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -726,6 +726,11 @@ 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); } diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java index 350b7fc2926f..e0cccf2f5200 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java @@ -63,6 +63,7 @@ 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; @@ -778,4 +779,43 @@ public class RemoteViewsTest { 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 sizedViews = new HashMap<>(); + sizedViews.put(new SizeF(300, 300), large); + sizedViews.put(new SizeF(100, 100), small); + RemoteViews views = new RemoteViews(sizedViews); + + Consumer visitor = (Consumer) 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())); + } } -- cgit v1.2.3 From 84d6438763478f0427d307572acdbdef818d6341 Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Thu, 25 May 2023 11:43:43 +0000 Subject: Visit URIs in themed remoteviews icons. Bug: 281018094 Test: atest RemoteViewsTest NotificationVisitUrisTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:634a69b7700017eac534f3f58cdcc2572f3cc659) Merged-In: I2014bf21cf90267f7f1b3f370bf00ab7001b064e Change-Id: I2014bf21cf90267f7f1b3f370bf00ab7001b064e --- core/java/android/widget/RemoteViews.java | 10 +++++++++- .../tests/coretests/src/android/widget/RemoteViewsTest.java | 13 +++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index adcb04f946cc..74e863224625 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -1835,7 +1835,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public final void visitUris(@NonNull Consumer visitor) { + public void visitUris(@NonNull Consumer visitor) { switch (this.type) { case URI: final Uri uri = (Uri) getParameterValue(null); @@ -2298,6 +2298,14 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return NIGHT_MODE_REFLECTION_ACTION_TAG; } + + @Override + public void visitUris(@NonNull Consumer visitor) { + if (this.type == ICON) { + visitIconUri((Icon) mDarkValue, visitor); + visitIconUri((Icon) mLightValue, visitor); + } + } } /** diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java index e0cccf2f5200..a8f2b1d22aed 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java @@ -720,6 +720,19 @@ public class RemoteViewsTest { 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 visitor = (Consumer) 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); -- cgit v1.2.3 From 706c50c46a03d8348040bf30ea567f8791051fb7 Mon Sep 17 00:00:00 2001 From: Michael Mikhail Date: Fri, 26 May 2023 19:41:21 +0000 Subject: DO NOT MERGE Verify URI permissions in MediaMetadata Add a check for URI permission to make sure that user can access the URI set in MediaMetadata. If permission is denied, clear the URI string set in metadata. Bug: 271851153 Test: atest MediaSessionTest Test: Verified by POC app attached in bug, image of second user is not the UMO background of the first user. (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:277e7e05866a3da3c5871c071231b2b7c911d81e) Merged-In: I932d5d5143998db89d7132ced84faffa4a0bd5aa Change-Id: I932d5d5143998db89d7132ced84faffa4a0bd5aa --- .../android/server/media/MediaSessionRecord.java | 53 ++++++++++++++++++---- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index cc4895ffaf24..b459cfe6b44e 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -22,6 +22,8 @@ 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; @@ -63,6 +65,9 @@ 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; @@ -78,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); /** @@ -131,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; @@ -192,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); @@ -1018,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 -- cgit v1.2.3 From 18a514f4ac9fa922e0e6f8e81ef7c88a72788dd4 Mon Sep 17 00:00:00 2001 From: Chandru S Date: Tue, 16 May 2023 10:41:07 -0700 Subject: Use Settings.System.getIntForUser instead of getInt to make sure user specific settings are used Bug: 265431505 Test: atest KeyguardViewMediatorTest (cherry picked from commit 625e009fc195ba5d658ca2d78ebb23d2770cc6c4) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:f223b95992115bba93a89e6c7ca53c269ea2c71d) Merged-In: I66a660c091c90a957a0fd1e144c013840db3f47e Change-Id: I66a660c091c90a957a0fd1e144c013840db3f47e --- .../com/android/systemui/keyguard/KeyguardViewMediator.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 76543c9c67aa..82189763def6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -1553,9 +1553,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() @@ -1567,8 +1567,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 @@ -2396,7 +2396,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 -- cgit v1.2.3 From cc113910a65067597c9e89755b718a05fd5bc8c1 Mon Sep 17 00:00:00 2001 From: Lee Shombert Date: Fri, 19 May 2023 15:52:00 -0700 Subject: Remove unnecessary padding code Bug: 213170822 Remove the code that CursorWindow::writeToParcel() uses to ensure slot data is 4-byte aligned. Because mAllocOffset and mSlotsOffset are already 4-byte aligned, the alignment step here is unnecessary. CursorWindow::spaceInUse() returns the total space used. The tests verify that the total space used is always a multiple of 4 bytes. Test: atest * libandroidfw_tests (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:5d4afa0986cbc440f458b4b8db05fd176ef3e6d2) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:c7e1590ea236576e5ae2ed1e1bcc14756c3363f2) Merged-In: I720699093d5c5a584283e5b76851938f449ffa21 Change-Id: I720699093d5c5a584283e5b76851938f449ffa21 --- libs/androidfw/CursorWindow.cpp | 10 ++++---- libs/androidfw/include/androidfw/CursorWindow.h | 3 +++ libs/androidfw/tests/CursorWindow_test.cpp | 31 ++++++++++++++++++++++--- 3 files changed, 35 insertions(+), 9 deletions(-) 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(newData), static_cast(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 -- cgit v1.2.3 From 4ae2e43bf140ec60bdeff07ab3ae29eaa84e313d Mon Sep 17 00:00:00 2001 From: Pranav Madapurmath Date: Thu, 1 Jun 2023 00:26:09 +0000 Subject: Merge "Resolve StatusHints image exploit across user." into sc-v2-dev am: e371b3018f Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/23465066 Fixes: 285211549 Fixes: 280797684 Signed-off-by: Automerger Merge Worker (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:3fc6dd50937d23c854fde540380c51fd451b1c55) Merged-In: Idd360f69fc9e5a9f32fd3ca76ec0440c8bb12cf4 Change-Id: Idd360f69fc9e5a9f32fd3ca76ec0440c8bb12cf4 --- .../java/android/telecom/ParcelableConference.java | 12 ++++- telecomm/java/android/telecom/StatusHints.java | 53 +++++++++++++++++++++- 2 files changed, 61 insertions(+), 4 deletions(-) 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 CREATOR = new Parcelable.Creator () { @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,10 +53,30 @@ 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. * @@ -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); -- cgit v1.2.3 From 66e18e617b9cc9cb2a93aaf9a3e53dc45827ff49 Mon Sep 17 00:00:00 2001 From: Miranda Kephart Date: Fri, 28 Apr 2023 10:58:46 -0400 Subject: [DO NOT MERGE] Update quickshare intent rather than recreating Currently, we extract the quickshare intent and re-wrap it as a new PendingIntent once we get the screenshot URI. This is insecure as it leads to executing the original with SysUI's permissions, which the app may not have. This change switches to using Intent.fillin to add the URI, keeping the original PendingIntent and original permission set. Bug: 278720336 Fix: 278720336 Test: manual (to test successful quickshare), atest SaveImageInBackgroundTaskTest (to verify original pending intent unchanged) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:589ce3909c6e9e30f073df86e7de4503854a032a) Merged-In: Icad3d5f939fcfb894e2038948954bc2735dbe326 Change-Id: Icad3d5f939fcfb894e2038948954bc2735dbe326 --- .../screenshot/SaveImageInBackgroundTask.java | 113 ++++---- .../systemui/screenshot/ScreenshotController.java | 1 + .../screenshot/ScreenshotSmartActions.java | 5 +- .../systemui/screenshot/SmartActionsReceiver.java | 7 +- .../screenshot/SaveImageInBackgroundTaskTest.kt | 283 +++++++++++++++++++++ 5 files changed, 356 insertions(+), 53 deletions(-) create mode 100644 packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt 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 { // 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 { 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 { 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 { } /** - * 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> 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 { 500); List 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> 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 getSmartActions(String screenshotId, CompletableFuture> 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/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() + private val smartActions = mock() + private val smartActionsProvider = mock() + private val saveImageData = SaveImageInBackgroundData() + private val sharedTransitionSupplier = + mock>() + private val testScreenshotId: String = "testScreenshotId" + private val testBitmap = mock() + private val testUser = UserHandle.getUserHandleForUid(0) + private val testIcon = mock() + private val testImageTime = 1234.toLong() + private val flags = FakeFeatureFlags() + + private val smartActionsUriFuture = mock>>() + private val smartActionsFuture = mock>>() + + 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()) + + val quickShareAction = + saveImageTask.queryQuickShareAction(testScreenshotId, testBitmap, testUser, testUri) + + assertNull(quickShareAction) + } + + @Test + fun testQueryQuickShare_withActions() { + val actions = ArrayList() + 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() + 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() + 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() + 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() + } +} -- cgit v1.2.3 From e6171cf3af21d59a23ebf7d14a71700253e9fe23 Mon Sep 17 00:00:00 2001 From: Achim Thesmann Date: Tue, 23 May 2023 00:26:33 +0000 Subject: Ignore virtual presentation windows - RESTRICT AUTOMERGE Windows of TYPE_PRESENTATION on virtual displays should not be counted as visible windows to determine if BAL is allowed. Test: manual test, atest BackgroundActivityLaunchTest Bug: 264029851, 205130886 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:4c40b187cd5277c27d20758c675865bf89180c7a) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:5bf9607bec3f1224158cfcff7dd91ac558b46c0f) Merged-In: I08b16ba1c155e951286ddc22019180cbd6334dfa Change-Id: I08b16ba1c155e951286ddc22019180cbd6334dfa --- services/core/java/com/android/server/wm/WindowState.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) 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 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] -- cgit v1.2.3 From c234c1b791038ce2eff3e285f27ea963249fe046 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= Date: Thu, 15 Jun 2023 18:31:34 +0200 Subject: Forbid granting access to NLSes with too-long component names This makes the limitation, which was previously only checked on the Settings UI, enforced everywhere. Fixes: 260570119 Fixes: 286043036 Test: atest + manually (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:6fcdbd0c6efc67b014b8e1b43c5ec233f912ee8b) Merged-In: I4c25d80978cb37a8fa1531f5045259d25ac64692 Change-Id: I4c25d80978cb37a8fa1531f5045259d25ac64692 --- core/java/android/app/NotificationManager.java | 6 ++++++ .../settingslib/RestrictedSwitchPreference.java | 8 +++++-- .../notification/NotificationManagerService.java | 5 +++++ .../com/android/server/vr/VrManagerService.java | 6 +++++- .../NotificationManagerServiceTest.java | 25 ++++++++++++++++++++++ 5 files changed, 47 insertions(+), 3 deletions(-) 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/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/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/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/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 2fa14a7c93c6..dcaca51c176b 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; @@ -3847,6 +3848,30 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { any(), anyInt(), anyBoolean(), anyBoolean(), anyBoolean()); } + @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(); -- cgit v1.2.3 From c39303e1cf11164ee93e3e5285198a7e6b7f15f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= Date: Wed, 5 Jul 2023 13:52:21 +0200 Subject: Visit Uris added by WearableExtender Bug: 283962802 Test: atest + manual (POC app now crashes on notify() as expected) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:3d36966ea2aeebc3501a69a8ef7afce5ef593cee) Merged-In: I0da18c631eb5e4844a48760c7aaedab715a0bfed Change-Id: I0da18c631eb5e4844a48760c7aaedab715a0bfed --- core/java/android/app/Notification.java | 17 ++++++++++++++++- .../notification/NotificationManagerServiceTest.java | 20 ++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 8a730fb0deaa..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 visitor) { + visitIconUri(visitor, getIcon()); + } + @Override public Action clone() { return new Action( @@ -2823,7 +2827,7 @@ public class Notification implements Parcelable if (actions != null) { for (Action action : actions) { - visitIconUri(visitor, action.getIcon()); + action.visitUris(visitor); } } @@ -2911,6 +2915,11 @@ public class Notification implements Parcelable if (mBubbleMetadata != null) { visitIconUri(visitor, mBubbleMetadata.getIcon()); } + + if (extras != null && extras.containsKey(WearableExtender.EXTRA_WEARABLE_EXTENSIONS)) { + WearableExtender extender = new WearableExtender(this); + extender.visitUris(visitor); + } } /** @@ -11626,6 +11635,12 @@ public class Notification implements Parcelable mFlags &= ~mask; } } + + private void visitUris(@NonNull Consumer visitor) { + for (Action action : mActions) { + action.visitUris(visitor); + } + } } /** 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 dcaca51c176b..bd74c2ea8262 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -5557,6 +5557,26 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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 visitor = (Consumer) 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); -- cgit v1.2.3 From 6801e4895123d5ee09a7ff64cc42d43e5bfa14ab Mon Sep 17 00:00:00 2001 From: Dmitry Dementyev Date: Wed, 5 Jul 2023 10:45:04 -0700 Subject: Update AccountManagerService checkKeyIntentParceledCorrectly. Bug: 265798288 Test: manual (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:8476b140eed0235df4e8f07d94420a1471191b55) Merged-In: Ia2030a9dc371dccadd4e188a529351ac4232bb4f Change-Id: Ia2030a9dc371dccadd4e188a529351ac4232bb4f --- .../core/java/com/android/server/accounts/AccountManagerService.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 1dc0942ceac5..7a51f5155a98 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -4932,7 +4932,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) { -- cgit v1.2.3 From 0a24f79778a86a35df67ad439065c0eb9b294591 Mon Sep 17 00:00:00 2001 From: Beth Thibodeau Date: Thu, 22 Jun 2023 18:26:44 -0500 Subject: Improve user handling when querying for resumable media - Before trying to query recent media from a saved component, check whether the current user actually has that component installed - Track user when creating the MediaBrowser, in case the user changes before the MBS returns a result Test: atest MediaResumeListenerTest Bug: 284297711 (cherry picked from commit e566a250ad61e269119b475c7ebdae6ca962c4a7) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:01d527cdfd135aa8a2e76c693137fbba5d109a8b) Merged-In: I838ff0e125acadabc8436a00dbff707cc4be6249 Change-Id: I838ff0e125acadabc8436a00dbff707cc4be6249 --- .../media/controls/resume/MediaResumeListener.kt | 36 ++++++++--- .../media/controls/resume/ResumeMediaBrowser.java | 15 ++++- .../controls/resume/ResumeMediaBrowserFactory.java | 7 ++- .../controls/resume/MediaResumeListenerTest.kt | 72 +++++++++++++++++++++- .../controls/resume/ResumeMediaBrowserTest.kt | 8 ++- 5 files changed, 120 insertions(+), 18 deletions(-) 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; } /** @@ -284,6 +289,14 @@ public class ResumeMediaBrowser { return new MediaController(mContext, token); } + /** + * 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/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 @Captor lateinit var actionCaptor: ArgumentCaptor @Captor lateinit var componentCaptor: ArgumentCaptor + @Captor lateinit var userIdCaptor: ArgumentCaptor + @Captor lateinit var userCallbackCaptor: ArgumentCaptor 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 -- cgit v1.2.3 From ce2f8f47ec4a7c0628995428cff942c8f35d9aa2 Mon Sep 17 00:00:00 2001 From: Jean-Michel Trivi Date: Wed, 7 Dec 2022 04:36:46 +0000 Subject: RingtoneManager: verify default ringtone is audio When a ringtone picker tries to set a ringtone through RingtoneManager.setActualDefaultRingtoneUri (also called by com.android.settings.DefaultRingtonePreference), verify the mimeType can be obtained (not found when caller doesn't have access to it) and it is an audio resource. Bug: 205837340 Test: atest android.media.audio.cts.RingtoneManagerTest (cherry picked from commit 38618f9fb16d3b5617e2289354d47abe5af17dad) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:88f5aae54cf522e4ec50c2dbf2c782872734db00) Merged-In: I3f2c487ded405c0c1a83ef0a2fe99cff7cc9328e Change-Id: I3f2c487ded405c0c1a83ef0a2fe99cff7cc9328e --- media/java/android/media/RingtoneManager.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java index 27db41cb9f4e..d3c3c370a641 100644 --- a/media/java/android/media/RingtoneManager.java +++ b/media/java/android/media/RingtoneManager.java @@ -814,10 +814,10 @@ public class RingtoneManager { return ringtoneUri; } - + /** * Sets the {@link Uri} of the default sound for a given sound type. - * + * * @param context A context used for querying. * @param type The type whose default sound should be set. One of * {@link #TYPE_RINGTONE}, {@link #TYPE_NOTIFICATION}, or @@ -833,6 +833,21 @@ public class RingtoneManager { if(!isInternalRingtoneUri(ringtoneUri)) { ringtoneUri = ContentProvider.maybeAddUserId(ringtoneUri, context.getUserId()); } + + if (ringtoneUri != null) { + final String mimeType = resolver.getType(ringtoneUri); + if (mimeType == null) { + Log.e(TAG, "setActualDefaultRingtoneUri for URI:" + ringtoneUri + + " ignored: failure to find mimeType (no access from this context?)"); + return; + } + if (!(mimeType.startsWith("audio/") || mimeType.equals("application/ogg"))) { + Log.e(TAG, "setActualDefaultRingtoneUri for URI:" + ringtoneUri + + " ignored: associated mimeType:" + mimeType + " is not an audio type"); + return; + } + } + Settings.System.putStringForUser(resolver, setting, ringtoneUri != null ? ringtoneUri.toString() : null, context.getUserId()); -- cgit v1.2.3 From cf7f93cec3f8c51c00cf97cf11dc1162d53189ca Mon Sep 17 00:00:00 2001 From: Beth Thibodeau Date: Tue, 30 May 2023 18:45:47 -0500 Subject: [DO NOT MERGE] Add placeholder when media control title is blank When an app posts a media control with no available title, show a placeholder string with the app name instead Bug: 274775190 Test: atest MediaDataManagerTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:965e514614f374dcffe2a638b7ecee3d51531340) Merged-In: Ie406c180af48653595e8e222a15b4dda27de2e0e Change-Id: Ie406c180af48653595e8e222a15b4dda27de2e0e --- packages/SystemUI/res/values/strings.xml | 2 + .../media/controls/pipeline/MediaDataManager.kt | 8 +- .../controls/pipeline/MediaDataManagerTest.kt | 122 +++++++++++++++++++-- 3 files changed, 121 insertions(+), 11 deletions(-) diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 81241c91d1c6..ba806e591c1c 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2382,6 +2382,8 @@ Play %1$s from %2$s For You + + %1$s is running diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt index 525b2fcb8dbc..4e20a24e9add 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt @@ -786,12 +786,16 @@ class MediaDataManager( // Song name var song: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE) - if (song == null) { + if (song.isNullOrBlank()) { song = metadata?.getString(MediaMetadata.METADATA_KEY_TITLE) } - if (song == null) { + if (song.isNullOrBlank()) { song = HybridGroupManager.resolveTitle(notif) } + if (song.isNullOrBlank()) { + // For apps that don't include a title, add a placeholder + song = context.getString(R.string.controls_media_empty_title, appName) + } // Explicit Indicator var isExplicit = false diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt index d428db7b9dda..9ced057d3ad8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt @@ -25,6 +25,7 @@ import android.app.smartspace.SmartspaceConfig import android.app.smartspace.SmartspaceManager import android.app.smartspace.SmartspaceTarget import android.content.Intent +import android.content.pm.PackageManager import android.graphics.Bitmap import android.graphics.drawable.Icon import android.media.MediaDescription @@ -76,6 +77,7 @@ import org.mockito.ArgumentMatchers.anyInt import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito +import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify @@ -516,6 +518,107 @@ class MediaDataManagerTest : SysuiTestCase() { verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) } + @Test + fun testOnNotificationAdded_emptyTitle_hasPlaceholder() { + // When the manager has a notification with an empty title + val mockPackageManager = mock(PackageManager::class.java) + context.setMockPackageManager(mockPackageManager) + whenever(mockPackageManager.getApplicationLabel(any())).thenReturn(APP_NAME) + whenever(controller.metadata) + .thenReturn( + metadataBuilder + .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE) + .build() + ) + mediaDataManager.onNotificationAdded(KEY, mediaNotification) + + // Then a media control is created with a placeholder title string + assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) + assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(null), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) + val placeholderTitle = context.getString(R.string.controls_media_empty_title, APP_NAME) + assertThat(mediaDataCaptor.value.song).isEqualTo(placeholderTitle) + } + + @Test + fun testOnNotificationAdded_blankTitle_hasPlaceholder() { + // GIVEN that the manager has a notification with a blank title + val mockPackageManager = mock(PackageManager::class.java) + context.setMockPackageManager(mockPackageManager) + whenever(mockPackageManager.getApplicationLabel(any())).thenReturn(APP_NAME) + whenever(controller.metadata) + .thenReturn( + metadataBuilder + .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE) + .build() + ) + mediaDataManager.onNotificationAdded(KEY, mediaNotification) + + // Then a media control is created with a placeholder title string + assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) + assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(null), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) + val placeholderTitle = context.getString(R.string.controls_media_empty_title, APP_NAME) + assertThat(mediaDataCaptor.value.song).isEqualTo(placeholderTitle) + } + + @Test + fun testOnNotificationAdded_emptyMetadata_usesNotificationTitle() { + // When the app sets the metadata title fields to empty strings, but does include a + // non-blank notification title + val mockPackageManager = mock(PackageManager::class.java) + context.setMockPackageManager(mockPackageManager) + whenever(mockPackageManager.getApplicationLabel(any())).thenReturn(APP_NAME) + whenever(controller.metadata) + .thenReturn( + metadataBuilder + .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE) + .putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, SESSION_EMPTY_TITLE) + .build() + ) + mediaNotification = + SbnBuilder().run { + setPkg(PACKAGE_NAME) + modifyNotification(context).also { + it.setSmallIcon(android.R.drawable.ic_media_pause) + it.setContentTitle(SESSION_TITLE) + it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) }) + } + build() + } + mediaDataManager.onNotificationAdded(KEY, mediaNotification) + + // Then the media control is added using the notification's title + assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) + assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(null), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) + assertThat(mediaDataCaptor.value.song).isEqualTo(SESSION_TITLE) + } + @Test fun testOnNotificationRemoved_emptyTitle_notConverted() { // GIVEN that the manager has a notification with a resume action and empty title. @@ -529,8 +632,11 @@ class MediaDataManagerTest : SysuiTestCase() { val data = mediaDataCaptor.value val instanceId = data.instanceId assertThat(data.resumption).isFalse() - mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {})) - + mediaDataManager.onMediaDataLoaded( + KEY, + null, + data.copy(song = SESSION_EMPTY_TITLE, resumeAction = Runnable {}) + ) // WHEN the notification is removed reset(listener) mediaDataManager.onNotificationRemoved(KEY) @@ -554,17 +660,15 @@ class MediaDataManagerTest : SysuiTestCase() { @Test fun testOnNotificationRemoved_blankTitle_notConverted() { // GIVEN that the manager has a notification with a resume action and blank title. - whenever(controller.metadata) - .thenReturn( - metadataBuilder - .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE) - .build() - ) addNotificationAndLoad() val data = mediaDataCaptor.value val instanceId = data.instanceId assertThat(data.resumption).isFalse() - mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {})) + mediaDataManager.onMediaDataLoaded( + KEY, + null, + data.copy(song = SESSION_BLANK_TITLE, resumeAction = Runnable {}) + ) // WHEN the notification is removed reset(listener) -- cgit v1.2.3 From 2d3bf34335e026cc4d958511a7c3450a184dc8d5 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Wed, 21 Jun 2023 13:37:32 -0700 Subject: Import translations. DO NOT MERGE ANYWHERE BUG:286996125 Auto-generated-cl: translation import (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:7aa5b1415941f8c4172d02072f59349d30450232) Merged-In: Ic6ab0430324902d7fe42feb491c2b92a13e8bc17 Change-Id: Ic6ab0430324902d7fe42feb491c2b92a13e8bc17 --- packages/SystemUI/res/values-af/strings.xml | 1 + packages/SystemUI/res/values-am/strings.xml | 1 + packages/SystemUI/res/values-ar/strings.xml | 1 + packages/SystemUI/res/values-as/strings.xml | 1 + packages/SystemUI/res/values-az/strings.xml | 1 + packages/SystemUI/res/values-b+sr+Latn/strings.xml | 1 + packages/SystemUI/res/values-be/strings.xml | 1 + packages/SystemUI/res/values-bg/strings.xml | 1 + packages/SystemUI/res/values-bn/strings.xml | 1 + packages/SystemUI/res/values-bs/strings.xml | 1 + packages/SystemUI/res/values-ca/strings.xml | 1 + packages/SystemUI/res/values-cs/strings.xml | 1 + packages/SystemUI/res/values-da/strings.xml | 1 + packages/SystemUI/res/values-de/strings.xml | 1 + packages/SystemUI/res/values-el/strings.xml | 1 + packages/SystemUI/res/values-en-rAU/strings.xml | 1 + packages/SystemUI/res/values-en-rCA/strings.xml | 1 + packages/SystemUI/res/values-en-rGB/strings.xml | 1 + packages/SystemUI/res/values-en-rIN/strings.xml | 1 + packages/SystemUI/res/values-en-rXC/strings.xml | 1 + packages/SystemUI/res/values-es-rUS/strings.xml | 1 + packages/SystemUI/res/values-es/strings.xml | 1 + packages/SystemUI/res/values-et/strings.xml | 1 + packages/SystemUI/res/values-eu/strings.xml | 1 + packages/SystemUI/res/values-fa/strings.xml | 1 + packages/SystemUI/res/values-fi/strings.xml | 1 + packages/SystemUI/res/values-fr-rCA/strings.xml | 1 + packages/SystemUI/res/values-fr/strings.xml | 1 + packages/SystemUI/res/values-gl/strings.xml | 1 + packages/SystemUI/res/values-gu/strings.xml | 1 + packages/SystemUI/res/values-hi/strings.xml | 1 + packages/SystemUI/res/values-hr/strings.xml | 1 + packages/SystemUI/res/values-hu/strings.xml | 1 + packages/SystemUI/res/values-hy/strings.xml | 1 + packages/SystemUI/res/values-in/strings.xml | 1 + packages/SystemUI/res/values-is/strings.xml | 1 + packages/SystemUI/res/values-it/strings.xml | 1 + packages/SystemUI/res/values-iw/strings.xml | 1 + packages/SystemUI/res/values-ja/strings.xml | 1 + packages/SystemUI/res/values-ka/strings.xml | 1 + packages/SystemUI/res/values-kk/strings.xml | 1 + packages/SystemUI/res/values-km/strings.xml | 1 + packages/SystemUI/res/values-kn/strings.xml | 1 + packages/SystemUI/res/values-ko/strings.xml | 1 + packages/SystemUI/res/values-ky/strings.xml | 1 + packages/SystemUI/res/values-lo/strings.xml | 1 + packages/SystemUI/res/values-lt/strings.xml | 1 + packages/SystemUI/res/values-lv/strings.xml | 1 + packages/SystemUI/res/values-mk/strings.xml | 1 + packages/SystemUI/res/values-ml/strings.xml | 1 + packages/SystemUI/res/values-mn/strings.xml | 1 + packages/SystemUI/res/values-mr/strings.xml | 1 + packages/SystemUI/res/values-ms/strings.xml | 1 + packages/SystemUI/res/values-my/strings.xml | 1 + packages/SystemUI/res/values-nb/strings.xml | 1 + packages/SystemUI/res/values-ne/strings.xml | 1 + packages/SystemUI/res/values-nl/strings.xml | 1 + packages/SystemUI/res/values-or/strings.xml | 1 + packages/SystemUI/res/values-pa/strings.xml | 1 + packages/SystemUI/res/values-pl/strings.xml | 1 + packages/SystemUI/res/values-pt-rBR/strings.xml | 1 + packages/SystemUI/res/values-pt-rPT/strings.xml | 1 + packages/SystemUI/res/values-pt/strings.xml | 1 + packages/SystemUI/res/values-ro/strings.xml | 1 + packages/SystemUI/res/values-ru/strings.xml | 1 + packages/SystemUI/res/values-si/strings.xml | 1 + packages/SystemUI/res/values-sk/strings.xml | 1 + packages/SystemUI/res/values-sl/strings.xml | 1 + packages/SystemUI/res/values-sq/strings.xml | 1 + packages/SystemUI/res/values-sr/strings.xml | 1 + packages/SystemUI/res/values-sv/strings.xml | 1 + packages/SystemUI/res/values-sw/strings.xml | 1 + packages/SystemUI/res/values-ta/strings.xml | 1 + packages/SystemUI/res/values-te/strings.xml | 1 + packages/SystemUI/res/values-th/strings.xml | 1 + packages/SystemUI/res/values-tl/strings.xml | 1 + packages/SystemUI/res/values-tr/strings.xml | 1 + packages/SystemUI/res/values-uk/strings.xml | 1 + packages/SystemUI/res/values-ur/strings.xml | 1 + packages/SystemUI/res/values-uz/strings.xml | 1 + packages/SystemUI/res/values-vi/strings.xml | 1 + packages/SystemUI/res/values-zh-rCN/strings.xml | 1 + packages/SystemUI/res/values-zh-rHK/strings.xml | 1 + packages/SystemUI/res/values-zh-rTW/strings.xml | 1 + packages/SystemUI/res/values-zu/strings.xml | 1 + 85 files changed, 85 insertions(+) diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml index 789a5883c238..11e6bc9472f5 100644 --- a/packages/SystemUI/res/values-af/strings.xml +++ b/packages/SystemUI/res/values-af/strings.xml @@ -870,6 +870,7 @@ "tablet" "Saai jou media uit" "Saai tans %1$s uit" + "%1$s loop tans" "Onaktief, gaan program na" "Nie gekry nie" "Kontrole is nie beskikbaar nie" diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index 0faf10cd7ec2..4cb73e31339d 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -870,6 +870,7 @@ "ጡባዊ" "የእርስዎን ሚዲያ cast በማድረግ ላይ" "%1$sን Cast በማድረግ ላይ" + "%1$s እያሄደ ነው" "ንቁ ያልኾነ፣ መተግበሪያን ይፈትሹ" "አልተገኘም" "መቆጣጠሪያ አይገኝም" diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index 7f8b07119663..57c2a947cc9c 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -870,6 +870,7 @@ "جهاز لوحي" "بثّ الوسائط" "جارٍ بثّ \"%1$s\"" + "\"%1$s\" قيد التشغيل" "غير نشط، تحقّق من التطبيق." "لم يتم العثور عليه." "عنصر التحكّم غير متوفّر" diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml index 881c9ca082bb..e5e8213f0ed4 100644 --- a/packages/SystemUI/res/values-as/strings.xml +++ b/packages/SystemUI/res/values-as/strings.xml @@ -870,6 +870,7 @@ "টেবলেট" "আপোনাৰ মিডিয়া কাষ্ট কৰি থকা হৈছে" "%1$s কাষ্ট কৰি থকা হৈছে" + "%1$s চলি আছে" "সক্ৰিয় নহয়, এপ্‌টো পৰীক্ষা কৰক" "বিচাৰি পোৱা নগ’ল" "নিয়ন্ত্ৰণটো উপলব্ধ নহয়" diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml index 684a13e1bec6..ce4cf8f4258b 100644 --- a/packages/SystemUI/res/values-az/strings.xml +++ b/packages/SystemUI/res/values-az/strings.xml @@ -870,6 +870,7 @@ "planşet" "Medianız yayımlanır" "%1$s yayımlanır" + "%1$s işləyir" "Aktiv deyil, tətbiqi yoxlayın" "Tapılmadı" "Nəzarət əlçatan deyil" diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml index 7727d3a174cc..ab7c08d5ac33 100644 --- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml +++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml @@ -870,6 +870,7 @@ "tablet" "Prebacivanje medija" "Prebacuje se %1$s" + "Aplikacija %1$s je pokrenuta" "Neaktivno. Vidite aplikaciju" "Nije pronađeno" "Kontrola nije dostupna" diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml index 73e109d8bfca..3aeda314019f 100644 --- a/packages/SystemUI/res/values-be/strings.xml +++ b/packages/SystemUI/res/values-be/strings.xml @@ -870,6 +870,7 @@ "планшэт" "Трансляцыя мультымедыйнага змесціва" "Трансляцыя праграмы \"%1$s\"" + "%1$s працуе" "Неактыўна, праверце праграму" "Не знойдзена" "Кіраванне недаступнае" diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml index c9e6e991e0ef..312c1520bf5f 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -870,6 +870,7 @@ "таблет" "Мултимедията се предава" "%1$s се предава" + "%1$s се изпълнява" "Неактивно, проверете прилож." "Не е намерено" "Контролата не е налице" diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml index fb4344cc679f..84d70aa1710a 100644 --- a/packages/SystemUI/res/values-bn/strings.xml +++ b/packages/SystemUI/res/values-bn/strings.xml @@ -870,6 +870,7 @@ "ট্যাবলেট" "আপনার মিডিয়া কাস্ট করা" "%1$s কাস্ট করা হচ্ছে" + "%1$s চলছে" "বন্ধ আছে, অ্যাপ চেক করুন" "খুঁজে পাওয়া যায়নি" "কন্ট্রোল উপলভ্য নেই" diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml index 5af4836e9758..42b08e9709a9 100644 --- a/packages/SystemUI/res/values-bs/strings.xml +++ b/packages/SystemUI/res/values-bs/strings.xml @@ -870,6 +870,7 @@ "tablet" "Emitiranje medija" "Emitiranje aplikacije %1$s" + "Aplikacija %1$s je pokrenuta" "Neaktivno, vidite aplikaciju" "Nije pronađeno" "Kontrola nije dostupna" diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index 02b4b38a6f34..222fbc1e00c6 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -870,6 +870,7 @@ "tauleta" "S\'està emetent el contingut multimèdia" "S\'està emetent %1$s" + "%1$s s\'està executant" "Inactiu; comprova l\'aplicació" "No s\'ha trobat" "El control no està disponible" diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index de58c4eab8af..51c47d09e01d 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -870,6 +870,7 @@ "tablet" "Odesílání médií" "Odesílání aplikace %1$s" + "Aplikace %1$s je spuštěna" "Neaktivní, zkontrolujte aplikaci" "Nenalezeno" "Ovládání není k dispozici" diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index 4d5b0cf50e9e..fbef293d2052 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -870,6 +870,7 @@ "tablet" "Caster medie" "Caster %1$s" + "%1$s kører" "Inaktiv. Tjek appen" "Ikke fundet" "Styringselement ikke tilgængeligt" diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index 2fc7ddcd7a03..0d5cf13f8a32 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -870,6 +870,7 @@ "Tablet" "Medien werden gestreamt" "%1$s wird gestreamt" + "%1$s wird ausgeführt" "Inaktiv – sieh in der App nach" "Nicht gefunden" "Steuerelement nicht verfügbar" diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml index fe2a23c70dcf..4a0dca03ec1f 100644 --- a/packages/SystemUI/res/values-el/strings.xml +++ b/packages/SystemUI/res/values-el/strings.xml @@ -870,6 +870,7 @@ "tablet" "Μετάδοση των μέσων σας" "Μετάδοση %1$s" + "Η εφαρμογή %1$s εκτελείται" "Ανενεργό, έλεγχος εφαρμογής" "Δεν βρέθηκε." "Μη διαθέσιμο στοιχείο ελέγχου" diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml index 1a1c202aaa3a..036eed686346 100644 --- a/packages/SystemUI/res/values-en-rAU/strings.xml +++ b/packages/SystemUI/res/values-en-rAU/strings.xml @@ -870,6 +870,7 @@ "tablet" "Casting your media" "Casting %1$s" + "%1$s is running" "Inactive, check app" "Not found" "Control is unavailable" diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml index 7b2eb277feae..ab8bbc4a7e4a 100644 --- a/packages/SystemUI/res/values-en-rCA/strings.xml +++ b/packages/SystemUI/res/values-en-rCA/strings.xml @@ -870,6 +870,7 @@ "tablet" "Casting your media" "Casting %1$s" + "%1$s is running" "Inactive, check app" "Not found" "Control is unavailable" diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml index 1a1c202aaa3a..036eed686346 100644 --- a/packages/SystemUI/res/values-en-rGB/strings.xml +++ b/packages/SystemUI/res/values-en-rGB/strings.xml @@ -870,6 +870,7 @@ "tablet" "Casting your media" "Casting %1$s" + "%1$s is running" "Inactive, check app" "Not found" "Control is unavailable" diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml index 1a1c202aaa3a..036eed686346 100644 --- a/packages/SystemUI/res/values-en-rIN/strings.xml +++ b/packages/SystemUI/res/values-en-rIN/strings.xml @@ -870,6 +870,7 @@ "tablet" "Casting your media" "Casting %1$s" + "%1$s is running" "Inactive, check app" "Not found" "Control is unavailable" diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml index 6b1fc66c2e19..8592e0f4ab27 100644 --- a/packages/SystemUI/res/values-en-rXC/strings.xml +++ b/packages/SystemUI/res/values-en-rXC/strings.xml @@ -870,6 +870,7 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‏‏‎‏‏‏‎‎‏‎‏‏‏‏‎‏‏‏‎‏‎‎‎‎‏‏‏‎‏‎‎‏‎‎‏‏‎‏‎‏‎‎‏‎‎‏‏‎‎‎‏‏‏‎‎‏‎tablet‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‏‏‎‎‎‏‎‏‎‏‎‎‏‎‎‎‎‎‏‎‎‏‎‏‏‏‎‏‏‏‎‏‎‎‎‏‏‎‏‏‎‎‎‏‎‏‎‏‏‏‎‎‏‏‎Casting your media‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‏‎‎‏‏‏‎‏‏‎‏‏‏‏‏‏‎‏‏‏‎‎‏‎‎‏‎‏‎‏‎‏‏‏‏‏‎‏‎‏‎‎‎‎‎‏‏‏‏‎‏‎Casting ‎‏‎‎‏‏‎%1$s‎‏‎‎‏‏‏‎‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‎‎‎‎‏‏‎‏‎‏‏‎‏‏‎‏‎‎‎‎‎‎‏‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‏‏‎‏‎‏‎‏‏‎‎‏‏‎‏‎‎‏‎‎‏‏‎%1$s‎‏‎‎‏‏‏‎ is running‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‎‎‏‎‏‏‎‎‎‏‏‏‎‎‎‏‏‏‎‏‎‎‎‏‎‏‎‏‎‎‏‏‏‏‎‏‏‎‏‎‎‎‎‏‎‎‏‏‏‏‏‏‎‎Inactive, check app‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‎‏‎‎‏‎‎‏‎‏‎‎‎‎‏‎‏‏‏‎‏‎‎‏‎‎‏‏‎‏‎‎‎‏‏‏‏‏‎‏‏‏‏‏‎‎‏‏‎‎‏‏‏‏‎‎Not found‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‏‏‎‎‎‎‏‎‏‏‏‏‎‎‏‏‎‎‎‏‎‎‏‎‏‎‎‏‎‏‎‎‎‎‎‏‎‏‎‎‏‎‎‎‎‏‎‎‏‏‎‎‏‎‏‎‎Control is unavailable‎‏‎‎‏‎" diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index 23f922fb0b7e..aee41909e975 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -870,6 +870,7 @@ "tablet" "Transmitiendo tu contenido multimedia" "Transmitiendo %1$s" + "%1$s se está ejecutando" "Inactivo. Verifica la app" "No se encontró" "El control no está disponible" diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml index 0fe2cdcf400a..9961ac554922 100644 --- a/packages/SystemUI/res/values-es/strings.xml +++ b/packages/SystemUI/res/values-es/strings.xml @@ -870,6 +870,7 @@ "tablet" "Enviando tu contenido multimedia" "Enviando %1$s" + "%1$s se está ejecutando" "Inactivo, comprobar aplicación" "No se ha encontrado" "Control no disponible" diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml index a1ccd82aabb6..53deebab3b03 100644 --- a/packages/SystemUI/res/values-et/strings.xml +++ b/packages/SystemUI/res/values-et/strings.xml @@ -870,6 +870,7 @@ "tahvelarvuti" "Teie meedia ülekandmine" "Rakenduse %1$s ülekandmine" + "%1$s töötab" "Passiivne, vaadake rakendust" "Ei leitud" "Juhtelement pole saadaval" diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml index aec32a5e1166..60977e251fd9 100644 --- a/packages/SystemUI/res/values-eu/strings.xml +++ b/packages/SystemUI/res/values-eu/strings.xml @@ -870,6 +870,7 @@ "tableta" "Multimedia-edukia igortzen" "%1$s aplikazioa igortzen" + "%1$s abian da" "Inaktibo; egiaztatu aplikazioa" "Ez da aurkitu" "Ez dago erabilgarri kontrolatzeko aukera" diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index e8ff3bfbefd3..67df7cd7c252 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -870,6 +870,7 @@ "رایانه لوحی" "پخش محتوای رسانه‌ها" "پخش محتوای %1$s" + "%1$s در حال اجرا است" "غیرفعال، برنامه را بررسی کنید" "پیدا نشد" "کنترل دردسترس نیست" diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml index 2f9ebaea47da..d4098f95134d 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -870,6 +870,7 @@ "tabletti" "Striimataan mediaa" "Striimataan %1$s" + "%1$s on käynnissä" "Epäaktiivinen, tarkista sovellus" "Ei löydy" "Ohjain ei ole käytettävissä" diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml index 8845ac7b90e8..a62a7ba7db09 100644 --- a/packages/SystemUI/res/values-fr-rCA/strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/strings.xml @@ -870,6 +870,7 @@ "tablette" "Diffusion de votre contenu multimédia en cours…" "Diffusion de %1$s en cours…" + "%1$s en cours d\'exécution" "Délai expiré, vérifiez l\'appli" "Introuvable" "La commande n\'est pas accessible" diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml index 68c5b51cb123..4180984cfc30 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -870,6 +870,7 @@ "tablette" "Casting de vos contenus multimédias" "Casting de %1$s" + "%1$s est en cours d\'exécution" "Délai expiré, vérifier l\'appli" "Introuvable" "Commande indisponible" diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml index 9078e51b3b3a..5c6b181fa79c 100644 --- a/packages/SystemUI/res/values-gl/strings.xml +++ b/packages/SystemUI/res/values-gl/strings.xml @@ -870,6 +870,7 @@ "tableta" "Emitindo contido multimedia" "Emitindo %1$s" + "%1$s estase executando" "Inactivo. Comproba a app" "Non se atopou" "O control non está dispoñible" diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml index 4bf9078ebd20..f6263a7f9379 100644 --- a/packages/SystemUI/res/values-gu/strings.xml +++ b/packages/SystemUI/res/values-gu/strings.xml @@ -870,6 +870,7 @@ "ટૅબ્લેટ" "તમારું મીડિયા કાસ્ટ કરી રહ્યાં છીએ" "%1$s કાસ્ટ કરી રહ્યાં છીએ" + "%1$s ચાલી રહી છે" "નિષ્ક્રિય, ઍપને ચેક કરો" "મળ્યું નથી" "નિયંત્રણ ઉપલબ્ધ નથી" diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index e95788cd2d49..462b21ece540 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -870,6 +870,7 @@ "टैबलेट" "आपका मीडिया कास्ट किया जा रहा है" "%1$s को कास्ट किया जा रहा है" + "%1$s चालू है" "काम नहीं कर रहा, ऐप जांचें" "कंट्रोल नहीं है" "कंट्रोल मौजूद नहीं है" diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml index c53be7eebded..08e7a3a21b6e 100644 --- a/packages/SystemUI/res/values-hr/strings.xml +++ b/packages/SystemUI/res/values-hr/strings.xml @@ -870,6 +870,7 @@ "tablet" "Emitiranje medijskih sadržaja" "Emitiranje aplikacije %1$s" + "Aplikacija %1$s je pokrenuta" "Neaktivno, provjerite aplik." "Nije pronađeno" "Kontrola nije dostupna" diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml index d45491f7b6e0..408a834796ad 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -870,6 +870,7 @@ "táblagép" "A médiatartalom átküldése folyamatban van" "%1$s átküldése" + "A(z) %1$s jelenleg fut" "Inaktív, ellenőrizze az appot" "Nem található" "Nem hozzáférhető vezérlő" diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml index a43ccb234b5c..b55187026de7 100644 --- a/packages/SystemUI/res/values-hy/strings.xml +++ b/packages/SystemUI/res/values-hy/strings.xml @@ -870,6 +870,7 @@ "պլանշետ" "Մեդիա բովանդակության հեռարձակում" "%1$s հավելվածի հեռարձակում" + "%1$s հավելվածն աշխատում է" "Ակտիվ չէ, ստուգեք հավելվածը" "Չի գտնվել" "Կառավարման տարրը հասանելի չէ" diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index 6be304c059b6..35a6be13971b 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -870,6 +870,7 @@ "tablet" "Mentransmisikan media" "Mentransmisikan %1$s" + "%1$s sedang berjalan" "Nonaktif, periksa aplikasi" "Tidak ditemukan" "Kontrol tidak tersedia" diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml index ad803ca1f97f..f69838470787 100644 --- a/packages/SystemUI/res/values-is/strings.xml +++ b/packages/SystemUI/res/values-is/strings.xml @@ -870,6 +870,7 @@ "spjaldtölva" "Sendir út efni frá þér" "Sendir út %1$s" + "%1$s er í gangi" "Óvirkt, athugaðu forrit" "Fannst ekki" "Stýring er ekki tiltæk" diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml index 2d6f5bbf92a8..46b6f25f5d07 100644 --- a/packages/SystemUI/res/values-it/strings.xml +++ b/packages/SystemUI/res/values-it/strings.xml @@ -870,6 +870,7 @@ "tablet" "Trasmissione di contenuti multimediali" "Trasmissione di %1$s" + "%1$s è in esecuzione" "Inattivo, controlla l\'app" "Controllo non trovato" "Il controllo non è disponibile" diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml index b4cb279deef5..bb5cb1e87130 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -870,6 +870,7 @@ "טאבלט" "‏העברה (cast) של מדיה" "‏מתבצעת העברה (cast) של %1$s" + "אפליקציית %1$s פועלת" "לא פעיל, יש לבדוק את האפליקציה" "לא נמצא" "הפקד לא זמין" diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml index a7eab27d1c9b..4223496e2e1f 100644 --- a/packages/SystemUI/res/values-ja/strings.xml +++ b/packages/SystemUI/res/values-ja/strings.xml @@ -870,6 +870,7 @@ "タブレット" "メディアをキャストしています" "%1$s をキャストしています" + "%1$s を実行しています" "無効: アプリをご確認ください" "見つかりませんでした" "コントロールを使用できません" diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml index 7188d682fa35..a8d8ddc59de6 100644 --- a/packages/SystemUI/res/values-ka/strings.xml +++ b/packages/SystemUI/res/values-ka/strings.xml @@ -870,6 +870,7 @@ "ტაბლეტი" "მედიის ტრანსლირება" "მიმდინარეობს %1$s-ის ტრანსლირება" + "%1$s გაშვებულია" "არააქტიურია, გადაამოწმეთ აპი" "ვერ მოიძებნა" "კონტროლი მიუწვდომელია" diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml index 915c5bcbebe6..48aa40e0f57b 100644 --- a/packages/SystemUI/res/values-kk/strings.xml +++ b/packages/SystemUI/res/values-kk/strings.xml @@ -870,6 +870,7 @@ "планшет" "Медиаконтентті трансляциялау" "Трансляция: %1$s" + "%1$s қосулы тұр" "Өшірулі. Қолданба тексеріңіз." "Табылмады" "Басқару виджеті қолжетімсіз" diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml index e853bea9887f..b6d5d54feed3 100644 --- a/packages/SystemUI/res/values-km/strings.xml +++ b/packages/SystemUI/res/values-km/strings.xml @@ -870,6 +870,7 @@ "ថេប្លេត" "កំពុងភ្ជាប់មេឌៀ​របស់អ្នក" "កំពុង​ភ្ជាប់ %1$s" + "%1$s កំពុង​ដំណើរការ" "អសកម្ម ពិនិត្យមើល​កម្មវិធី" "រកមិន​ឃើញទេ" "មិនអាច​គ្រប់គ្រង​បានទេ" diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml index 4f322de07bbc..652d706f69ec 100644 --- a/packages/SystemUI/res/values-kn/strings.xml +++ b/packages/SystemUI/res/values-kn/strings.xml @@ -870,6 +870,7 @@ "ಟ್ಯಾಬ್ಲೆಟ್" "ನಿಮ್ಮ ಮಾಧ್ಯಮವನ್ನು ಬಿತ್ತರಿಸಲಾಗುತ್ತಿದೆ" "%1$s ಅನ್ನು ಬಿತ್ತರಿಸಲಾಗುತ್ತಿದೆ" + "%1$s ರನ್ ಆಗುತ್ತಿದೆ" "ನಿಷ್ಕ್ರಿಯ, ಆ್ಯಪ್ ಪರಿಶೀಲಿಸಿ" "ಕಂಡುಬಂದಿಲ್ಲ" "ನಿಯಂತ್ರಣ ಲಭ್ಯವಿಲ್ಲ" diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index be4ef11b3de6..1e8241e99192 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -870,6 +870,7 @@ "태블릿" "미디어 전송" "%1$s 전송 중" + "%1$s 실행 중" "비활성. 앱을 확인하세요." "찾을 수 없음" "컨트롤을 사용할 수 없음" diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml index a5e3dc61d33b..e710c929a5d3 100644 --- a/packages/SystemUI/res/values-ky/strings.xml +++ b/packages/SystemUI/res/values-ky/strings.xml @@ -870,6 +870,7 @@ "планшет" "Медиа тышкы экранга чыгарылууда" "%1$s түзмөгүнө чыгарылууда" + "%1$s иштеп жатат" "Жигерсиз. Колдонмону текшериңиз" "Табылган жок" "Башкара албайсыз" diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml index 01e35cf7ec3d..983463dc99b2 100644 --- a/packages/SystemUI/res/values-lo/strings.xml +++ b/packages/SystemUI/res/values-lo/strings.xml @@ -870,6 +870,7 @@ "ແທັບເລັດ" "ການ​ກຳ​ນົດ​ບົດ​ບາດ​ສື່​ຂອງ​ທ່ານ" "ການ​ກຳ​ນົດ​ບົດ​ບາດ %1$s" + "%1$s ກຳລັງເຮັດວຽກຢູ່" "ບໍ່ເຮັດວຽກ, ກະລຸນາກວດສອບແອັບ" "ບໍ່ພົບ" "ບໍ່ສາມາດໃຊ້ການຄວບຄຸມໄດ້" diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml index 893c97802a7c..b1717c0668e2 100644 --- a/packages/SystemUI/res/values-lt/strings.xml +++ b/packages/SystemUI/res/values-lt/strings.xml @@ -870,6 +870,7 @@ "planšetinis kompiuteris" "Perduodama medija" "Perduodama programa „%1$s“" + "„%1$s“ vykdoma" "Neaktyvu, patikrinkite progr." "Nerasta" "Valdiklis nepasiekiamas" diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml index befcfbe1b105..60fbf08ff443 100644 --- a/packages/SystemUI/res/values-lv/strings.xml +++ b/packages/SystemUI/res/values-lv/strings.xml @@ -870,6 +870,7 @@ "planšetdators" "Notiek multivides satura apraide" "Notiek lietotnes %1$s apraide" + "Lietotne %1$s darbojas" "Neaktīva, pārbaudiet lietotni" "Netika atrasta" "Vadīkla nav pieejama" diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml index ca1ac647467b..2c66f12c34b4 100644 --- a/packages/SystemUI/res/values-mk/strings.xml +++ b/packages/SystemUI/res/values-mk/strings.xml @@ -870,6 +870,7 @@ "таблет" "Емитување на вашите аудиовизуелни содржини" "Се емитува %1$s" + "%1$s работи" "Неактивна, провери апликација" "Не е најдено" "Контролата не е достапна" diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml index 102d81ea0653..d597bb51792f 100644 --- a/packages/SystemUI/res/values-ml/strings.xml +++ b/packages/SystemUI/res/values-ml/strings.xml @@ -870,6 +870,7 @@ "ടാബ്‌ലെറ്റ്" "നിങ്ങളുടെ മീഡിയ കാസ്റ്റ് ചെയ്യുന്നു" "%1$s കാസ്‌റ്റ് ചെയ്യുന്നു" + "%1$s റൺ ചെയ്യുന്നു" "നിഷ്‌ക്രിയം, ആപ്പ് പരിശോധിക്കൂ" "കണ്ടെത്തിയില്ല" "നിയന്ത്രണം ലഭ്യമല്ല" diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml index 020fe3fd27a6..703a80a8498e 100644 --- a/packages/SystemUI/res/values-mn/strings.xml +++ b/packages/SystemUI/res/values-mn/strings.xml @@ -870,6 +870,7 @@ "таблет" "Таны медиаг дамжуулж байна" "%1$s-г дамжуулж байна" + "%1$s ажиллаж байна" "Идэвхгүй байна, аппыг шалгана уу" "Олдсонгүй" "Хяналт боломжгүй байна" diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml index 13111e2685c1..a3645bf31750 100644 --- a/packages/SystemUI/res/values-mr/strings.xml +++ b/packages/SystemUI/res/values-mr/strings.xml @@ -870,6 +870,7 @@ "टॅबलेट" "तुमचा मीडिया कास्ट करत आहे" "%1$s कास्ट करत आहे" + "%1$s रन होत आहे" "निष्क्रिय, ॲप तपासा" "आढळले नाही" "नियंत्रण उपलब्ध नाही" diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml index 526c0503e89f..35f7ddc67b88 100644 --- a/packages/SystemUI/res/values-ms/strings.xml +++ b/packages/SystemUI/res/values-ms/strings.xml @@ -870,6 +870,7 @@ "tablet" "Menghantar media anda" "Menghantar %1$s" + "%1$s sedang dijalankan" "Tidak aktif, semak apl" "Tidak ditemukan" "Kawalan tidak tersedia" diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml index b93725deffa8..f0303366ea83 100644 --- a/packages/SystemUI/res/values-my/strings.xml +++ b/packages/SystemUI/res/values-my/strings.xml @@ -870,6 +870,7 @@ "တက်ဘလက်" "သင့်မီဒီယာကို ကာစ်လုပ်နေသည်" "%1$s ကို ကာစ်လုပ်နေသည်" + "%1$s ပွင့်နေပါသည်" "ရပ်နေသည်၊ အက်ပ်ကို စစ်ဆေးပါ" "မတွေ့ပါ" "ထိန်းချုပ်မှု မရနိုင်ပါ" diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index e04c3d7ba09b..feb0e915510c 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -870,6 +870,7 @@ "nettbrett" "Caster mediene" "Caster %1$s" + "%1$s kjører" "Inaktiv. Sjekk appen" "Ikke funnet" "Kontrollen er utilgjengelig" diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml index fc5a2ec0afa0..eca32825c031 100644 --- a/packages/SystemUI/res/values-ne/strings.xml +++ b/packages/SystemUI/res/values-ne/strings.xml @@ -870,6 +870,7 @@ "ट्याब्लेट" "तपाईंको मिडिया कास्ट गरिँदै छ" "%1$s कास्ट गरिँदै छ" + "%1$s चलिरहेको छ" "निष्क्रिय छ, एप जाँच गर्नु…" "फेला परेन" "नियन्त्रण उपलब्ध छैन" diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml index 2682ec8df56a..f47dfc6b42db 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -870,6 +870,7 @@ "tablet" "Je media casten" "%1$s casten" + "%1$s is actief" "Inactief, check de app" "Niet gevonden" "Beheeroptie niet beschikbaar" diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml index cc69f846682f..3fd9707e3c80 100644 --- a/packages/SystemUI/res/values-or/strings.xml +++ b/packages/SystemUI/res/values-or/strings.xml @@ -870,6 +870,7 @@ "ଟାବଲେଟ" "ଆପଣଙ୍କ ମିଡିଆକୁ କାଷ୍ଟ କରାଯାଉଛି" "%1$sକୁ କାଷ୍ଟ କରାଯାଉଛି" + "%1$s ଚାଲୁଛି" "ନିଷ୍କ୍ରିୟ ଅଛି, ଆପ ଯାଞ୍ଚ କରନ୍ତୁ" "ମିଳିଲା ନାହିଁ" "ନିୟନ୍ତ୍ରଣ ଉପଲବ୍ଧ ନାହିଁ" diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml index a1d5d9ff9e9a..480efbd3bc17 100644 --- a/packages/SystemUI/res/values-pa/strings.xml +++ b/packages/SystemUI/res/values-pa/strings.xml @@ -870,6 +870,7 @@ "ਟੈਬਲੈੱਟ" "ਤੁਹਾਡੇ ਮੀਡੀਆ ਨੂੰ ਕਾਸਟ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ" "%1$s \'ਤੇ ਕਾਸਟ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ" + "%1$s ਚੱਲ ਰਿਹਾ ਹੈ" "ਅਕਿਰਿਆਸ਼ੀਲ, ਐਪ ਦੀ ਜਾਂਚ ਕਰੋ" "ਨਹੀਂ ਮਿਲਿਆ" "ਕੰਟਰੋਲ ਉਪਲਬਧ ਨਹੀਂ ਹੈ" diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index ec4cecb0fa35..2e038de647f0 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -870,6 +870,7 @@ "tablet" "Przesyłanie multimediów" "Przesyłanie treści z aplikacji %1$s" + "Aplikacja %1$s jest uruchomiona" "Nieaktywny, sprawdź aplikację" "Nie znaleziono" "Element jest niedostępny" diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml index f950f36014de..aee0962ccba1 100644 --- a/packages/SystemUI/res/values-pt-rBR/strings.xml +++ b/packages/SystemUI/res/values-pt-rBR/strings.xml @@ -870,6 +870,7 @@ "tablet" "Transmitindo sua mídia" "Transmitindo o app %1$s" + "%1$s está em execução" "Inativo, verifique o app" "Não encontrado" "O controle está indisponível" diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index 78f179d26cc1..3203bf81abb6 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -870,6 +870,7 @@ "tablet" "A transmitir o conteúdo multimédia" "A transmitir a app %1$s…" + "%1$s em execução" "Inativa. Consulte a app." "Não encontrado." "O controlo está indisponível" diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml index f950f36014de..aee0962ccba1 100644 --- a/packages/SystemUI/res/values-pt/strings.xml +++ b/packages/SystemUI/res/values-pt/strings.xml @@ -870,6 +870,7 @@ "tablet" "Transmitindo sua mídia" "Transmitindo o app %1$s" + "%1$s está em execução" "Inativo, verifique o app" "Não encontrado" "O controle está indisponível" diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index b8146da2c334..dc7f42d38fe1 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -870,6 +870,7 @@ "tabletă" "Se proiectează conținutul media" "Se proiectează %1$s" + "%1$s rulează" "Inactiv, verifică aplicația" "Nu s-a găsit" "Comanda este indisponibilă" diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index 771b7202dc2d..edd9df3a95f1 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -870,6 +870,7 @@ "планшет" "Трансляция медиаконтента" "Трансляция: %1$s" + "Приложение \"%1$s\" запущено" "Нет ответа. Проверьте приложение." "Не найдено." "Управление недоступно" diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml index 3859f471c585..cb355bdd754e 100644 --- a/packages/SystemUI/res/values-si/strings.xml +++ b/packages/SystemUI/res/values-si/strings.xml @@ -870,6 +870,7 @@ "ටැබ්ලටය" "ඔබේ මාධ්‍ය විකාශය කිරීම" "%1$s විකාශය කරමින්" + "%1$s ධාවනය වේ" "අක්‍රියයි, යෙදුම පරීක්ෂා කරන්න" "හමු නොවිණි" "පාලනය ලබා ගත නොහැකිය" diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index f2262599e968..daf5d72e1c7b 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -870,6 +870,7 @@ "tablet" "Prenášajú sa médiá" "Prenáša sa %1$s" + "Aplikácia %1$s je spustená" "Neaktívne, preverte aplikáciu" "Nenájdené" "Ovládač nie je k dispozícii" diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml index ca9de63e5ed1..5cf8bab9f336 100644 --- a/packages/SystemUI/res/values-sl/strings.xml +++ b/packages/SystemUI/res/values-sl/strings.xml @@ -870,6 +870,7 @@ "tablični računalnik" "Predvajanje predstavnosti" "Predvajanje aplikacije %1$s" + "%1$s se izvaja" "Neaktivno, poglejte aplikacijo" "Ni mogoče najti" "Kontrolnik ni na voljo" diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml index a348385550cd..6cfa7387c1fe 100644 --- a/packages/SystemUI/res/values-sq/strings.xml +++ b/packages/SystemUI/res/values-sq/strings.xml @@ -870,6 +870,7 @@ "tablet" "Po transmeton median tënde" "Po transmeton %1$s" + "%1$s po ekzekutohet" "Joaktive, kontrollo aplikacionin" "Nuk u gjet" "Kontrolli është i padisponueshëm" diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml index fef43eb6b637..de183197710e 100644 --- a/packages/SystemUI/res/values-sr/strings.xml +++ b/packages/SystemUI/res/values-sr/strings.xml @@ -870,6 +870,7 @@ "таблет" "Пребацивање медија" "Пребацује се %1$s" + "Апликација %1$s је покренута" "Неактивно. Видите апликацију" "Није пронађено" "Контрола није доступна" diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index 3d1ed13cc946..e8733f445246 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -870,6 +870,7 @@ "surfplatta" "Castar din media" "Castar %1$s" + "%1$s körs" "Inaktiv, kolla appen" "Hittades inte" "Styrning är inte tillgänglig" diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index b3d71a5349da..f4c398420778 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -870,6 +870,7 @@ "kompyuta kibao" "Inatuma maudhui yako" "Inatuma %1$s" + "%1$s inatumika" "Haitumiki, angalia programu" "Hakipatikani" "Kidhibiti hakipatikani" diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml index 0c62d2522619..4e3b166f5264 100644 --- a/packages/SystemUI/res/values-ta/strings.xml +++ b/packages/SystemUI/res/values-ta/strings.xml @@ -870,6 +870,7 @@ "டேப்லெட்" "உங்கள் மீடியா அலைபரப்பப்படுகிறது" "%1$s ஆப்ஸை அலைபரப்புகிறது" + "%1$s இயங்கிக் கொண்டிருக்கிறது" "செயலில் இல்லை , சரிபார்க்கவும்" "இல்லை" "கட்டுப்பாடு இல்லை" diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml index 473ecfb8f0b4..3e09cfc92d5c 100644 --- a/packages/SystemUI/res/values-te/strings.xml +++ b/packages/SystemUI/res/values-te/strings.xml @@ -870,6 +870,7 @@ "టాబ్లెట్" "మీ మీడియా ప్రసారం అవుతోంది" "%1$s ప్రసారం అవుతోంది" + "%1$s రన్ అవుతోంది" "ఇన్‌యాక్టివ్, యాప్ చెక్ చేయండి" "కనుగొనబడలేదు" "కంట్రోల్ అందుబాటులో లేదు" diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index c523d97ee7a3..7de78da9d9c1 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -870,6 +870,7 @@ "แท็บเล็ต" "กำลังแคสต์สื่อ" "กำลังแคสต์ %1$s" + "%1$s กำลังทำงาน" "ไม่มีการใช้งาน โปรดตรวจสอบแอป" "ไม่พบ" "ใช้การควบคุมไม่ได้" diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml index 93f817757230..25added71e71 100644 --- a/packages/SystemUI/res/values-tl/strings.xml +++ b/packages/SystemUI/res/values-tl/strings.xml @@ -870,6 +870,7 @@ "tablet" "Pag-cast ng iyong media" "Kina-cast ang %1$s" + "Tumatakbo ang %1$s" "Hindi aktibo, tingnan ang app" "Hindi nahanap" "Hindi available ang kontrol" diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index c2f76f68ae36..90a3dad503ca 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -870,6 +870,7 @@ "tablet" "Medyanız yayınlanıyor" "%1$s yayınlanıyor" + "%1$s çalışıyor" "Devre dışı, uygulamaya bakın" "Bulunamadı" "Kontrol kullanılamıyor" diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index 56ac8a6a37e7..63a581db332f 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -870,6 +870,7 @@ "планшет" "Трансляція медіаконтенту" "Трансляція додатка %1$s" + "%1$s працює" "Неактивно, перейдіть у додаток" "Не знайдено" "Елемент керування недоступний" diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml index c1d5740a098b..c07e9cfc6286 100644 --- a/packages/SystemUI/res/values-ur/strings.xml +++ b/packages/SystemUI/res/values-ur/strings.xml @@ -870,6 +870,7 @@ "ٹیبلیٹ" "آپ کا میڈیا کاسٹ ہو رہا ہے" "%1$s کاسٹ ہو رہا ہے" + "%1$s چل رہی ہے" "غیر فعال، ایپ چیک کریں" "نہیں ملا" "کنٹرول دستیاب نہیں ہے" diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml index 3a292880701a..d320e18ec9f0 100644 --- a/packages/SystemUI/res/values-uz/strings.xml +++ b/packages/SystemUI/res/values-uz/strings.xml @@ -870,6 +870,7 @@ "planshet" "Mediani translatsiya qilish" "Translatsiya qilinmoqda: %1$s" + "%1$s ishlamoqda" "Nofaol. Ilovani tekshiring" "Topilmadi" "Boshqarish imkonsiz" diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index 5cd6afe8f198..99f54fc19f6d 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -870,6 +870,7 @@ "máy tính bảng" "Truyền nội dung đa phương tiện" "Đang truyền %1$s" + "%1$s đang chạy" "Không hoạt động, hãy kiểm tra ứng dụng" "Không tìm thấy" "Không có chức năng điều khiển" diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml index 41234463fa0a..64653abd6f03 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -870,6 +870,7 @@ "平板电脑" "投放您的媒体" "投放 %1$s" + "“%1$s”正在运行" "无效,请检查应用" "未找到" "控件不可用" diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml index e2ae520e7e0f..5094c48194fc 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -870,6 +870,7 @@ "平板電腦" "投放媒體" "投放 %1$s 內容" + "「%1$s」執行中" "已停用,請檢查應用程式" "找不到" "無法使用控制功能" diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index 97769f99d478..5069606ad861 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -870,6 +870,7 @@ "平板電腦" "投放媒體" "投放「%1$s」的內容" + "「%1$s」執行中" "無效,請查看應用程式" "找不到控制項" "無法使用控制項" diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml index 281e57e4b972..646ad8bca1bb 100644 --- a/packages/SystemUI/res/values-zu/strings.xml +++ b/packages/SystemUI/res/values-zu/strings.xml @@ -870,6 +870,7 @@ "ithebulethi" "Isakaza imidiya yakho" "Isakaza i-%1$s" + "I-%1$s iyasebenza" "Akusebenzi, hlola uhlelo lokusebenza" "Ayitholakali" "Ukulawula akutholakali" -- cgit v1.2.3 From 69f018b5f8440d5c69d0495b22cb4905214f50fa Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Mon, 3 Jul 2023 16:29:47 +0000 Subject: DO NOT MERGE Revert "Verify URI permissions for EXTRA_REMOTE_INPUT_HISTORY_ITEMS." This reverts commit 43b1711332763788c7abf05c3baa931296c45bbb. Reason for revert: regression reported at b/289223315 Bug: 289223315 Bug: 276729064 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:f69ded9ec319f753d1464586ee28248b84a2bacd) Merged-In: I101938fbc51592537023345ba1e642827510981b Change-Id: I101938fbc51592537023345ba1e642827510981b --- core/java/android/app/Notification.java | 11 ----------- .../server/notification/NotificationManagerServiceTest.java | 11 ----------- 2 files changed, 22 deletions(-) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 5584003dcad3..61f14dffdbb9 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2861,17 +2861,6 @@ 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) { 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 bd74c2ea8262..fa686fc90e70 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -129,7 +129,6 @@ 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; @@ -5416,12 +5415,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .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); @@ -5429,8 +5422,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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") @@ -5448,8 +5439,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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 -- cgit v1.2.3 From 8f00d3e9152838197afd8a87f75f14a7f18fbe5d Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Mon, 8 May 2023 18:39:35 +0000 Subject: Verify URI permissions for EXTRA_REMOTE_INPUT_HISTORY_ITEMS. Also added a step to serialize & deserialize the notification in the test, to prevent exceptions about not being able to cast e.g. Parcelable[] to RemoteInputHistoryItem[]. Test: atest NotificationManagerServiceTest & tested with POC from bug Bug: 276729064 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:4e19431a60300c6ea6c7f7dd64299916e4eb09bc) Merged-In: I7053ca59f9c7f1df5226418594109cfb8b609b1e Change-Id: I7053ca59f9c7f1df5226418594109cfb8b609b1e --- core/java/android/app/Notification.java | 12 ++++++++++++ .../notification/NotificationManagerServiceTest.java | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 61f14dffdbb9..a0fe6c504932 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2861,6 +2861,18 @@ public class Notification implements Parcelable if (person != null) { visitor.accept(person.getIconUri()); } + + final RemoteInputHistoryItem[] history = extras.getParcelableArray( + Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS, + RemoteInputHistoryItem.class); + 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) { 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 fa686fc90e70..39b215d9a2ec 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -129,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; @@ -5415,6 +5416,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .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); @@ -5422,6 +5429,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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") @@ -5430,6 +5439,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .addExtras(extras) .build(); + // Serialize and deserialize the notification to make sure nothing breaks in the process, + // since that's what will usually happen before we get to call visitUris. + Parcel parcel = Parcel.obtain(); + n.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + n = new Notification(parcel); + Consumer visitor = (Consumer) spy(Consumer.class); n.visitUris(visitor); verify(visitor, times(1)).accept(eq(audioContents)); @@ -5439,6 +5455,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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 -- cgit v1.2.3 From 3d27bd3512b9b7870482488c75bb2a34e46bfe6b Mon Sep 17 00:00:00 2001 From: Josep del Rio Date: Mon, 26 Jun 2023 09:30:06 +0000 Subject: Do not share key mappings with JNI object The key mapping information between the native key mappings and the KeyCharacterMap object available in Java is currently shared, which means that a read can be attempted while it's being modified. Bug: 274058082 Test: Patch tested by Oppo (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:3d993de0d1ada8065d1fe561f690c8f82b6a7d4b) Merged-In: I745008a0a8ea30830660c45dcebee917b3913d13 Change-Id: I745008a0a8ea30830660c45dcebee917b3913d13 --- core/jni/android_view_InputDevice.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp index 9cc72437a023..f7c770e0bffb 100644 --- a/core/jni/android_view_InputDevice.cpp +++ b/core/jni/android_view_InputDevice.cpp @@ -42,6 +42,13 @@ jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& devi return NULL; } + // b/274058082: Pass a copy of the key character map to avoid concurrent + // access + std::shared_ptr map = deviceInfo.getKeyCharacterMap(); + if (map != nullptr) { + map = std::make_shared(*map); + } + ScopedLocalRef descriptorObj(env, env->NewStringUTF(deviceInfo.getIdentifier().descriptor.c_str())); if (!descriptorObj.get()) { @@ -49,8 +56,8 @@ jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& devi } ScopedLocalRef kcmObj(env, - android_view_KeyCharacterMap_create(env, deviceInfo.getId(), - deviceInfo.getKeyCharacterMap())); + android_view_KeyCharacterMap_create(env, deviceInfo.getId(), + map)); if (!kcmObj.get()) { return NULL; } -- cgit v1.2.3 From 950b1dd5dfee368f835687d2a28fcb26c1c247b1 Mon Sep 17 00:00:00 2001 From: Tim Yu Date: Tue, 20 Jun 2023 21:24:36 +0000 Subject: [DO NOT MERGE] Verify URI Permissions in Autofill RemoteViews Check permissions of URI inside of FillResponse's RemoteViews. If the current user does not have the required permissions to view the URI, the RemoteView is dropped from displaying. This fixes a security spill in which a user can view content of another user through a malicious Autofill provider. Bug: 283137865 Fixes: b/283264674 b/281666022 b/281665050 b/281848557 b/281533566 b/281534749 b/283101289 Test: Verified by POC app attached in bugs (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:60a0e4f12a1e1ebc609e200ecbb7f80dcb5c1319) Merged-In: I6f4d2a35e89bbed7bd9e07bf5cd3e2d68b20af9a Change-Id: I6f4d2a35e89bbed7bd9e07bf5cd3e2d68b20af9a --- .../java/com/android/server/autofill/Helper.java | 43 ++++++++++++++++++++++ .../android/server/autofill/ui/DialogFillUi.java | 12 ++++-- .../com/android/server/autofill/ui/FillUi.java | 11 ++++-- .../com/android/server/autofill/ui/SaveUi.java | 3 +- 4 files changed, 60 insertions(+), 9 deletions(-) diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java index bc5d6457c945..48113a81cca5 100644 --- a/services/autofill/java/com/android/server/autofill/Helper.java +++ b/services/autofill/java/com/android/server/autofill/Helper.java @@ -18,6 +18,8 @@ package com.android.server.autofill; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.app.ActivityManager; import android.app.assist.AssistStructure; import android.app.assist.AssistStructure.ViewNode; import android.app.assist.AssistStructure.WindowNode; @@ -34,6 +36,7 @@ import android.view.View; import android.view.WindowManager; import android.view.autofill.AutofillId; import android.view.autofill.AutofillValue; +import android.widget.RemoteViews; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.ArrayUtils; @@ -42,6 +45,8 @@ import java.io.PrintWriter; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; +import java.util.concurrent.atomic.AtomicBoolean; + public final class Helper { @@ -75,6 +80,44 @@ public final class Helper { throw new UnsupportedOperationException("contains static members only"); } + private static boolean checkRemoteViewUriPermissions( + @UserIdInt int userId, @NonNull RemoteViews rView) { + final AtomicBoolean permissionsOk = new AtomicBoolean(true); + + rView.visitUris(uri -> { + int uriOwnerId = android.content.ContentProvider.getUserIdFromUri(uri); + boolean allowed = uriOwnerId == userId; + permissionsOk.set(allowed && permissionsOk.get()); + }); + + return permissionsOk.get(); + } + + /** + * Checks the URI permissions of the remote view, + * to see if the current userId is able to access it. + * + * Returns the RemoteView that is passed if user is able, null otherwise. + * + * TODO: instead of returning a null remoteview when + * the current userId cannot access an URI, + * return a new RemoteView with the URI removed. + */ + public static @Nullable RemoteViews sanitizeRemoteView(RemoteViews rView) { + if (rView == null) return null; + + int userId = ActivityManager.getCurrentUser(); + + boolean ok = checkRemoteViewUriPermissions(userId, rView); + if (!ok) { + Slog.w(TAG, + "sanitizeRemoteView() user: " + userId + + " tried accessing resource that does not belong to them"); + } + return (ok ? rView : null); + } + + @Nullable static AutofillId[] toArray(@Nullable ArraySet set) { if (set == null) return null; diff --git a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java index c2c630e01bee..59184e9ed288 100644 --- a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java @@ -52,6 +52,7 @@ import android.widget.TextView; import com.android.internal.R; import com.android.server.autofill.AutofillManagerService; +import com.android.server.autofill.Helper; import java.io.PrintWriter; import java.util.ArrayList; @@ -197,7 +198,8 @@ final class DialogFillUi { } private void setHeader(View decor, FillResponse response) { - final RemoteViews presentation = response.getDialogHeader(); + final RemoteViews presentation = + Helper.sanitizeRemoteView(response.getDialogHeader()); if (presentation == null) { return; } @@ -232,9 +234,10 @@ final class DialogFillUi { } private void initialAuthenticationLayout(View decor, FillResponse response) { - RemoteViews presentation = response.getDialogPresentation(); + RemoteViews presentation = Helper.sanitizeRemoteView( + response.getDialogPresentation()); if (presentation == null) { - presentation = response.getPresentation(); + presentation = Helper.sanitizeRemoteView(response.getPresentation()); } if (presentation == null) { throw new RuntimeException("No presentation for fill dialog authentication"); @@ -278,7 +281,8 @@ final class DialogFillUi { final Dataset dataset = response.getDatasets().get(i); final int index = dataset.getFieldIds().indexOf(focusedViewId); if (index >= 0) { - RemoteViews presentation = dataset.getFieldDialogPresentation(index); + RemoteViews presentation = Helper.sanitizeRemoteView( + dataset.getFieldDialogPresentation(index)); if (presentation == null) { if (sDebug) { Slog.w(TAG, "not displaying UI on field " + focusedViewId + " because " diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java index 8fbdd81cc4cc..76fa258734cc 100644 --- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java @@ -144,8 +144,9 @@ final class FillUi { final LayoutInflater inflater = LayoutInflater.from(mContext); - final RemoteViews headerPresentation = response.getHeader(); - final RemoteViews footerPresentation = response.getFooter(); + final RemoteViews headerPresentation = Helper.sanitizeRemoteView(response.getHeader()); + final RemoteViews footerPresentation = Helper.sanitizeRemoteView(response.getFooter()); + final ViewGroup decor; if (mFullScreen) { decor = (ViewGroup) inflater.inflate(R.layout.autofill_dataset_picker_fullscreen, null); @@ -223,6 +224,9 @@ final class FillUi { ViewGroup container = decor.findViewById(R.id.autofill_dataset_picker); final View content; try { + if (Helper.sanitizeRemoteView(response.getPresentation()) == null) { + throw new RuntimeException("Permission error accessing RemoteView"); + } content = response.getPresentation().applyWithTheme( mContext, decor, interceptionHandler, mThemeId); container.addView(content); @@ -302,7 +306,8 @@ final class FillUi { final Dataset dataset = response.getDatasets().get(i); final int index = dataset.getFieldIds().indexOf(focusedViewId); if (index >= 0) { - final RemoteViews presentation = dataset.getFieldPresentation(index); + final RemoteViews presentation = Helper.sanitizeRemoteView( + dataset.getFieldPresentation(index)); if (presentation == null) { Slog.w(TAG, "not displaying UI on field " + focusedViewId + " because " + "service didn't provide a presentation for it on " + dataset); diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java index 677871f6c85f..533a7b69a650 100644 --- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java @@ -368,8 +368,7 @@ final class SaveUi { return false; } writeLog(MetricsEvent.AUTOFILL_SAVE_CUSTOM_DESCRIPTION); - - final RemoteViews template = customDescription.getPresentation(); + final RemoteViews template = Helper.sanitizeRemoteView(customDescription.getPresentation()); if (template == null) { Slog.w(TAG, "No remote view on custom description"); return false; -- cgit v1.2.3 From 791225d99d1bd019bf69150dce12f1bfe5e2a20e Mon Sep 17 00:00:00 2001 From: Varun Shah Date: Fri, 19 May 2023 17:36:30 -0700 Subject: Update parcling logic for Uris. Implicitly convert all Uris to StringUris during parcel read/write. Bug: 231476072 Test: atest UriTest (cherry picked from commit 98bc5f99b14239aa871a998548ad80a076756318) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:18a2f95baeabdf23ecdb0475e62b8395825a26f6) Merged-In: Ic7688a00a07705301e5b06ee8783e801395e9f15 Change-Id: Ic7688a00a07705301e5b06ee8783e801395e9f15 --- core/java/android/net/Uri.java | 36 ++++--- core/tests/coretests/src/android/net/UriTest.java | 116 +++++++++++----------- 2 files changed, 77 insertions(+), 75 deletions(-) diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index 3da696ad0bc7..7fbaf1027af6 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -882,10 +882,11 @@ public abstract class Uri implements Parcelable, Comparable { } static Uri readFrom(Parcel parcel) { + final StringUri stringUri = new StringUri(parcel.readString8()); return new OpaqueUri( - parcel.readString8(), - Part.readFrom(parcel), - Part.readFrom(parcel) + stringUri.parseScheme(), + stringUri.getSsp(), + stringUri.getFragmentPart() ); } @@ -895,9 +896,7 @@ public abstract class Uri implements Parcelable, Comparable { public void writeToParcel(Parcel parcel, int flags) { parcel.writeInt(TYPE_ID); - parcel.writeString8(scheme); - ssp.writeTo(parcel); - fragment.writeTo(parcel); + parcel.writeString8(toString()); } public boolean isHierarchical() { @@ -1196,22 +1195,25 @@ public abstract class Uri implements Parcelable, Comparable { Part query, Part fragment) { this.scheme = scheme; this.authority = Part.nonNull(authority); - this.path = path == null ? PathPart.NULL : path; + this.path = generatePath(path); this.query = Part.nonNull(query); this.fragment = Part.nonNull(fragment); } - static Uri readFrom(Parcel parcel) { - final String scheme = parcel.readString8(); - final Part authority = Part.readFrom(parcel); + private PathPart generatePath(PathPart originalPath) { // In RFC3986 the path should be determined based on whether there is a scheme or // authority present (https://www.rfc-editor.org/rfc/rfc3986.html#section-3.3). final boolean hasSchemeOrAuthority = (scheme != null && scheme.length() > 0) || !authority.isEmpty(); - final PathPart path = PathPart.readFrom(hasSchemeOrAuthority, parcel); - final Part query = Part.readFrom(parcel); - final Part fragment = Part.readFrom(parcel); - return new HierarchicalUri(scheme, authority, path, query, fragment); + final PathPart newPath = hasSchemeOrAuthority ? PathPart.makeAbsolute(originalPath) + : originalPath; + return newPath == null ? PathPart.NULL : newPath; + } + + static Uri readFrom(Parcel parcel) { + final StringUri stringUri = new StringUri(parcel.readString8()); + return new HierarchicalUri(stringUri.getScheme(), stringUri.getAuthorityPart(), + stringUri.getPathPart(), stringUri.getQueryPart(), stringUri.getFragmentPart()); } public int describeContents() { @@ -1220,11 +1222,7 @@ public abstract class Uri implements Parcelable, Comparable { public void writeToParcel(Parcel parcel, int flags) { parcel.writeInt(TYPE_ID); - parcel.writeString8(scheme); - authority.writeTo(parcel); - path.writeTo(parcel); - query.writeTo(parcel); - fragment.writeTo(parcel); + parcel.writeString8(toString()); } public boolean isHierarchical() { diff --git a/core/tests/coretests/src/android/net/UriTest.java b/core/tests/coretests/src/android/net/UriTest.java index 89632a46267e..2a4ca79d997e 100644 --- a/core/tests/coretests/src/android/net/UriTest.java +++ b/core/tests/coretests/src/android/net/UriTest.java @@ -25,8 +25,6 @@ import junit.framework.TestCase; import java.io.File; import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.Arrays; import java.util.Iterator; import java.util.List; @@ -869,84 +867,90 @@ public class UriTest extends TestCase { return (Uri) hierarchicalUriConstructor.newInstance("https", authority, path, null, null); } - /** Attempting to unparcel a legacy parcel format of Uri.{,Path}Part should fail. */ - public void testUnparcelLegacyPart_fails() throws Exception { - assertUnparcelLegacyPart_fails(Class.forName("android.net.Uri$Part")); - assertUnparcelLegacyPart_fails(Class.forName("android.net.Uri$PathPart")); - } - - private static void assertUnparcelLegacyPart_fails(Class partClass) throws Exception { - Parcel parcel = Parcel.obtain(); - parcel.writeInt(0 /* BOTH */); - parcel.writeString("encoded"); - parcel.writeString("decoded"); - parcel.setDataPosition(0); - - Method readFromMethod = partClass.getDeclaredMethod("readFrom", Parcel.class); - readFromMethod.setAccessible(true); - try { - readFromMethod.invoke(null, parcel); - fail(); - } catch (InvocationTargetException expected) { - Throwable targetException = expected.getTargetException(); - // Check that the exception was thrown for the correct reason. - assertEquals("Unknown representation: 0", targetException.getMessage()); - } finally { - parcel.recycle(); - } - } - - private Uri buildUriFromRawParcel(boolean argumentsEncoded, + private Uri buildUriFromParts(boolean argumentsEncoded, String scheme, String authority, String path, String query, String fragment) { - // Representation value (from AbstractPart.REPRESENTATION_{ENCODED,DECODED}). - final int representation = argumentsEncoded ? 1 : 2; - Parcel parcel = Parcel.obtain(); - try { - parcel.writeInt(3); // hierarchical - parcel.writeString8(scheme); - parcel.writeInt(representation); - parcel.writeString8(authority); - parcel.writeInt(representation); - parcel.writeString8(path); - parcel.writeInt(representation); - parcel.writeString8(query); - parcel.writeInt(representation); - parcel.writeString8(fragment); - parcel.setDataPosition(0); - return Uri.CREATOR.createFromParcel(parcel); - } finally { - parcel.recycle(); + final Uri.Builder builder = new Uri.Builder(); + builder.scheme(scheme); + if (argumentsEncoded) { + builder.encodedAuthority(authority); + builder.encodedPath(path); + builder.encodedQuery(query); + builder.encodedFragment(fragment); + } else { + builder.authority(authority); + builder.path(path); + builder.query(query); + builder.fragment(fragment); } + return builder.build(); } public void testUnparcelMalformedPath() { // Regression tests for b/171966843. // Test cases with arguments encoded (covering testing `scheme` * `authority` options). - Uri uri0 = buildUriFromRawParcel(true, "https", "google.com", "@evil.com", null, null); + Uri uri0 = buildUriFromParts(true, "https", "google.com", "@evil.com", null, null); assertEquals("https://google.com/@evil.com", uri0.toString()); - Uri uri1 = buildUriFromRawParcel(true, null, "google.com", "@evil.com", "name=spark", "x"); + Uri uri1 = buildUriFromParts(true, null, "google.com", "@evil.com", "name=spark", "x"); assertEquals("//google.com/@evil.com?name=spark#x", uri1.toString()); - Uri uri2 = buildUriFromRawParcel(true, "http:", null, "@evil.com", null, null); + Uri uri2 = buildUriFromParts(true, "http:", null, "@evil.com", null, null); assertEquals("http::/@evil.com", uri2.toString()); - Uri uri3 = buildUriFromRawParcel(true, null, null, "@evil.com", null, null); + Uri uri3 = buildUriFromParts(true, null, null, "@evil.com", null, null); assertEquals("@evil.com", uri3.toString()); // Test cases with arguments not encoded (covering testing `scheme` * `authority` options). - Uri uriA = buildUriFromRawParcel(false, "https", "google.com", "@evil.com", null, null); + Uri uriA = buildUriFromParts(false, "https", "google.com", "@evil.com", null, null); assertEquals("https://google.com/%40evil.com", uriA.toString()); - Uri uriB = buildUriFromRawParcel(false, null, "google.com", "@evil.com", null, null); + Uri uriB = buildUriFromParts(false, null, "google.com", "@evil.com", null, null); assertEquals("//google.com/%40evil.com", uriB.toString()); - Uri uriC = buildUriFromRawParcel(false, "http:", null, "@evil.com", null, null); + Uri uriC = buildUriFromParts(false, "http:", null, "@evil.com", null, null); assertEquals("http::/%40evil.com", uriC.toString()); - Uri uriD = buildUriFromRawParcel(false, null, null, "@evil.com", "name=spark", "y"); + Uri uriD = buildUriFromParts(false, null, null, "@evil.com", "name=spark", "y"); assertEquals("%40evil.com?name%3Dspark#y", uriD.toString()); } + public void testParsedUriFromStringEquality() { + Uri uri = buildUriFromParts( + true, "https", "google.com", "@evil.com", null, null); + assertEquals(uri, Uri.parse(uri.toString())); + Uri uri2 = buildUriFromParts( + true, "content://evil.authority?foo=", "safe.authority", "@evil.com", null, null); + assertEquals(uri2, Uri.parse(uri2.toString())); + Uri uri3 = buildUriFromParts( + false, "content://evil.authority?foo=", "safe.authority", "@evil.com", null, null); + assertEquals(uri3, Uri.parse(uri3.toString())); + } + + public void testParceledUrisAreEqual() { + Uri opaqueUri = Uri.fromParts("fake://uri#", "ssp", "fragment"); + Parcel parcel = Parcel.obtain(); + try { + opaqueUri.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + Uri postParcelUri = Uri.CREATOR.createFromParcel(parcel); + Uri parsedUri = Uri.parse(postParcelUri.toString()); + assertEquals(parsedUri.getScheme(), postParcelUri.getScheme()); + } finally { + parcel.recycle(); + } + + Uri hierarchicalUri = new Uri.Builder().scheme("fake://uri#").authority("auth").build(); + parcel = Parcel.obtain(); + try { + hierarchicalUri.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + Uri postParcelUri = Uri.CREATOR.createFromParcel(parcel); + Uri parsedUri = Uri.parse(postParcelUri.toString()); + assertEquals(parsedUri.getScheme(), postParcelUri.getScheme()); + } finally { + parcel.recycle(); + } + } + public void testToSafeString() { checkToSafeString("tel:xxxxxx", "tel:Google"); checkToSafeString("tel:xxxxxxxxxx", "tel:1234567890"); -- cgit v1.2.3 From f4a8752dba4c27bae547ffd462c46286be33ce20 Mon Sep 17 00:00:00 2001 From: Hongwei Wang Date: Thu, 25 May 2023 12:18:44 -0700 Subject: Disallow loading icon from content URI to PipMenu Bug: 278246904 Test: manually, with the PoC app attached to the bug (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:1aee65603e262affd815fa53dcc5416c605e4037) Merged-In: Ib3f5b8b6b9ce644fdf1173548d9078e4d969ae2e Change-Id: Ib3f5b8b6b9ce644fdf1173548d9078e4d969ae2e --- .../com/android/wm/shell/pip/phone/PipMenuView.java | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java index 167c0321d3ad..779c539a2097 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java @@ -45,6 +45,7 @@ import android.content.Intent; import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -513,13 +514,19 @@ public class PipMenuView extends FrameLayout { final boolean isCloseAction = mCloseAction != null && Objects.equals( mCloseAction.getActionIntent(), action.getActionIntent()); - // TODO: Check if the action drawable has changed before we reload it - action.getIcon().loadDrawableAsync(mContext, d -> { - if (d != null) { - d.setTint(Color.WHITE); - actionView.setImageDrawable(d); - } - }, mMainHandler); + final int iconType = action.getIcon().getType(); + if (iconType == Icon.TYPE_URI || iconType == Icon.TYPE_URI_ADAPTIVE_BITMAP) { + // Disallow loading icon from content URI + actionView.setImageDrawable(null); + } else { + // TODO: Check if the action drawable has changed before we reload it + action.getIcon().loadDrawableAsync(mContext, d -> { + if (d != null) { + d.setTint(Color.WHITE); + actionView.setImageDrawable(d); + } + }, mMainHandler); + } actionView.setCustomCloseBackgroundVisibility( isCloseAction ? View.VISIBLE : View.GONE); actionView.setContentDescription(action.getContentDescription()); -- cgit v1.2.3 From b5adfd3d4452f72698b1cb21adc1fd8ea6f1f823 Mon Sep 17 00:00:00 2001 From: Kunal Malhotra Date: Fri, 2 Jun 2023 23:32:02 +0000 Subject: Fixing DatabaseUtils to detect malformed UTF-16 strings Test: tested with POC in bug, also using atest Bug: 224771621 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:fb4a72e3943d166088407e61aa4439ac349f3f12) Merged-In: Ide65205b83063801971c5778af3154bcf3f0e530 Change-Id: Ide65205b83063801971c5778af3154bcf3f0e530 --- core/java/android/database/DatabaseUtils.java | 32 +++++++++++++++++++-------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java index 6c8a8500e4e3..d41df4f49d48 100644 --- a/core/java/android/database/DatabaseUtils.java +++ b/core/java/android/database/DatabaseUtils.java @@ -511,17 +511,31 @@ public class DatabaseUtils { */ public static void appendEscapedSQLString(StringBuilder sb, String sqlString) { sb.append('\''); - if (sqlString.indexOf('\'') != -1) { - int length = sqlString.length(); - for (int i = 0; i < length; i++) { - char c = sqlString.charAt(i); - if (c == '\'') { - sb.append('\''); + int length = sqlString.length(); + for (int i = 0; i < length; i++) { + char c = sqlString.charAt(i); + if (Character.isHighSurrogate(c)) { + if (i == length - 1) { + continue; + } + if (Character.isLowSurrogate(sqlString.charAt(i + 1))) { + // add them both + sb.append(c); + sb.append(sqlString.charAt(i + 1)); + continue; + } else { + // this is a lone surrogate, skip it + continue; } - sb.append(c); } - } else - sb.append(sqlString); + if (Character.isLowSurrogate(c)) { + continue; + } + if (c == '\'') { + sb.append('\''); + } + sb.append(c); + } sb.append('\''); } -- cgit v1.2.3 From 1538790d05cd64e455623a8229e28f1ee0fc86e4 Mon Sep 17 00:00:00 2001 From: Anton Potapov Date: Tue, 4 Jul 2023 12:15:41 +0100 Subject: Add userId check before loading icon in Device Controls Test: manual with the steps from the bug Test: manual with a normal icon Test: atest CanUseIconPredicate Test: atest ControlViewHolderTest Bug: 272025416 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:ffa97f42dd9496bb404e01727c923292d05a4466) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:570aad7c61e4fc8854ed1aba97cbb6e6a491ca6d) Merged-In: I60896a6f53307f0e97a9223b599a2891c6c0c08d Change-Id: I60896a6f53307f0e97a9223b599a2891c6c0c08d --- .../systemui/controls/management/ControlAdapter.kt | 15 ++-- .../controls/management/ControlsEditingActivity.kt | 2 +- .../management/ControlsFavoritingActivity.kt | 4 +- .../controls/management/StructureAdapter.kt | 11 +-- .../systemui/controls/ui/CanUseIconPredicate.kt | 30 ++++++++ .../systemui/controls/ui/ControlViewHolder.kt | 50 +++++++------ .../controls/ui/ControlsUiControllerImpl.kt | 3 +- .../controls/ui/TemperatureControlBehavior.kt | 2 +- .../systemui/controls/ui/ThumbnailBehavior.kt | 12 +++- .../controls/ui/CanUseIconPredicateTest.kt | 81 ++++++++++++++++++++++ .../systemui/controls/ui/ControlViewHolderTest.kt | 3 +- 11 files changed, 173 insertions(+), 40 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/controls/ui/CanUseIconPredicate.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt index 3eb58bba1ca4..ec76f433b23b 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt @@ -38,6 +38,7 @@ import androidx.core.view.accessibility.AccessibilityNodeInfoCompat import androidx.recyclerview.widget.RecyclerView import com.android.systemui.R import com.android.systemui.controls.ControlInterface +import com.android.systemui.controls.ui.CanUseIconPredicate import com.android.systemui.controls.ui.RenderInfo private typealias ModelFavoriteChanger = (String, Boolean) -> Unit @@ -51,7 +52,8 @@ private typealias ModelFavoriteChanger = (String, Boolean) -> Unit * @property elevation elevation of each control view */ class ControlAdapter( - private val elevation: Float + private val elevation: Float, + private val currentUserId: Int, ) : RecyclerView.Adapter() { companion object { @@ -107,7 +109,8 @@ class ControlAdapter( background = parent.context.getDrawable( R.drawable.control_background_ripple) }, - model?.moveHelper // Indicates that position information is needed + currentUserId, + model?.moveHelper, // Indicates that position information is needed ) { id, favorite -> model?.changeFavoriteStatus(id, favorite) } @@ -212,8 +215,9 @@ private class ZoneHolder(view: View) : Holder(view) { */ internal class ControlHolder( view: View, + currentUserId: Int, val moveHelper: ControlsModel.MoveHelper?, - val favoriteCallback: ModelFavoriteChanger + val favoriteCallback: ModelFavoriteChanger, ) : Holder(view) { private val favoriteStateDescription = itemView.context.getString(R.string.accessibility_control_favorite) @@ -228,6 +232,7 @@ internal class ControlHolder( visibility = View.VISIBLE } + private val canUseIconPredicate = CanUseIconPredicate(currentUserId) private val accessibilityDelegate = ControlHolderAccessibilityDelegate( this::stateDescription, this::getLayoutPosition, @@ -287,7 +292,9 @@ internal class ControlHolder( val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme()) icon.imageTintList = null - ci.customIcon?.let { + ci.customIcon + ?.takeIf(canUseIconPredicate) + ?.let { icon.setImageIcon(it) } ?: run { icon.setImageDrawable(ri.icon) diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt index 7df08651d5ab..735bdc7af83c 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt @@ -205,7 +205,7 @@ open class ControlsEditingActivity @Inject constructor( val elevation = resources.getFloat(R.dimen.control_card_elevation) val recyclerView = requireViewById(R.id.list) recyclerView.alpha = 0.0f - val adapter = ControlAdapter(elevation).apply { + val adapter = ControlAdapter(elevation, userTracker.userId).apply { registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { var hasAnimated = false override fun onChanged() { diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt index 3e97d3132bc7..c25bb190a6c1 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt @@ -175,7 +175,7 @@ open class ControlsFavoritingActivity @Inject constructor( } executor.execute { - structurePager.adapter = StructureAdapter(listOfStructures) + structurePager.adapter = StructureAdapter(listOfStructures, userTracker.userId) structurePager.setCurrentItem(structureIndex) if (error) { statusText.text = resources.getString(R.string.controls_favorite_load_error, @@ -221,7 +221,7 @@ open class ControlsFavoritingActivity @Inject constructor( structurePager.alpha = 0.0f pageIndicator.alpha = 0.0f structurePager.apply { - adapter = StructureAdapter(emptyList()) + adapter = StructureAdapter(emptyList(), userTracker.userId) registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { super.onPageSelected(position) diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt index 747bcbe1c229..5977d379acde 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt @@ -24,13 +24,15 @@ import androidx.recyclerview.widget.RecyclerView import com.android.systemui.R class StructureAdapter( - private val models: List + private val models: List, + private val currentUserId: Int, ) : RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, p1: Int): StructureHolder { val layoutInflater = LayoutInflater.from(parent.context) return StructureHolder( - layoutInflater.inflate(R.layout.controls_structure_page, parent, false) + layoutInflater.inflate(R.layout.controls_structure_page, parent, false), + currentUserId, ) } @@ -40,7 +42,8 @@ class StructureAdapter( holder.bind(models[index].model) } - class StructureHolder(view: View) : RecyclerView.ViewHolder(view) { + class StructureHolder(view: View, currentUserId: Int) : + RecyclerView.ViewHolder(view) { private val recyclerView: RecyclerView private val controlAdapter: ControlAdapter @@ -48,7 +51,7 @@ class StructureAdapter( init { recyclerView = itemView.requireViewById(R.id.listAll) val elevation = itemView.context.resources.getFloat(R.dimen.control_card_elevation) - controlAdapter = ControlAdapter(elevation) + controlAdapter = ControlAdapter(elevation, currentUserId) setUpRecyclerView() } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/CanUseIconPredicate.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/CanUseIconPredicate.kt new file mode 100644 index 000000000000..61c21237144d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/CanUseIconPredicate.kt @@ -0,0 +1,30 @@ +/* + * 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.controls.ui + +import android.content.ContentProvider +import android.graphics.drawable.Icon + +class CanUseIconPredicate(private val currentUserId: Int) : (Icon) -> Boolean { + + override fun invoke(icon: Icon): Boolean = + if (icon.type == Icon.TYPE_URI || icon.type == Icon.TYPE_URI_ADAPTIVE_BITMAP) { + ContentProvider.getUserIdFromUri(icon.uri, currentUserId) == currentUserId + } else { + true + } +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt index 6a9aaf865251..931062865c64 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt @@ -68,7 +68,8 @@ class ControlViewHolder( val bgExecutor: DelayableExecutor, val controlActionCoordinator: ControlActionCoordinator, val controlsMetricsLogger: ControlsMetricsLogger, - val uid: Int + val uid: Int, + val currentUserId: Int, ) { companion object { @@ -85,29 +86,9 @@ class ControlViewHolder( private val ATTR_DISABLED = intArrayOf(-android.R.attr.state_enabled) const val MIN_LEVEL = 0 const val MAX_LEVEL = 10000 - - fun findBehaviorClass( - status: Int, - template: ControlTemplate, - deviceType: Int - ): Supplier { - return when { - status != Control.STATUS_OK -> Supplier { StatusBehavior() } - template == ControlTemplate.NO_TEMPLATE -> Supplier { TouchBehavior() } - template is ThumbnailTemplate -> Supplier { ThumbnailBehavior() } - - // Required for legacy support, or where cameras do not use the new template - deviceType == DeviceTypes.TYPE_CAMERA -> Supplier { TouchBehavior() } - template is ToggleTemplate -> Supplier { ToggleBehavior() } - template is StatelessTemplate -> Supplier { TouchBehavior() } - template is ToggleRangeTemplate -> Supplier { ToggleRangeBehavior() } - template is RangeTemplate -> Supplier { ToggleRangeBehavior() } - template is TemperatureControlTemplate -> Supplier { TemperatureControlBehavior() } - else -> Supplier { DefaultBehavior() } - } - } } + private val canUseIconPredicate = CanUseIconPredicate(currentUserId) private val toggleBackgroundIntensity: Float = layout.context.resources .getFraction(R.fraction.controls_toggle_bg_intensity, 1, 1) private var stateAnimator: ValueAnimator? = null @@ -147,6 +128,27 @@ class ControlViewHolder( status.setSelected(true) } + fun findBehaviorClass( + status: Int, + template: ControlTemplate, + deviceType: Int + ): Supplier { + return when { + status != Control.STATUS_OK -> Supplier { StatusBehavior() } + template == ControlTemplate.NO_TEMPLATE -> Supplier { TouchBehavior() } + template is ThumbnailTemplate -> Supplier { ThumbnailBehavior(currentUserId) } + + // Required for legacy support, or where cameras do not use the new template + deviceType == DeviceTypes.TYPE_CAMERA -> Supplier { TouchBehavior() } + template is ToggleTemplate -> Supplier { ToggleBehavior() } + template is StatelessTemplate -> Supplier { TouchBehavior() } + template is ToggleRangeTemplate -> Supplier { ToggleRangeBehavior() } + template is RangeTemplate -> Supplier { ToggleRangeBehavior() } + template is TemperatureControlTemplate -> Supplier { TemperatureControlBehavior() } + else -> Supplier { DefaultBehavior() } + } + } + fun bindData(cws: ControlWithState, isLocked: Boolean) { // If an interaction is in progress, the update may visually interfere with the action the // action the user wants to make. Don't apply the update, and instead assume a new update @@ -473,7 +475,9 @@ class ControlViewHolder( status.setTextColor(color) - control?.getCustomIcon()?.let { + control?.customIcon + ?.takeIf(canUseIconPredicate) + ?.let { icon.setImageIcon(it) icon.imageTintList = it.tintList } ?: run { diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index 554391649548..1c1f7702c4bd 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -685,7 +685,8 @@ class ControlsUiControllerImpl @Inject constructor ( bgExecutor, controlActionCoordinator, controlsMetricsLogger, - selected.uid + selected.uid, + controlsController.get().currentUserId, ) cvh.bindData(it, false /* isLocked, will be ignored on initial load */) controlViewsById.put(key, cvh) diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt index a7dc09bb17e5..39d69704d817 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt @@ -63,7 +63,7 @@ class TemperatureControlBehavior : Behavior { // interactions (touch, range) subBehavior = cvh.bindBehavior( subBehavior, - ControlViewHolder.findBehaviorClass( + cvh.findBehaviorClass( control.status, subTemplate, control.deviceType diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt index c2168aa8d9d9..0b57e792f9f7 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt @@ -33,7 +33,7 @@ import com.android.systemui.controls.ui.ControlViewHolder.Companion.MIN_LEVEL * Supports display of static images on the background of the tile. When marked active, the title * and subtitle will not be visible. To be used with {@link Thumbnailtemplate} only. */ -class ThumbnailBehavior : Behavior { +class ThumbnailBehavior(currentUserId: Int) : Behavior { lateinit var template: ThumbnailTemplate lateinit var control: Control lateinit var cvh: ControlViewHolder @@ -42,6 +42,7 @@ class ThumbnailBehavior : Behavior { private var shadowRadius: Float = 0f private var shadowColor: Int = 0 + private val canUseIconPredicate = CanUseIconPredicate(currentUserId) private val enabled: Boolean get() = template.isActive() @@ -80,11 +81,16 @@ class ThumbnailBehavior : Behavior { cvh.status.setShadowLayer(shadowOffsetX, shadowOffsetY, shadowRadius, shadowColor) cvh.bgExecutor.execute { - val drawable = template.getThumbnail().loadDrawable(cvh.context) + val drawable = template.thumbnail + ?.takeIf(canUseIconPredicate) + ?.loadDrawable(cvh.context) cvh.uiExecutor.execute { val radius = cvh.context.getResources() .getDimensionPixelSize(R.dimen.control_corner_radius).toFloat() - clipLayer.setDrawable(CornerDrawable(drawable, radius)) + // TODO(b/290037843): Add a placeholder + drawable?.let { + clipLayer.drawable = CornerDrawable(it, radius) + } clipLayer.setColorFilter(BlendModeColorFilter(cvh.context.resources .getColor(R.color.control_thumbnail_tint), BlendMode.LUMINOSITY)) cvh.applyRenderInfo(enabled, colorOffset) diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt new file mode 100644 index 000000000000..bfdb9231a9f8 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt @@ -0,0 +1,81 @@ +/* + * 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.controls.ui + +import android.content.ContentProvider +import android.graphics.Bitmap +import android.graphics.drawable.Icon +import android.net.Uri +import android.os.UserHandle +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class CanUseIconPredicateTest : SysuiTestCase() { + + private companion object { + const val USER_ID_1 = 1 + const val USER_ID_2 = 2 + } + + val underTest: CanUseIconPredicate = CanUseIconPredicate(USER_ID_1) + + @Test + fun testReturnsFalseForDifferentUser() { + val user2Icon = + Icon.createWithContentUri( + ContentProvider.createContentUriForUser( + Uri.parse("content://test"), + UserHandle.of(USER_ID_2) + ) + ) + + assertThat(underTest.invoke(user2Icon)).isFalse() + } + + @Test + fun testReturnsTrueForCorrectUser() { + val user1Icon = + Icon.createWithContentUri( + ContentProvider.createContentUriForUser( + Uri.parse("content://test"), + UserHandle.of(USER_ID_1) + ) + ) + + assertThat(underTest.invoke(user1Icon)).isTrue() + } + + @Test + fun testReturnsTrueForUriWithoutUser() { + val uriIcon = Icon.createWithContentUri(Uri.parse("content://test")) + + assertThat(underTest.invoke(uriIcon)).isTrue() + } + + @Test + fun testReturnsTrueForNonUriIcon() { + val bitmapIcon = Icon.createWithBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)) + + assertThat(underTest.invoke(bitmapIcon)).isTrue() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt index d3c465dab438..42f28c8c6043 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt @@ -66,7 +66,8 @@ class ControlViewHolderTest : SysuiTestCase() { FakeExecutor(clock), mock(ControlActionCoordinator::class.java), mock(ControlsMetricsLogger::class.java), - uid = 100 + uid = 100, + 0, ) val cws = ControlWithState( -- cgit v1.2.3 From 72c2f969b6cadb2eba31edc54d1ceaed5b857a5a Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Fri, 28 Jul 2023 22:03:03 +0000 Subject: RESTRICT AUTOMERGE: SettingsProvider: exclude secure_frp_mode from resets When RescueParty detects that a system process is crashing frequently, it tries to recover in various ways, such as by resetting all settings. Unfortunately, this included resetting the secure_frp_mode setting, which is the means by which the system keeps track of whether the Factory Reset Protection (FRP) challenge has been passed yet. With this setting reset, some FRP restrictions went away and it became possible to bypass FRP by setting a new lockscreen credential. Fix this by excluding secure_frp_mode from resets. Note: currently this bug isn't reproducible on 'main' due to ag/23727749 disabling much of RescueParty, but that is a temporary change. Bug: 253043065 Test: With ag/23727749 reverted and with my fix to prevent com.android.settings from crashing *not* applied, tried repeatedly setting lockscreen credential while in FRP mode, using the smartlock setup activity launched by intent via adb. Verified that although RescueParty is still triggered after 5 attempts, secure_frp_mode is no longer reset (its value remains "1"). Test: Verified that secure_frp_mode still gets changed from 1 to 0 when FRP is passed legitimately. Test: atest com.android.providers.settings.SettingsProviderTest Test: atest android.provider.SettingsProviderTest (cherry picked from commit 9890dd7f15c091f7d1a09e4fddb9f85d32015955) (changed Global.SECURE_FRP_MODE to Secure.SECURE_FRP_MODE, needed because this setting was moved in U) (removed static keyword from shouldExcludeSettingFromReset(), needed for compatibility with Java 15 and earlier) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:efc2b69c88c3b4686a521935d6e5e9884b9e6347) Merged-In: Id95ed43b9cc2208090064392bcd5dc012710af93 Change-Id: Id95ed43b9cc2208090064392bcd5dc012710af93 --- .../providers/settings/SettingsProvider.java | 17 +++++++++++---- .../providers/settings/SettingsProviderTest.java | 25 ++++++++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 84a559391343..d951121f7323 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -3101,6 +3101,15 @@ public class SettingsProvider extends ContentProvider { return settingsState.getSettingLocked(name); } + private boolean shouldExcludeSettingFromReset(Setting setting, String prefix) { + // If a prefix was specified, exclude settings whose names don't start with it. + if (prefix != null && !setting.getName().startsWith(prefix)) { + return true; + } + // Never reset SECURE_FRP_MODE, as it could be abused to bypass FRP via RescueParty. + return Secure.SECURE_FRP_MODE.equals(setting.getName()); + } + public void resetSettingsLocked(int type, int userId, String packageName, int mode, String tag) { resetSettingsLocked(type, userId, packageName, mode, tag, /*prefix=*/ @@ -3123,7 +3132,7 @@ public class SettingsProvider extends ContentProvider { Setting setting = settingsState.getSettingLocked(name); if (packageName.equals(setting.getPackageName())) { if ((tag != null && !tag.equals(setting.getTag())) - || (prefix != null && !setting.getName().startsWith(prefix))) { + || shouldExcludeSettingFromReset(setting, prefix)) { continue; } if (settingsState.resetSettingLocked(name)) { @@ -3143,7 +3152,7 @@ public class SettingsProvider extends ContentProvider { Setting setting = settingsState.getSettingLocked(name); if (!SettingsState.isSystemPackage(getContext(), setting.getPackageName())) { - if (prefix != null && !setting.getName().startsWith(prefix)) { + if (shouldExcludeSettingFromReset(setting, prefix)) { continue; } if (settingsState.resetSettingLocked(name)) { @@ -3163,7 +3172,7 @@ public class SettingsProvider extends ContentProvider { Setting setting = settingsState.getSettingLocked(name); if (!SettingsState.isSystemPackage(getContext(), setting.getPackageName())) { - if (prefix != null && !setting.getName().startsWith(prefix)) { + if (shouldExcludeSettingFromReset(setting, prefix)) { continue; } if (setting.isDefaultFromSystem()) { @@ -3186,7 +3195,7 @@ public class SettingsProvider extends ContentProvider { for (String name : settingsState.getSettingNamesLocked()) { Setting setting = settingsState.getSettingLocked(name); boolean someSettingChanged = false; - if (prefix != null && !setting.getName().startsWith(prefix)) { + if (shouldExcludeSettingFromReset(setting, prefix)) { continue; } if (setting.isDefaultFromSystem()) { diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java index eaf0dcb9b4e7..1c6d2b08136c 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java @@ -464,6 +464,31 @@ public class SettingsProviderTest extends BaseSettingsProviderTest { } } + // To prevent FRP bypasses, the SECURE_FRP_MODE setting should not be reset when all other + // settings are reset. But it should still be possible to explicitly set its value. + @Test + public void testSecureFrpModeSettingCannotBeReset() throws Exception { + final String name = Settings.Secure.SECURE_FRP_MODE; + final String origValue = getSetting(SETTING_TYPE_GLOBAL, name); + setSettingViaShell(SETTING_TYPE_GLOBAL, name, "1", false); + try { + assertEquals("1", getSetting(SETTING_TYPE_GLOBAL, name)); + for (int type : new int[] { SETTING_TYPE_GLOBAL, SETTING_TYPE_SECURE }) { + resetSettingsViaShell(type, Settings.RESET_MODE_UNTRUSTED_DEFAULTS); + resetSettingsViaShell(type, Settings.RESET_MODE_UNTRUSTED_CHANGES); + resetSettingsViaShell(type, Settings.RESET_MODE_TRUSTED_DEFAULTS); + } + // The value should still be "1". It should not have been reset to null. + assertEquals("1", getSetting(SETTING_TYPE_GLOBAL, name)); + // It should still be possible to explicitly set the value to "0". + setSettingViaShell(SETTING_TYPE_GLOBAL, name, "0", false); + assertEquals("0", getSetting(SETTING_TYPE_GLOBAL, name)); + } finally { + setSettingViaShell(SETTING_TYPE_GLOBAL, name, origValue, false); + assertEquals(origValue, getSetting(SETTING_TYPE_GLOBAL, name)); + } + } + private void doTestQueryStringInBracketsViaProviderApiForType(int type) { // Make sure we have a clean slate. deleteStringViaProviderApi(type, FAKE_SETTING_NAME); -- cgit v1.2.3 From c67c9b66928258d6b523a5f30b57385fe58ac362 Mon Sep 17 00:00:00 2001 From: Tingting Zhang Date: Fri, 7 Jul 2023 16:03:38 +0800 Subject: perf: disable pre-rendering feature for some multi-layer cases Multi-layer rendering can result in unexpected pending between UI thread and render thread if pre-rendering enabled. Need to disable pre-rendering for multi-layer cases when view get updated and calling doRemoveView function. Change-Id: I02989689c5ff5af6b08417a035b7684834a342b7 CRs-Fixed: 3528182 --- core/java/android/view/WindowManagerGlobal.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index ae9a1220a246..1afefe5777c7 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -546,7 +546,13 @@ public final class WindowManagerGlobal { visibleRootCount++; } } - if (visibleRootCount == 1) { + + // The visibleRootCount more than one means multi-layer, and multi-layer rendering + // can result in unexpected pending between UI thread and render thread with + // pre-rendering enabled. Need to disable pre-rendering for multi-layer cases. + if (visibleRootCount > 1) { + ScrollOptimizer.disableOptimizer(true); + } else if (visibleRootCount == 1) { ScrollOptimizer.disableOptimizer(false); } -- cgit v1.2.3