summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java2
-rw-r--r--cmds/bu/src/com/android/commands/bu/Backup.java3
-rw-r--r--core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java2
-rw-r--r--core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java2
-rw-r--r--core/java/android/os/Process.java2
-rw-r--r--core/java/android/view/HandwritingInitiator.java77
-rw-r--r--core/java/android/view/inputmethod/InputConnectionWrapper.java14
-rw-r--r--core/res/res/drawable-hdpi/pointer_handwriting.pngbin0 -> 1609 bytes
-rw-r--r--core/res/res/drawable-mdpi/pointer_handwriting.pngbin0 -> 912 bytes
-rw-r--r--core/res/res/drawable-xhdpi/pointer_handwriting.pngbin0 -> 2504 bytes
-rw-r--r--core/res/res/drawable-xxhdpi/pointer_handwriting.pngbin0 -> 4983 bytes
-rw-r--r--core/res/res/drawable/pointer_handwriting_icon.xml6
-rw-r--r--core/tests/coretests/src/android/os/ProcessTest.java1
-rw-r--r--core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java141
-rw-r--r--core/tests/coretests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java45
-rw-r--r--graphics/java/android/graphics/RuntimeShader.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java17
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java24
-rw-r--r--packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java57
-rw-r--r--packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt135
-rw-r--r--packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt60
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/CarrierTextManagerLog.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/data/model/SceneContainerConfig.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt136
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt73
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt87
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneKey.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneModel.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt64
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt120
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/Fakes.kt51
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt139
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt78
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt74
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt8
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java22
-rw-r--r--services/backup/java/com/android/server/backup/BackupManagerService.java86
-rw-r--r--services/backup/java/com/android/server/backup/UserBackupManagerService.java12
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java9
-rw-r--r--services/core/java/com/android/server/am/BroadcastProcessQueue.java38
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueModernImpl.java80
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java5
-rw-r--r--services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java3
-rw-r--r--services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java15
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryStatsImpl.java20
-rw-r--r--services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java7
-rw-r--r--services/core/java/com/android/server/wm/ImeTargetChangeListener.java3
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java13
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java2
-rw-r--r--services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java9
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java7
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java36
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java74
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/backup/BackupManagerServiceTest.java48
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java56
-rw-r--r--services/tests/servicestests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java154
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java3
-rw-r--r--services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java66
-rw-r--r--wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java19
-rw-r--r--wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java4
66 files changed, 1969 insertions, 445 deletions
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index b6dc32a29f04..7d09b992d080 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -253,7 +253,7 @@ public class Bmgr {
try {
boolean enable = Boolean.parseBoolean(arg);
- mBmgr.setAutoRestore(enable);
+ mBmgr.setAutoRestoreForUser(userId, enable);
System.out.println(
"Auto restore is now "
+ (enable ? "enabled" : "disabled")
diff --git a/cmds/bu/src/com/android/commands/bu/Backup.java b/cmds/bu/src/com/android/commands/bu/Backup.java
index 373677eccf62..11c9773a7a6c 100644
--- a/cmds/bu/src/com/android/commands/bu/Backup.java
+++ b/cmds/bu/src/com/android/commands/bu/Backup.java
@@ -56,6 +56,7 @@ public final class Backup {
}
public void run(String[] args) {
+ Log.d(TAG, "Called run() with args: " + String.join(" ", args));
if (mBackupManager == null) {
Log.e(TAG, "Can't obtain Backup Manager binder");
return;
@@ -70,6 +71,8 @@ public final class Backup {
return;
}
+ Log.d(TAG, "UserId : " + userId);
+
String arg = nextArg();
if (arg.equals("backup")) {
doBackup(OsConstants.STDOUT_FILENO, userId);
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index 10753faace98..e787779d0f57 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -546,7 +546,7 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
if (mExtensionClientId >= 0) {
CameraExtensionCharacteristics.unregisterClient(mExtensionClientId);
- if (mInitialized) {
+ if (mInitialized || (mCaptureSession != null)) {
notifyClose = true;
CameraExtensionCharacteristics.releaseSession();
}
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 48f18c9d2387..8e7c7e0cfca8 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -840,7 +840,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
if (mExtensionClientId >= 0) {
CameraExtensionCharacteristics.unregisterClient(mExtensionClientId);
- if (mInitialized) {
+ if (mInitialized || (mCaptureSession != null)) {
notifyClose = true;
CameraExtensionCharacteristics.releaseSession();
}
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index bf3d52d358ed..04525e8b8ff7 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -1359,7 +1359,7 @@ public class Process {
String formatSize = MemoryProperties.memory_ddr_size().orElse("0KB");
long memSize = FileUtils.parseSize(formatSize);
- if (memSize == Long.MIN_VALUE) {
+ if (memSize <= 0) {
return FileUtils.roundStorageSize(getTotalMemory());
}
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index dd4f9644da96..ab58306ba5ab 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -85,7 +85,21 @@ public class HandwritingInitiator {
* to {@link #findBestCandidateView(float, float)}.
*/
@Nullable
- private View mCachedHoverTarget = null;
+ private WeakReference<View> mCachedHoverTarget = null;
+
+ /**
+ * Whether to show the hover icon for the current connected view.
+ * Hover icon should be hidden for the current connected view after handwriting is initiated
+ * for it until one of the following events happens:
+ * a) user performs a click or long click. In other words, if it receives a series of motion
+ * events that don't trigger handwriting, show hover icon again.
+ * b) the stylus hovers on another editor that supports handwriting (or a handwriting delegate).
+ * c) the current connected editor lost focus.
+ *
+ * If the stylus is hovering on an unconnected editor that supports handwriting, we always show
+ * the hover icon.
+ */
+ private boolean mShowHoverIconForConnectedView = true;
@VisibleForTesting
public HandwritingInitiator(@NonNull ViewConfiguration viewConfiguration,
@@ -142,6 +156,12 @@ public class HandwritingInitiator {
// check whether the stylus we are tracking goes up.
if (mState != null) {
mState.mShouldInitHandwriting = false;
+ if (!mState.mHasInitiatedHandwriting
+ && !mState.mHasPreparedHandwritingDelegation) {
+ // The user just did a click, long click or another stylus gesture,
+ // show hover icon again for the connected view.
+ mShowHoverIconForConnectedView = true;
+ }
}
return false;
case MotionEvent.ACTION_MOVE:
@@ -214,7 +234,11 @@ public class HandwritingInitiator {
*/
public void onDelegateViewFocused(@NonNull View view) {
if (view == getConnectedView()) {
- tryAcceptStylusHandwritingDelegation(view);
+ if (tryAcceptStylusHandwritingDelegation(view)) {
+ // A handwriting delegate view is accepted and handwriting starts; hide the
+ // hover icon.
+ mShowHoverIconForConnectedView = false;
+ }
}
}
@@ -237,7 +261,12 @@ public class HandwritingInitiator {
} else {
mConnectedView = new WeakReference<>(view);
mConnectionCount = 1;
+ // A new view just gain focus. By default, we should show hover icon for it.
+ mShowHoverIconForConnectedView = true;
if (view.isHandwritingDelegate() && tryAcceptStylusHandwritingDelegation(view)) {
+ // A handwriting delegate view is accepted and handwriting starts; hide the
+ // hover icon.
+ mShowHoverIconForConnectedView = false;
return;
}
if (mState != null && mState.mShouldInitHandwriting) {
@@ -306,6 +335,7 @@ public class HandwritingInitiator {
mImm.startStylusHandwriting(view);
mState.mHasInitiatedHandwriting = true;
mState.mShouldInitHandwriting = false;
+ mShowHoverIconForConnectedView = false;
if (view instanceof TextView) {
((TextView) view).hideHint();
}
@@ -361,15 +391,35 @@ public class HandwritingInitiator {
* handwrite-able area.
*/
public PointerIcon onResolvePointerIcon(Context context, MotionEvent event) {
- if (shouldShowHandwritingPointerIcon(event)) {
+ final View hoverView = findHoverView(event);
+ if (hoverView == null) {
+ return null;
+ }
+
+ if (mShowHoverIconForConnectedView) {
+ return PointerIcon.getSystemIcon(context, PointerIcon.TYPE_HANDWRITING);
+ }
+
+ if (hoverView != getConnectedView()) {
+ // The stylus is hovering on another view that supports handwriting. We should show
+ // hover icon. Also reset the mShowHoverIconForConnectedView so that hover
+ // icon is displayed again next time when the stylus hovers on connected view.
+ mShowHoverIconForConnectedView = true;
return PointerIcon.getSystemIcon(context, PointerIcon.TYPE_HANDWRITING);
}
return null;
}
- private boolean shouldShowHandwritingPointerIcon(MotionEvent event) {
+ private View getCachedHoverTarget() {
+ if (mCachedHoverTarget == null) {
+ return null;
+ }
+ return mCachedHoverTarget.get();
+ }
+
+ private View findHoverView(MotionEvent event) {
if (!event.isStylusPointer() || !event.isHoverEvent()) {
- return false;
+ return null;
}
if (event.getActionMasked() == MotionEvent.ACTION_HOVER_ENTER
@@ -377,24 +427,25 @@ public class HandwritingInitiator {
final float hoverX = event.getX(event.getActionIndex());
final float hoverY = event.getY(event.getActionIndex());
- if (mCachedHoverTarget != null) {
- final Rect handwritingArea = getViewHandwritingArea(mCachedHoverTarget);
- if (isInHandwritingArea(handwritingArea, hoverX, hoverY, mCachedHoverTarget)
- && shouldTriggerStylusHandwritingForView(mCachedHoverTarget)) {
- return true;
+ final View cachedHoverTarget = getCachedHoverTarget();
+ if (cachedHoverTarget != null) {
+ final Rect handwritingArea = getViewHandwritingArea(cachedHoverTarget);
+ if (isInHandwritingArea(handwritingArea, hoverX, hoverY, cachedHoverTarget)
+ && shouldTriggerStylusHandwritingForView(cachedHoverTarget)) {
+ return cachedHoverTarget;
}
}
final View candidateView = findBestCandidateView(hoverX, hoverY);
if (candidateView != null) {
- mCachedHoverTarget = candidateView;
- return true;
+ mCachedHoverTarget = new WeakReference<>(candidateView);
+ return candidateView;
}
}
mCachedHoverTarget = null;
- return false;
+ return null;
}
private static void requestFocusWithoutReveal(View view) {
diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java
index 62f3c909dd4f..9c3067ce8d63 100644
--- a/core/java/android/view/inputmethod/InputConnectionWrapper.java
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -440,4 +440,18 @@ public class InputConnectionWrapper implements InputConnection {
public TextSnapshot takeSnapshot() {
return mTarget.takeSnapshot();
}
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the target is {@code null}.
+ */
+ @Override
+ public boolean replaceText(
+ @IntRange(from = 0) int start,
+ @IntRange(from = 0) int end,
+ @NonNull CharSequence text,
+ int newCursorPosition,
+ @Nullable TextAttribute textAttribute) {
+ return mTarget.replaceText(start, end, text, newCursorPosition, textAttribute);
+ }
}
diff --git a/core/res/res/drawable-hdpi/pointer_handwriting.png b/core/res/res/drawable-hdpi/pointer_handwriting.png
new file mode 100644
index 000000000000..6d7c59cccfc7
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_handwriting.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_handwriting.png b/core/res/res/drawable-mdpi/pointer_handwriting.png
new file mode 100644
index 000000000000..b36241bec84e
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_handwriting.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_handwriting.png b/core/res/res/drawable-xhdpi/pointer_handwriting.png
new file mode 100644
index 000000000000..dea1972a6216
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_handwriting.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_handwriting.png b/core/res/res/drawable-xxhdpi/pointer_handwriting.png
new file mode 100644
index 000000000000..870c40206e3c
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_handwriting.png
Binary files differ
diff --git a/core/res/res/drawable/pointer_handwriting_icon.xml b/core/res/res/drawable/pointer_handwriting_icon.xml
index cdbf6938bd57..bba4e6e4c2e6 100644
--- a/core/res/res/drawable/pointer_handwriting_icon.xml
+++ b/core/res/res/drawable/pointer_handwriting_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
- android:bitmap="@drawable/pointer_crosshair"
- android:hotSpotX="12dp"
- android:hotSpotY="12dp" /> \ No newline at end of file
+ android:bitmap="@drawable/pointer_handwriting"
+ android:hotSpotX="8.25dp"
+ android:hotSpotY="23.75dp" /> \ No newline at end of file
diff --git a/core/tests/coretests/src/android/os/ProcessTest.java b/core/tests/coretests/src/android/os/ProcessTest.java
index 52846dfbb14b..b2ffdc035e8b 100644
--- a/core/tests/coretests/src/android/os/ProcessTest.java
+++ b/core/tests/coretests/src/android/os/ProcessTest.java
@@ -73,6 +73,7 @@ public class ProcessTest extends TestCase {
}
public void testGetAdvertisedMem() {
+ assertTrue(Process.getAdvertisedMem() > 0);
assertTrue(Process.getTotalMemory() <= Process.getAdvertisedMem());
}
}
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index c0125afef2e8..34eac35d3c0b 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -17,6 +17,7 @@
package android.view.stylus;
import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_HOVER_MOVE;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.stylus.HandwritingTestUtil.createView;
@@ -26,6 +27,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -42,6 +44,7 @@ import android.platform.test.annotations.Presubmit;
import android.view.HandwritingInitiator;
import android.view.InputDevice;
import android.view.MotionEvent;
+import android.view.PointerIcon;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
@@ -115,6 +118,7 @@ public class HandwritingInitiatorTest {
HW_BOUNDS_OFFSETS_BOTTOM_PX);
mHandwritingInitiator.updateHandwritingAreasForView(mTestView1);
mHandwritingInitiator.updateHandwritingAreasForView(mTestView2);
+ doReturn(true).when(mHandwritingInitiator).tryAcceptStylusHandwritingDelegation(any());
}
@Test
@@ -486,6 +490,112 @@ public class HandwritingInitiatorTest {
}
@Test
+ public void onResolvePointerIcon_withinHWArea_showPointerIcon() {
+ MotionEvent hoverEvent = createStylusHoverEvent(sHwArea1.centerX(), sHwArea1.centerY());
+ PointerIcon icon = mHandwritingInitiator.onResolvePointerIcon(mContext, hoverEvent);
+ assertThat(icon.getType()).isEqualTo(PointerIcon.TYPE_HANDWRITING);
+ }
+
+ @Test
+ public void onResolvePointerIcon_withinExtendedHWArea_showPointerIcon() {
+ int x = sHwArea1.left - HW_BOUNDS_OFFSETS_LEFT_PX / 2;
+ int y = sHwArea1.top - HW_BOUNDS_OFFSETS_TOP_PX / 2;
+ MotionEvent hoverEvent = createStylusHoverEvent(x, y);
+
+ PointerIcon icon = mHandwritingInitiator.onResolvePointerIcon(mContext, hoverEvent);
+ assertThat(icon.getType()).isEqualTo(PointerIcon.TYPE_HANDWRITING);
+ }
+
+ @Test
+ public void onResolvePointerIcon_afterHandwriting_hidePointerIconForConnectedView() {
+ // simulate the case where sTestView1 is focused.
+ mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+ injectStylusEvent(mHandwritingInitiator, sHwArea1.centerX(), sHwArea1.centerY(),
+ /* exceedsHWSlop */ true);
+ // Verify that handwriting started for sTestView1.
+ verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1);
+
+ MotionEvent hoverEvent1 = createStylusHoverEvent(sHwArea1.centerX(), sHwArea1.centerY());
+ PointerIcon icon1 = mHandwritingInitiator.onResolvePointerIcon(mContext, hoverEvent1);
+ // After handwriting is initiated for the connected view, hide the hover icon.
+ assertThat(icon1).isNull();
+
+ MotionEvent hoverEvent2 = createStylusHoverEvent(sHwArea2.centerX(), sHwArea2.centerY());
+ PointerIcon icon2 = mHandwritingInitiator.onResolvePointerIcon(mContext, hoverEvent2);
+ // Now stylus is hovering on another editor, show the hover icon.
+ assertThat(icon2.getType()).isEqualTo(PointerIcon.TYPE_HANDWRITING);
+
+ // After the hover icon is displayed again, it will show hover icon for the connected view
+ // again.
+ PointerIcon icon3 = mHandwritingInitiator.onResolvePointerIcon(mContext, hoverEvent1);
+ assertThat(icon3.getType()).isEqualTo(PointerIcon.TYPE_HANDWRITING);
+ }
+
+ @Test
+ public void onResolvePointerIcon_afterHandwriting_hidePointerIconForDelegatorView() {
+ // Set mTextView2 to be the delegate of mTestView1.
+ mTestView2.setIsHandwritingDelegate(true);
+
+ mTestView1.setHandwritingDelegatorCallback(
+ () -> mHandwritingInitiator.onInputConnectionCreated(mTestView2));
+
+ injectStylusEvent(mHandwritingInitiator, sHwArea1.centerX(), sHwArea1.centerY(),
+ /* exceedsHWSlop */ true);
+ // Prerequisite check, verify that handwriting started for delegateView.
+ verify(mHandwritingInitiator, times(1)).tryAcceptStylusHandwritingDelegation(mTestView2);
+
+ MotionEvent hoverEvent = createStylusHoverEvent(sHwArea2.centerX(), sHwArea2.centerY());
+ PointerIcon icon = mHandwritingInitiator.onResolvePointerIcon(mContext, hoverEvent);
+ // After handwriting is initiated for the connected view, hide the hover icon.
+ assertThat(icon).isNull();
+ }
+
+ @Test
+ public void onResolvePointerIcon_showHoverIconAfterTap() {
+ // Simulate the case where sTestView1 is focused.
+ mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+ injectStylusEvent(mHandwritingInitiator, sHwArea1.centerX(), sHwArea1.centerY(),
+ /* exceedsHWSlop */ true);
+ // Verify that handwriting started for sTestView1.
+ verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1);
+
+ MotionEvent hoverEvent1 = createStylusHoverEvent(sHwArea1.centerX(), sHwArea1.centerY());
+ PointerIcon icon1 = mHandwritingInitiator.onResolvePointerIcon(mContext, hoverEvent1);
+ // After handwriting is initiated for the connected view, hide the hover icon.
+ assertThat(icon1).isNull();
+
+ // When exceedsHwSlop is false, it simulates a tap.
+ injectStylusEvent(mHandwritingInitiator, sHwArea1.centerX(), sHwArea1.centerY(),
+ /* exceedsHWSlop */ false);
+
+ PointerIcon icon2 = mHandwritingInitiator.onResolvePointerIcon(mContext, hoverEvent1);
+ assertThat(icon2.getType()).isEqualTo(PointerIcon.TYPE_HANDWRITING);
+ }
+
+ @Test
+ public void onResolvePointerIcon_showHoverIconAfterFocusChange() {
+ // Simulate the case where sTestView1 is focused.
+ mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+ injectStylusEvent(mHandwritingInitiator, sHwArea1.centerX(), sHwArea1.centerY(),
+ /* exceedsHWSlop */ true);
+ // Verify that handwriting started for sTestView1.
+ verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1);
+
+ MotionEvent hoverEvent1 = createStylusHoverEvent(sHwArea1.centerX(), sHwArea1.centerY());
+ PointerIcon icon1 = mHandwritingInitiator.onResolvePointerIcon(mContext, hoverEvent1);
+ // After handwriting is initiated for the connected view, hide the hover icon.
+ assertThat(icon1).isNull();
+
+ // Simulate that focus is switched to mTestView2 first and then switched back.
+ mHandwritingInitiator.onInputConnectionCreated(mTestView2);
+ mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+
+ PointerIcon icon2 = mHandwritingInitiator.onResolvePointerIcon(mContext, hoverEvent1);
+ // After the change of focus, hover icon shows again.
+ assertThat(icon2.getType()).isEqualTo(PointerIcon.TYPE_HANDWRITING);
+ }
+
+ @Test
public void autoHandwriting_whenDisabled_wontStartHW() {
View mockView = createView(sHwArea1, false /* autoHandwritingEnabled */,
true /* isStylusHandwritingAvailable */);
@@ -657,6 +767,35 @@ public class HandwritingInitiatorTest {
return canvas;
}
+ /**
+ * Inject {@link MotionEvent}s to the {@link HandwritingInitiator}.
+ * @param x the x coordinate of the first {@link MotionEvent}.
+ * @param y the y coordinate of the first {@link MotionEvent}.
+ * @param exceedsHWSlop whether the injected {@link MotionEvent} movements exceed the
+ * handwriting slop. If true, it simulates handwriting. Otherwise, it
+ * simulates a tap/click,
+ */
+ private void injectStylusEvent(HandwritingInitiator handwritingInitiator, int x, int y,
+ boolean exceedsHWSlop) {
+ MotionEvent event1 = createStylusEvent(ACTION_DOWN, x, y, 0);
+
+ if (exceedsHWSlop) {
+ x += mHandwritingSlop * 2;
+ } else {
+ x += mHandwritingSlop / 2;
+ }
+ MotionEvent event2 = createStylusEvent(ACTION_MOVE, x, y, 0);
+ MotionEvent event3 = createStylusEvent(ACTION_UP, x, y, 0);
+
+ handwritingInitiator.onTouchEvent(event1);
+ handwritingInitiator.onTouchEvent(event2);
+ handwritingInitiator.onTouchEvent(event3);
+ }
+
+ private MotionEvent createStylusHoverEvent(int x, int y) {
+ return createStylusEvent(ACTION_HOVER_MOVE, x, y, /* eventTime */ 0);
+ }
+
private MotionEvent createStylusEvent(int action, int x, int y, long eventTime) {
MotionEvent.PointerProperties[] properties = MotionEvent.PointerProperties.createArray(1);
properties[0].toolType = MotionEvent.TOOL_TYPE_STYLUS;
@@ -668,6 +807,6 @@ public class HandwritingInitiatorTest {
return MotionEvent.obtain(0 /* downTime */, eventTime /* eventTime */, action, 1,
properties, coords, 0 /* metaState */, 0 /* buttonState */, 1 /* xPrecision */,
1 /* yPrecision */, 0 /* deviceId */, 0 /* edgeFlags */,
- InputDevice.SOURCE_TOUCHSCREEN, 0 /* flags */);
+ InputDevice.SOURCE_STYLUS, 0 /* flags */);
}
}
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java b/core/tests/coretests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java
new file mode 100644
index 000000000000..a6262944e8b0
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.internal.inputmethod;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputConnectionWrapper;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class InputConnectionWrapperTest {
+ @Test
+ public void verifyAllMethodsWrapped() {
+ final var notWrapped = Arrays.stream(InputConnectionWrapper.class.getMethods()).filter(
+ method -> method.isDefault() && method.getDeclaringClass() == InputConnection.class
+ ).collect(Collectors.toList());
+ assertThat(notWrapped).isEmpty();
+ }
+}
diff --git a/graphics/java/android/graphics/RuntimeShader.java b/graphics/java/android/graphics/RuntimeShader.java
index 9c36fc36474c..3e6457919031 100644
--- a/graphics/java/android/graphics/RuntimeShader.java
+++ b/graphics/java/android/graphics/RuntimeShader.java
@@ -19,6 +19,7 @@ package android.graphics;
import android.annotation.ColorInt;
import android.annotation.ColorLong;
import android.annotation.NonNull;
+import android.util.ArrayMap;
import android.view.Window;
import libcore.util.NativeAllocationRegistry;
@@ -256,6 +257,12 @@ public class RuntimeShader extends Shader {
private long mNativeInstanceRuntimeShaderBuilder;
/**
+ * For tracking GC usage. Keep a java-side reference for reachable objects to
+ * enable better heap tracking & tooling support
+ */
+ private ArrayMap<String, Shader> mShaderUniforms = new ArrayMap<>();
+
+ /**
* Creates a new RuntimeShader.
*
* @param shader The text of AGSL shader program to run.
@@ -490,6 +497,7 @@ public class RuntimeShader extends Shader {
if (shader == null) {
throw new NullPointerException("The shader parameter must not be null");
}
+ mShaderUniforms.put(shaderName, shader);
nativeUpdateShader(
mNativeInstanceRuntimeShaderBuilder, shaderName, shader.getNativeInstance());
discardNativeInstance();
@@ -511,6 +519,7 @@ public class RuntimeShader extends Shader {
throw new NullPointerException("The shader parameter must not be null");
}
+ mShaderUniforms.put(shaderName, shader);
nativeUpdateShader(mNativeInstanceRuntimeShaderBuilder, shaderName,
shader.getNativeInstanceWithDirectSampling());
discardNativeInstance();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index d27933e2f800..1bbd3679948b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -31,6 +31,7 @@ import android.content.pm.ShortcutInfo;
import android.graphics.Rect;
import android.os.Binder;
import android.util.CloseGuard;
+import android.util.Slog;
import android.view.SurfaceControl;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -49,6 +50,8 @@ import java.util.concurrent.Executor;
*/
public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
+ private static final String TAG = TaskViewTaskController.class.getSimpleName();
+
private final CloseGuard mGuard = new CloseGuard();
private final ShellTaskOrganizer mTaskOrganizer;
@@ -405,6 +408,11 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
* Call to remove the task from window manager. This task will not appear in recents.
*/
void removeTask() {
+ if (mTaskToken == null) {
+ // Call to remove task before we have one, do nothing
+ Slog.w(TAG, "Trying to remove a task that was never added? (no taskToken)");
+ return;
+ }
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.removeTask(mTaskToken);
mTaskViewTransitions.closeTaskView(wct, this);
@@ -493,11 +501,14 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
.show(mTaskLeash);
// Also reparent on finishTransaction since the finishTransaction will reparent back
// to its "original" parent by default.
+ Rect boundsOnScreen = mTaskViewBase.getCurrentBoundsOnScreen();
finishTransaction.reparent(mTaskLeash, mSurfaceControl)
- .setPosition(mTaskLeash, 0, 0);
- mTaskViewTransitions.updateBoundsState(this, mTaskViewBase.getCurrentBoundsOnScreen());
+ .setPosition(mTaskLeash, 0, 0)
+ // TODO: maybe once b/280900002 is fixed this will be unnecessary
+ .setWindowCrop(mTaskLeash, boundsOnScreen.width(), boundsOnScreen.height());
+ mTaskViewTransitions.updateBoundsState(this, boundsOnScreen);
mTaskViewTransitions.updateVisibilityState(this, true /* visible */);
- wct.setBounds(mTaskToken, mTaskViewBase.getCurrentBoundsOnScreen());
+ wct.setBounds(mTaskToken, boundsOnScreen);
} else {
// The surface has already been destroyed before the task has appeared,
// so go ahead and hide the task entirely
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
index 0a515cdea465..81fc8438eec0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
@@ -520,4 +520,28 @@ public class TaskViewTest extends ShellTestCase {
verify(mTaskViewTransitions).updateVisibilityState(eq(mTaskViewTaskController), eq(false));
verify(mTaskViewTransitions, never()).updateBoundsState(eq(mTaskViewTaskController), any());
}
+
+ @Test
+ public void testRemoveTaskView_noTask() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+ mTaskView.removeTask();
+ verify(mTaskViewTransitions, never()).closeTaskView(any(), any());
+ }
+
+ @Test
+ public void testRemoveTaskView() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+ mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+ mLeash, wct);
+
+ verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any());
+
+ mTaskView.removeTask();
+ verify(mTaskViewTransitions).closeTaskView(any(), eq(mTaskViewTaskController));
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
index 1f75e81c201f..b15378570358 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
@@ -16,11 +16,6 @@
package com.android.keyguard;
-import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_ACTIVE_DATA_SUB_CHANGED;
-import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_ON_SIM_STATE_CHANGED;
-import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_ON_TELEPHONY_CAPABLE;
-import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_REFRESH_CARRIER_INFO;
-
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -37,7 +32,6 @@ import android.util.Log;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
-import com.android.keyguard.logging.CarrierTextManagerLogger;
import com.android.settingslib.WirelessUtils;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
@@ -46,7 +40,6 @@ import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository;
import com.android.systemui.telephony.TelephonyListenerManager;
-import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -75,7 +68,6 @@ public class CarrierTextManager {
private final AtomicBoolean mNetworkSupported = new AtomicBoolean();
@VisibleForTesting
protected KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private final CarrierTextManagerLogger mLogger;
private final WifiRepository mWifiRepository;
private final boolean[] mSimErrorState;
private final int mSimSlotsNumber;
@@ -105,13 +97,19 @@ public class CarrierTextManager {
protected final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
@Override
public void onRefreshCarrierInfo() {
- mLogger.logUpdateCarrierTextForReason(REASON_REFRESH_CARRIER_INFO);
+ if (DEBUG) {
+ Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: "
+ + Boolean.toString(mTelephonyCapable));
+ }
updateCarrierText();
}
@Override
public void onTelephonyCapable(boolean capable) {
- mLogger.logUpdateCarrierTextForReason(REASON_ON_TELEPHONY_CAPABLE);
+ if (DEBUG) {
+ Log.d(TAG, "onTelephonyCapable() mTelephonyCapable: "
+ + Boolean.toString(capable));
+ }
mTelephonyCapable = capable;
updateCarrierText();
}
@@ -123,7 +121,7 @@ public class CarrierTextManager {
return;
}
- mLogger.logUpdateCarrierTextForReason(REASON_ON_SIM_STATE_CHANGED);
+ if (DEBUG) Log.d(TAG, "onSimStateChanged: " + getStatusForIccState(simState));
if (getStatusForIccState(simState) == CarrierTextManager.StatusMode.SimIoError) {
mSimErrorState[slotId] = true;
updateCarrierText();
@@ -139,7 +137,6 @@ public class CarrierTextManager {
@Override
public void onActiveDataSubscriptionIdChanged(int subId) {
if (mNetworkSupported.get() && mCarrierTextCallback != null) {
- mLogger.logUpdateCarrierTextForReason(REASON_ACTIVE_DATA_SUB_CHANGED);
updateCarrierText();
}
}
@@ -178,9 +175,7 @@ public class CarrierTextManager {
WakefulnessLifecycle wakefulnessLifecycle,
@Main Executor mainExecutor,
@Background Executor bgExecutor,
- KeyguardUpdateMonitor keyguardUpdateMonitor,
- CarrierTextManagerLogger logger) {
-
+ KeyguardUpdateMonitor keyguardUpdateMonitor) {
mContext = context;
mIsEmergencyCallCapable = telephonyManager.isVoiceCapable();
@@ -196,7 +191,6 @@ public class CarrierTextManager {
mMainExecutor = mainExecutor;
mBgExecutor = bgExecutor;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mLogger = logger;
mBgExecutor.execute(() -> {
boolean supported = mContext.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
@@ -321,7 +315,7 @@ public class CarrierTextManager {
subOrderBySlot[i] = -1;
}
final CharSequence[] carrierNames = new CharSequence[numSubs];
- mLogger.logUpdate(numSubs);
+ if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs);
for (int i = 0; i < numSubs; i++) {
int subId = subs.get(i).getSubscriptionId();
@@ -331,7 +325,9 @@ public class CarrierTextManager {
int simState = mKeyguardUpdateMonitor.getSimState(subId);
CharSequence carrierName = subs.get(i).getCarrierName();
CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
- mLogger.logUpdateLoopStart(subId, simState, String.valueOf(carrierName));
+ if (DEBUG) {
+ Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName);
+ }
if (carrierTextForSimState != null) {
allSimsMissing = false;
carrierNames[i] = carrierTextForSimState;
@@ -344,7 +340,9 @@ public class CarrierTextManager {
// Wi-Fi is disassociated or disabled
if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
|| mWifiRepository.isWifiConnectedWithValidSsid()) {
- mLogger.logUpdateWfcCheck();
+ if (DEBUG) {
+ Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
+ }
anySimReadyAndInService = true;
}
}
@@ -381,7 +379,7 @@ public class CarrierTextManager {
if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_PLMN, false)) {
plmn = i.getStringExtra(TelephonyManager.EXTRA_PLMN);
}
- mLogger.logUpdateFromStickyBroadcast(plmn, spn);
+ if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn);
if (Objects.equals(plmn, spn)) {
text = plmn;
} else {
@@ -411,7 +409,6 @@ public class CarrierTextManager {
!allSimsMissing,
subsIds,
airplaneMode);
- mLogger.logCallbackSentFromUpdate(info);
postToCallback(info);
Trace.endSection();
}
@@ -648,7 +645,6 @@ public class CarrierTextManager {
private final Executor mMainExecutor;
private final Executor mBgExecutor;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private final CarrierTextManagerLogger mLogger;
private boolean mShowAirplaneMode;
private boolean mShowMissingSim;
@@ -662,8 +658,7 @@ public class CarrierTextManager {
WakefulnessLifecycle wakefulnessLifecycle,
@Main Executor mainExecutor,
@Background Executor bgExecutor,
- KeyguardUpdateMonitor keyguardUpdateMonitor,
- CarrierTextManagerLogger logger) {
+ KeyguardUpdateMonitor keyguardUpdateMonitor) {
mContext = context;
mSeparator = resources.getString(
com.android.internal.R.string.kg_text_message_separator);
@@ -674,7 +669,6 @@ public class CarrierTextManager {
mMainExecutor = mainExecutor;
mBgExecutor = bgExecutor;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mLogger = logger;
}
/** */
@@ -694,7 +688,7 @@ public class CarrierTextManager {
return new CarrierTextManager(
mContext, mSeparator, mShowAirplaneMode, mShowMissingSim, mWifiRepository,
mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle,
- mMainExecutor, mBgExecutor, mKeyguardUpdateMonitor, mLogger);
+ mMainExecutor, mBgExecutor, mKeyguardUpdateMonitor);
}
}
/**
@@ -722,17 +716,6 @@ public class CarrierTextManager {
this.subscriptionIds = subscriptionIds;
this.airplaneMode = airplaneMode;
}
-
- @Override
- public String toString() {
- return "CarrierTextCallbackInfo{"
- + "carrierText=" + carrierText
- + ", listOfCarriers=" + Arrays.toString(listOfCarriers)
- + ", anySimReady=" + anySimReady
- + ", subscriptionIds=" + Arrays.toString(subscriptionIds)
- + ", airplaneMode=" + airplaneMode
- + '}';
- }
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt
deleted file mode 100644
index 3dc787cab0b2..000000000000
--- a/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * 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.keyguard.logging
-
-import androidx.annotation.IntDef
-import com.android.keyguard.CarrierTextManager.CarrierTextCallbackInfo
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.dagger.CarrierTextManagerLog
-import javax.inject.Inject
-
-/** Logger adapter for [CarrierTextManager] to add detailed messages in a [LogBuffer] */
-@SysUISingleton
-class CarrierTextManagerLogger @Inject constructor(@CarrierTextManagerLog val buffer: LogBuffer) {
- /**
- * This method and the methods below trace the execution of CarrierTextManager.updateCarrierText
- */
- fun logUpdate(numSubs: Int) {
- buffer.log(
- TAG,
- LogLevel.VERBOSE,
- { int1 = numSubs },
- { "updateCarrierText: numSubs=$int1" },
- )
- }
-
- fun logUpdateLoopStart(sub: Int, simState: Int, carrierName: String) {
- buffer.log(
- TAG,
- LogLevel.VERBOSE,
- {
- int1 = sub
- int2 = simState
- str1 = carrierName
- },
- { "┣ updateCarrierText: updating sub=$int1 simState=$int2 carrierName=$str1" },
- )
- }
-
- fun logUpdateWfcCheck() {
- buffer.log(
- TAG,
- LogLevel.VERBOSE,
- {},
- { "┣ updateCarrierText: found WFC state" },
- )
- }
-
- fun logUpdateFromStickyBroadcast(plmn: String, spn: String) {
- buffer.log(
- TAG,
- LogLevel.VERBOSE,
- {
- str1 = plmn
- str2 = spn
- },
- { "┣ updateCarrierText: getting PLMN/SPN sticky brdcst. plmn=$str1, spn=$str1" },
- )
- }
-
- /** De-structures the info object so that we don't have to generate new strings */
- fun logCallbackSentFromUpdate(info: CarrierTextCallbackInfo) {
- buffer.log(
- TAG,
- LogLevel.VERBOSE,
- {
- str1 = "${info.carrierText}"
- bool1 = info.anySimReady
- bool2 = info.airplaneMode
- },
- {
- "â”— updateCarrierText: " +
- "result=(carrierText=$str1, anySimReady=$bool1, airplaneMode=$bool2)"
- },
- )
- }
-
- /**
- * Used to log the starting point for _why_ the carrier text is updating. In order to keep us
- * from holding on to too many objects, we'll just use simple ints for reasons here
- */
- fun logUpdateCarrierTextForReason(@CarrierTextRefreshReason reason: Int) {
- buffer.log(
- TAG,
- LogLevel.DEBUG,
- { int1 = reason },
- { "refreshing carrier info for reason: ${reason.reasonMessage()}" }
- )
- }
-
- companion object {
- const val REASON_REFRESH_CARRIER_INFO = 1
- const val REASON_ON_TELEPHONY_CAPABLE = 2
- const val REASON_ON_SIM_STATE_CHANGED = 3
- const val REASON_ACTIVE_DATA_SUB_CHANGED = 4
-
- @Retention(AnnotationRetention.SOURCE)
- @IntDef(
- value =
- [
- REASON_REFRESH_CARRIER_INFO,
- REASON_ON_TELEPHONY_CAPABLE,
- REASON_ON_SIM_STATE_CHANGED,
- REASON_ACTIVE_DATA_SUB_CHANGED,
- ]
- )
- annotation class CarrierTextRefreshReason
-
- private fun @receiver:CarrierTextRefreshReason Int.reasonMessage() =
- when (this) {
- REASON_REFRESH_CARRIER_INFO -> "REFRESH_CARRIER_INFO"
- REASON_ON_TELEPHONY_CAPABLE -> "ON_TELEPHONY_CAPABLE"
- REASON_ON_SIM_STATE_CHANGED -> "SIM_STATE_CHANGED"
- REASON_ACTIVE_DATA_SUB_CHANGED -> "ACTIVE_DATA_SUB_CHANGED"
- else -> "unknown"
- }
- }
-}
-
-private const val TAG = "CarrierTextManagerLog"
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
index daafea8b62c7..f05152ae8418 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
@@ -17,9 +17,11 @@
package com.android.keyguard.logging
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.TrustManagedModel
import com.android.systemui.keyguard.shared.model.TrustModel
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogLevel.DEBUG
import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog
import javax.inject.Inject
@@ -39,7 +41,7 @@ constructor(
) {
logBuffer.log(
TAG,
- LogLevel.DEBUG,
+ DEBUG,
{
bool1 = enabled
bool2 = newlyUnlocked
@@ -65,7 +67,7 @@ constructor(
fun trustModelEmitted(value: TrustModel) {
logBuffer.log(
TAG,
- LogLevel.DEBUG,
+ DEBUG,
{
int1 = value.userId
bool1 = value.isTrusted
@@ -77,12 +79,40 @@ constructor(
fun isCurrentUserTrusted(isCurrentUserTrusted: Boolean) {
logBuffer.log(
TAG,
- LogLevel.DEBUG,
+ DEBUG,
{ bool1 = isCurrentUserTrusted },
{ "isCurrentUserTrusted emitted: $bool1" }
)
}
+ fun isCurrentUserTrustManaged(isTrustManaged: Boolean) {
+ logBuffer.log(TAG, DEBUG, { bool1 = isTrustManaged }, { "isTrustManaged emitted: $bool1" })
+ }
+
+ fun onTrustManagedChanged(trustManaged: Boolean, userId: Int) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ bool1 = trustManaged
+ int1 = userId
+ },
+ { "onTrustManagedChanged isTrustManaged: $bool1 for user: $int1" }
+ )
+ }
+
+ fun trustManagedModelEmitted(it: TrustManagedModel) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ bool1 = it.isTrustManaged
+ int1 = it.userId
+ },
+ { "trustManagedModel emitted: userId: $int1, isTrustManaged: $bool1" }
+ )
+ }
+
companion object {
const val TAG = "TrustRepositoryLog"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
index 1fa018bcbf39..e4906696a5e3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
@@ -22,6 +22,7 @@ import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLoggin
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.shared.model.TrustManagedModel
import com.android.systemui.keyguard.shared.model.TrustModel
import com.android.systemui.user.data.repository.UserRepository
import javax.inject.Inject
@@ -37,6 +38,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
/** Encapsulates any state relevant to trust agents and trust grants. */
interface TrustRepository {
@@ -45,6 +47,9 @@ interface TrustRepository {
/** Flow representing whether active unlock is available for the current user. */
val isCurrentUserActiveUnlockAvailable: StateFlow<Boolean>
+
+ /** Reports that whether trust is managed has changed for the current user. */
+ val isCurrentUserTrustManaged: StateFlow<Boolean>
}
@SysUISingleton
@@ -57,6 +62,7 @@ constructor(
private val logger: TrustRepositoryLogger,
) : TrustRepository {
private val latestTrustModelForUser = mutableMapOf<Int, TrustModel>()
+ private val trustManagedForUser = mutableMapOf<Int, TrustManagedModel>()
private val trust =
conflatedCallbackFlow {
@@ -79,9 +85,16 @@ constructor(
override fun onTrustError(message: CharSequence?) = Unit
- override fun onTrustManagedChanged(enabled: Boolean, userId: Int) = Unit
-
override fun onEnabledTrustAgentsChanged(userId: Int) = Unit
+
+ override fun onTrustManagedChanged(isTrustManaged: Boolean, userId: Int) {
+ logger.onTrustManagedChanged(isTrustManaged, userId)
+ trySendWithFailureLogging(
+ TrustManagedModel(userId, isTrustManaged),
+ TrustRepositoryLogger.TAG,
+ "onTrustManagedChanged"
+ )
+ }
}
trustManager.registerTrustListener(callback)
logger.trustListenerRegistered()
@@ -91,18 +104,43 @@ constructor(
}
}
.onEach {
- latestTrustModelForUser[it.userId] = it
- logger.trustModelEmitted(it)
+ when (it) {
+ is TrustModel -> {
+ latestTrustModelForUser[it.userId] = it
+ logger.trustModelEmitted(it)
+ }
+ is TrustManagedModel -> {
+ trustManagedForUser[it.userId] = it
+ logger.trustManagedModelEmitted(it)
+ }
+ }
}
.shareIn(applicationScope, started = SharingStarted.Eagerly, replay = 1)
- override val isCurrentUserTrusted: Flow<Boolean> =
- combine(trust, userRepository.selectedUserInfo, ::Pair)
- .map { latestTrustModelForUser[it.second.id]?.isTrusted ?: false }
- .distinctUntilChanged()
- .onEach { logger.isCurrentUserTrusted(it) }
- .onStart { emit(false) }
-
// TODO: Implement based on TrustManager callback b/267322286
override val isCurrentUserActiveUnlockAvailable: StateFlow<Boolean> = MutableStateFlow(true)
+
+ override val isCurrentUserTrustManaged: StateFlow<Boolean>
+ get() =
+ combine(trust, userRepository.selectedUserInfo, ::Pair)
+ .map { isUserTrustManaged(it.second.id) }
+ .distinctUntilChanged()
+ .onEach { logger.isCurrentUserTrustManaged(it) }
+ .onStart { emit(false) }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false
+ )
+
+ private fun isUserTrustManaged(userId: Int) =
+ trustManagedForUser[userId]?.isTrustManaged ?: false
+
+ override val isCurrentUserTrusted: Flow<Boolean>
+ get() =
+ combine(trust, userRepository.selectedUserInfo, ::Pair)
+ .map { latestTrustModelForUser[it.second.id]?.isTrusted ?: false }
+ .distinctUntilChanged()
+ .onEach { logger.isCurrentUserTrusted(it) }
+ .onStart { emit(false) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt
index 4fd14b1ce087..cdfab1a90b66 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt
@@ -16,10 +16,18 @@
package com.android.systemui.keyguard.shared.model
+sealed class TrustMessage
+
/** Represents the trust state */
data class TrustModel(
/** If true, the system believes the environment to be trusted. */
val isTrusted: Boolean,
/** The user, for which the trust changed. */
val userId: Int,
-)
+) : TrustMessage()
+
+/** Represents where trust agents are enabled for a particular user. */
+data class TrustManagedModel(
+ val userId: Int,
+ val isTrustManaged: Boolean,
+) : TrustMessage()
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/CarrierTextManagerLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/CarrierTextManagerLog.kt
deleted file mode 100644
index 62b80b20a673..000000000000
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/CarrierTextManagerLog.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package com.android.systemui.log.dagger
-
-import javax.inject.Qualifier
-
-/** A [LogBuffer] for detailed carrier text logs. */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class CarrierTextManagerLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 66c3c02df1fc..9be18ace79fa 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -373,16 +373,6 @@ public class LogModule {
}
/**
- * Provides a {@link LogBuffer} for use by {@link com.android.keyguard.KeyguardUpdateMonitor}.
- */
- @Provides
- @SysUISingleton
- @CarrierTextManagerLog
- public static LogBuffer provideCarrierTextManagerLog(LogBufferFactory factory) {
- return factory.create("CarrierTextManagerLog", 100);
- }
-
- /**
* Provides a {@link LogBuffer} for use by {@link com.android.systemui.ScreenDecorations}.
*/
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneContainerConfig.kt b/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneContainerConfig.kt
new file mode 100644
index 000000000000..d0769ebe941e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneContainerConfig.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.scene.data.model
+
+import com.android.systemui.scene.shared.model.SceneKey
+
+/** Models the configuration of a single scene container. */
+data class SceneContainerConfig(
+ /** Container name. Must be unique across all containers in System UI. */
+ val name: String,
+
+ /**
+ * The keys to all scenes in the container, sorted by z-order such that the last one renders on
+ * top of all previous ones. Scene keys within the same container must not repeat but it's okay
+ * to have the same scene keys in different containers.
+ */
+ val sceneKeys: List<SceneKey>,
+
+ /**
+ * The key of the scene that is the initial current scene when the container is first set up,
+ * before taking any application state in to account.
+ */
+ val initialSceneKey: SceneKey,
+) {
+ init {
+ check(sceneKeys.isNotEmpty()) { "A container must have at least one scene key." }
+
+ check(sceneKeys.contains(initialSceneKey)) {
+ "The initial key \"$initialSceneKey\" is not present in this container."
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
new file mode 100644
index 000000000000..61b162b014d8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
@@ -0,0 +1,136 @@
+/*
+ * 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.scene.data.repository
+
+import com.android.systemui.scene.data.model.SceneContainerConfig
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Source of truth for scene framework application state. */
+class SceneContainerRepository
+@Inject
+constructor(
+ containerConfigurations: Set<SceneContainerConfig>,
+) {
+
+ private val containerConfigByName: Map<String, SceneContainerConfig> =
+ containerConfigurations.associateBy { config -> config.name }
+ private val containerVisibilityByName: Map<String, MutableStateFlow<Boolean>> =
+ containerConfigByName
+ .map { (containerName, _) -> containerName to MutableStateFlow(true) }
+ .toMap()
+ private val currentSceneByContainerName: Map<String, MutableStateFlow<SceneModel>> =
+ containerConfigByName
+ .map { (containerName, config) ->
+ containerName to MutableStateFlow(SceneModel(config.initialSceneKey))
+ }
+ .toMap()
+ private val sceneTransitionProgressByContainerName: Map<String, MutableStateFlow<Float>> =
+ containerConfigByName
+ .map { (containerName, _) -> containerName to MutableStateFlow(1f) }
+ .toMap()
+
+ init {
+ val repeatedContainerNames =
+ containerConfigurations
+ .groupingBy { config -> config.name }
+ .eachCount()
+ .filter { (_, count) -> count > 1 }
+ check(repeatedContainerNames.isEmpty()) {
+ "Container names must be unique. The following container names appear more than once: ${
+ repeatedContainerNames
+ .map { (name, count) -> "\"$name\" appears $count times" }
+ .joinToString(", ")
+ }"
+ }
+ }
+
+ /**
+ * Returns the keys to all scenes in the container with the given name.
+ *
+ * The scenes will be sorted in z-order such that the last one is the one that should be
+ * rendered on top of all previous ones.
+ */
+ fun allSceneKeys(containerName: String): List<SceneKey> {
+ return containerConfigByName[containerName]?.sceneKeys
+ ?: error(noSuchContainerErrorMessage(containerName))
+ }
+
+ /** Sets the current scene in the container with the given name. */
+ fun setCurrentScene(containerName: String, scene: SceneModel) {
+ check(allSceneKeys(containerName).contains(scene.key)) {
+ """
+ Cannot set current scene key to "${scene.key}". The container "$containerName" does
+ not contain a scene with that key.
+ """
+ .trimIndent()
+ }
+
+ currentSceneByContainerName.setValue(containerName, scene)
+ }
+
+ /** The current scene in the container with the given name. */
+ fun currentScene(containerName: String): StateFlow<SceneModel> {
+ return currentSceneByContainerName.mutableOrError(containerName).asStateFlow()
+ }
+
+ /** Sets whether the container with the given name is visible. */
+ fun setVisible(containerName: String, isVisible: Boolean) {
+ containerVisibilityByName.setValue(containerName, isVisible)
+ }
+
+ /** Whether the container with the given name should be visible. */
+ fun isVisible(containerName: String): StateFlow<Boolean> {
+ return containerVisibilityByName.mutableOrError(containerName).asStateFlow()
+ }
+
+ /** Sets scene transition progress to the current scene in the container with the given name. */
+ fun setSceneTransitionProgress(containerName: String, progress: Float) {
+ sceneTransitionProgressByContainerName.setValue(containerName, progress)
+ }
+
+ /** Progress of the transition into the current scene in the container with the given name. */
+ fun sceneTransitionProgress(containerName: String): StateFlow<Float> {
+ return sceneTransitionProgressByContainerName.mutableOrError(containerName).asStateFlow()
+ }
+
+ private fun <T> Map<String, MutableStateFlow<T>>.mutableOrError(
+ containerName: String,
+ ): MutableStateFlow<T> {
+ return this[containerName] ?: error(noSuchContainerErrorMessage(containerName))
+ }
+
+ private fun <T> Map<String, MutableStateFlow<T>>.setValue(
+ containerName: String,
+ value: T,
+ ) {
+ val mutable = mutableOrError(containerName)
+ mutable.value = value
+ }
+
+ private fun noSuchContainerErrorMessage(containerName: String): String {
+ return """
+ No container named "$containerName". Existing containers:
+ ${containerConfigByName.values.joinToString(", ") { it.name }}
+ """
+ .trimIndent()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
new file mode 100644
index 000000000000..1e55975658a5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.scene.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.scene.data.repository.SceneContainerRepository
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
+
+/** Business logic and app state accessors for the scene framework. */
+@SysUISingleton
+class SceneInteractor
+@Inject
+constructor(
+ private val repository: SceneContainerRepository,
+) {
+
+ /**
+ * Returns the keys of all scenes in the container with the given name.
+ *
+ * The scenes will be sorted in z-order such that the last one is the one that should be
+ * rendered on top of all previous ones.
+ */
+ fun allSceneKeys(containerName: String): List<SceneKey> {
+ return repository.allSceneKeys(containerName)
+ }
+
+ /** Sets the scene in the container with the given name. */
+ fun setCurrentScene(containerName: String, scene: SceneModel) {
+ repository.setCurrentScene(containerName, scene)
+ }
+
+ /** The current scene in the container with the given name. */
+ fun currentScene(containerName: String): StateFlow<SceneModel> {
+ return repository.currentScene(containerName)
+ }
+
+ /** Sets the visibility of the container with the given name. */
+ fun setVisible(containerName: String, isVisible: Boolean) {
+ return repository.setVisible(containerName, isVisible)
+ }
+
+ /** Whether the container with the given name is visible. */
+ fun isVisible(containerName: String): StateFlow<Boolean> {
+ return repository.isVisible(containerName)
+ }
+
+ /** Sets scene transition progress to the current scene in the container with the given name. */
+ fun setSceneTransitionProgress(containerName: String, progress: Float) {
+ repository.setSceneTransitionProgress(containerName, progress)
+ }
+
+ /** Progress of the transition into the current scene in the container with the given name. */
+ fun sceneTransitionProgress(containerName: String): StateFlow<Float> {
+ return repository.sceneTransitionProgress(containerName)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
new file mode 100644
index 000000000000..435ff4baffd8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.scene.shared.model
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * Defines interface for classes that can describe a "scene".
+ *
+ * In the scene framework, there can be multiple scenes in a single scene "container". The container
+ * takes care of rendering the current scene and allowing scenes to be switched from one to another
+ * based on either user action (for example, swiping down while on the lock screen scene may switch
+ * to the shade scene).
+ *
+ * The framework also supports multiple containers, each one with its own configuration.
+ */
+interface Scene {
+
+ /** Uniquely-identifying key for this scene. The key must be unique within its container. */
+ val key: SceneKey
+
+ /**
+ * Returns a mapping between [UserAction] and flows that emit a [SceneModel].
+ *
+ * When the scene framework detects the user action, it starts a transition to the scene
+ * described by the latest value in the flow that's mapped from that user action.
+ *
+ * Once the [Scene] becomes the current one, the scene framework will invoke this method and set
+ * up collectors to watch for new values emitted to each of the flows. If a value is added to
+ * the map at a given [UserAction], the framework will set up user input handling for that
+ * [UserAction] and, if such a user action is detected, the framework will initiate a transition
+ * to that [SceneModel].
+ *
+ * Note that calling this method does _not_ mean that the given user action has occurred.
+ * Instead, the method is called before any user action/gesture is detected so that the
+ * framework can decide whether to set up gesture/input detectors/listeners for that type of
+ * user action.
+ *
+ * Note that a missing value for a specific [UserAction] means that the user action of the given
+ * type is not currently active in the scene and should be ignored by the framework, while the
+ * current scene is this one.
+ *
+ * The API is designed such that it's possible to emit ever-changing values for each
+ * [UserAction] to enable, disable, or change the destination scene of a given user action.
+ */
+ fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
+ MutableStateFlow(emptyMap<UserAction, SceneModel>()).asStateFlow()
+}
+
+/** Enumerates all scene framework supported user actions. */
+sealed interface UserAction {
+
+ /** The user is scrolling, dragging, swiping, or flinging. */
+ data class Swipe(
+ /** The direction of the swipe. */
+ val direction: Direction,
+ /** The number of pointers that were used (for example, one or two fingers). */
+ val pointerCount: Int = 1,
+ ) : UserAction
+
+ /** The user has hit the back button or performed the back navigation gesture. */
+ object Back : UserAction
+}
+
+/** Enumerates all known "cardinal" directions for user actions. */
+enum class Direction {
+ LEFT,
+ UP,
+ RIGHT,
+ DOWN,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneKey.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneKey.kt
new file mode 100644
index 000000000000..9ef439d72118
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneKey.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.scene.shared.model
+
+/** Keys of all known scenes. */
+sealed class SceneKey(
+ private val loggingName: String,
+) {
+ /**
+ * The bouncer is the scene that displays authentication challenges like PIN, password, or
+ * pattern.
+ */
+ object Bouncer : SceneKey("bouncer")
+
+ /**
+ * "Gone" is not a real scene but rather the absence of scenes when we want to skip showing any
+ * content from the scene framework.
+ */
+ object Gone : SceneKey("gone")
+
+ /** The lock screen is the scene that shows when the device is locked. */
+ object LockScreen : SceneKey("lockscreen")
+
+ /**
+ * The shade is the scene whose primary purpose is to show a scrollable list of notifications.
+ */
+ object Shade : SceneKey("shade")
+
+ /** The quick settings scene shows the quick setting tiles. */
+ object QuickSettings : SceneKey("quick_settings")
+
+ override fun toString(): String {
+ return loggingName
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneModel.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneModel.kt
new file mode 100644
index 000000000000..f3d549f03868
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneModel.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.scene.shared.model
+
+/** Models a scene. */
+data class SceneModel(
+
+ /** The key of the scene. */
+ val key: SceneKey,
+
+ /** An optional name for the transition that led to this scene being the current scene. */
+ val transitionName: String? = null,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
new file mode 100644
index 000000000000..afc053151ab5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.scene.ui.viewmodel
+
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.flow.StateFlow
+
+/** Models UI state for a single scene container. */
+class SceneContainerViewModel
+@AssistedInject
+constructor(
+ private val interactor: SceneInteractor,
+ @Assisted private val containerName: String,
+) {
+ /**
+ * Keys of all scenes in the container.
+ *
+ * The scenes will be sorted in z-order such that the last one is the one that should be
+ * rendered on top of all previous ones.
+ */
+ val allSceneKeys: List<SceneKey> = interactor.allSceneKeys(containerName)
+
+ /** The current scene. */
+ val currentScene: StateFlow<SceneModel> = interactor.currentScene(containerName)
+
+ /** Whether the container is visible. */
+ val isVisible: StateFlow<Boolean> = interactor.isVisible(containerName)
+
+ /** Requests a transition to the scene with the given key. */
+ fun setCurrentScene(scene: SceneModel) {
+ interactor.setCurrentScene(containerName, scene)
+ }
+
+ /** Notifies of the progress of a scene transition. */
+ fun setSceneTransitionProgress(progress: Float) {
+ interactor.setSceneTransitionProgress(containerName, progress)
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ containerName: String,
+ ): SceneContainerViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
index 991ff56e683c..eb20bba0d21f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
@@ -21,7 +21,6 @@ import androidx.annotation.VisibleForTesting
import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.MobileMappings
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.demomode.DemoMode
import com.android.systemui.demomode.DemoModeController
@@ -63,7 +62,6 @@ import kotlinx.coroutines.flow.stateIn
*/
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
-@SysUISingleton
class MobileRepositorySwitcher
@Inject
constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
index e96288ab9ef9..b1296179d7f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
@@ -19,7 +19,6 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository
import android.os.Bundle
import androidx.annotation.VisibleForTesting
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.demomode.DemoMode
import com.android.systemui.demomode.DemoModeController
@@ -55,7 +54,6 @@ import kotlinx.coroutines.flow.stateIn
*/
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
-@SysUISingleton
class WifiRepositorySwitcher
@Inject
constructor(
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
index 0b9ba78229e9..5557efa97e3e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
@@ -47,7 +47,6 @@ import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.text.TextUtils;
-import com.android.keyguard.logging.CarrierTextManagerLogger;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -97,8 +96,6 @@ public class CarrierTextManagerTest extends SysuiTestCase {
@Mock
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock
- private CarrierTextManagerLogger mLogger;
- @Mock
private PackageManager mPackageManager;
@Mock
private TelephonyManager mTelephonyManager;
@@ -147,7 +144,7 @@ public class CarrierTextManagerTest extends SysuiTestCase {
mCarrierTextManager = new CarrierTextManager.Builder(
mContext, mContext.getResources(), mWifiRepository,
mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle, mMainExecutor,
- mBgExecutor, mKeyguardUpdateMonitor, mLogger)
+ mBgExecutor, mKeyguardUpdateMonitor)
.setShowAirplaneMode(true)
.setShowMissingSim(true)
.build();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
index c2195c7bc2c1..8611359adc71 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
@@ -23,6 +23,7 @@ import androidx.test.filters.SmallTest
import com.android.keyguard.logging.TrustRepositoryLogger
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.FlowValue
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogcatEchoTracker
@@ -48,12 +49,14 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
class TrustRepositoryTest : SysuiTestCase() {
@Mock private lateinit var trustManager: TrustManager
- @Captor private lateinit var listenerCaptor: ArgumentCaptor<TrustManager.TrustListener>
+ @Captor private lateinit var listener: ArgumentCaptor<TrustManager.TrustListener>
private lateinit var userRepository: FakeUserRepository
private lateinit var testScope: TestScope
private val users = listOf(UserInfo(1, "user 1", 0), UserInfo(2, "user 1", 0))
private lateinit var underTest: TrustRepository
+ private lateinit var isCurrentUserTrusted: FlowValue<Boolean?>
+ private lateinit var isCurrentUserTrustManaged: FlowValue<Boolean?>
@Before
fun setUp() {
@@ -70,21 +73,90 @@ class TrustRepositoryTest : SysuiTestCase() {
TrustRepositoryImpl(testScope.backgroundScope, userRepository, trustManager, logger)
}
+ fun TestScope.init() {
+ runCurrent()
+ verify(trustManager).registerTrustListener(listener.capture())
+ isCurrentUserTrustManaged = collectLastValue(underTest.isCurrentUserTrustManaged)
+ isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
+ }
+
@Test
- fun isCurrentUserTrusted_whenTrustChanges_emitsLatestValue() =
+ fun isCurrentUserTrustManaged_whenItChanges_emitsLatestValue() =
+ testScope.runTest {
+ init()
+
+ val currentUserId = users[0].id
+ userRepository.setSelectedUserInfo(users[0])
+
+ listener.value.onTrustManagedChanged(true, currentUserId)
+ assertThat(isCurrentUserTrustManaged()).isTrue()
+
+ listener.value.onTrustManagedChanged(false, currentUserId)
+
+ assertThat(isCurrentUserTrustManaged()).isFalse()
+ }
+
+ @Test
+ fun isCurrentUserTrustManaged_isFalse_byDefault() =
testScope.runTest {
runCurrent()
- verify(trustManager).registerTrustListener(listenerCaptor.capture())
- val listener = listenerCaptor.value
+
+ assertThat(collectLastValue(underTest.isCurrentUserTrustManaged)()).isFalse()
+ }
+
+ @Test
+ fun isCurrentUserTrustManaged_whenItChangesForDifferentUser_noops() =
+ testScope.runTest {
+ init()
+ userRepository.setSelectedUserInfo(users[0])
+
+ // current user's trust is managed.
+ listener.value.onTrustManagedChanged(true, users[0].id)
+ // some other user's trust is not managed.
+ listener.value.onTrustManagedChanged(false, users[1].id)
+
+ assertThat(isCurrentUserTrustManaged()).isTrue()
+ }
+
+ @Test
+ fun isCurrentUserTrustManaged_whenUserChangesWithoutRecentTrustChange_defaultsToFalse() =
+ testScope.runTest {
+ init()
+
+ userRepository.setSelectedUserInfo(users[0])
+ listener.value.onTrustManagedChanged(true, users[0].id)
+
+ userRepository.setSelectedUserInfo(users[1])
+
+ assertThat(isCurrentUserTrustManaged()).isFalse()
+ }
+
+ @Test
+ fun isCurrentUserTrustManaged_itChangesFirstBeforeUserInfoChanges_emitsCorrectValue() =
+ testScope.runTest {
+ init()
+ userRepository.setSelectedUserInfo(users[1])
+
+ listener.value.onTrustManagedChanged(true, users[0].id)
+ assertThat(isCurrentUserTrustManaged()).isFalse()
+
+ userRepository.setSelectedUserInfo(users[0])
+
+ assertThat(isCurrentUserTrustManaged()).isTrue()
+ }
+
+ @Test
+ fun isCurrentUserTrusted_whenTrustChanges_emitsLatestValue() =
+ testScope.runTest {
+ init()
val currentUserId = users[0].id
userRepository.setSelectedUserInfo(users[0])
- val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
- listener.onTrustChanged(true, false, currentUserId, 0, emptyList())
+ listener.value.onTrustChanged(true, false, currentUserId, 0, emptyList())
assertThat(isCurrentUserTrusted()).isTrue()
- listener.onTrustChanged(false, false, currentUserId, 0, emptyList())
+ listener.value.onTrustChanged(false, false, currentUserId, 0, emptyList())
assertThat(isCurrentUserTrusted()).isFalse()
}
@@ -102,16 +174,14 @@ class TrustRepositoryTest : SysuiTestCase() {
@Test
fun isCurrentUserTrusted_whenTrustChangesForDifferentUser_noop() =
testScope.runTest {
- runCurrent()
- verify(trustManager).registerTrustListener(listenerCaptor.capture())
+ init()
+
userRepository.setSelectedUserInfo(users[0])
- val listener = listenerCaptor.value
- val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
// current user is trusted.
- listener.onTrustChanged(true, true, users[0].id, 0, emptyList())
+ listener.value.onTrustChanged(true, true, users[0].id, 0, emptyList())
// some other user is not trusted.
- listener.onTrustChanged(false, false, users[1].id, 0, emptyList())
+ listener.value.onTrustChanged(false, false, users[1].id, 0, emptyList())
assertThat(isCurrentUserTrusted()).isTrue()
}
@@ -119,29 +189,24 @@ class TrustRepositoryTest : SysuiTestCase() {
@Test
fun isCurrentUserTrusted_whenTrustChangesForCurrentUser_emitsNewValue() =
testScope.runTest {
- runCurrent()
- verify(trustManager).registerTrustListener(listenerCaptor.capture())
- val listener = listenerCaptor.value
+ init()
userRepository.setSelectedUserInfo(users[0])
- val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
- listener.onTrustChanged(true, true, users[0].id, 0, emptyList())
+ listener.value.onTrustChanged(true, true, users[0].id, 0, emptyList())
assertThat(isCurrentUserTrusted()).isTrue()
- listener.onTrustChanged(false, true, users[0].id, 0, emptyList())
+ listener.value.onTrustChanged(false, true, users[0].id, 0, emptyList())
assertThat(isCurrentUserTrusted()).isFalse()
}
@Test
fun isCurrentUserTrusted_whenUserChangesWithoutRecentTrustChange_defaultsToFalse() =
testScope.runTest {
- runCurrent()
- verify(trustManager).registerTrustListener(listenerCaptor.capture())
- val listener = listenerCaptor.value
+ init()
+
userRepository.setSelectedUserInfo(users[0])
- listener.onTrustChanged(true, true, users[0].id, 0, emptyList())
+ listener.value.onTrustChanged(true, true, users[0].id, 0, emptyList())
- val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
userRepository.setSelectedUserInfo(users[1])
assertThat(isCurrentUserTrusted()).isFalse()
@@ -150,12 +215,9 @@ class TrustRepositoryTest : SysuiTestCase() {
@Test
fun isCurrentUserTrusted_trustChangesFirstBeforeUserInfoChanges_emitsCorrectValue() =
testScope.runTest {
- runCurrent()
- verify(trustManager).registerTrustListener(listenerCaptor.capture())
- val listener = listenerCaptor.value
- val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
+ init()
- listener.onTrustChanged(true, true, users[0].id, 0, emptyList())
+ listener.value.onTrustChanged(true, true, users[0].id, 0, emptyList())
assertThat(isCurrentUserTrusted()).isFalse()
userRepository.setSelectedUserInfo(users[0])
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/Fakes.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/Fakes.kt
new file mode 100644
index 000000000000..1cdaec0c6581
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/Fakes.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.scene.data.repository
+
+import com.android.systemui.scene.data.model.SceneContainerConfig
+import com.android.systemui.scene.shared.model.SceneKey
+
+fun fakeSceneContainerRepository(
+ containerConfigurations: Set<SceneContainerConfig> =
+ setOf(
+ fakeSceneContainerConfig("container1"),
+ fakeSceneContainerConfig("container2"),
+ )
+): SceneContainerRepository {
+ return SceneContainerRepository(containerConfigurations)
+}
+
+fun fakeSceneKeys(): List<SceneKey> {
+ return listOf(
+ SceneKey.QuickSettings,
+ SceneKey.Shade,
+ SceneKey.LockScreen,
+ SceneKey.Bouncer,
+ SceneKey.Gone,
+ )
+}
+
+fun fakeSceneContainerConfig(
+ name: String,
+ sceneKeys: List<SceneKey> = fakeSceneKeys(),
+): SceneContainerConfig {
+ return SceneContainerConfig(
+ name = name,
+ sceneKeys = sceneKeys,
+ initialSceneKey = SceneKey.LockScreen,
+ )
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
new file mode 100644
index 000000000000..9e264db845e1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
@@ -0,0 +1,139 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.scene.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class SceneContainerRepositoryTest : SysuiTestCase() {
+
+ @Test
+ fun allSceneKeys() {
+ val underTest = fakeSceneContainerRepository()
+ assertThat(underTest.allSceneKeys("container1"))
+ .isEqualTo(
+ listOf(
+ SceneKey.QuickSettings,
+ SceneKey.Shade,
+ SceneKey.LockScreen,
+ SceneKey.Bouncer,
+ SceneKey.Gone,
+ )
+ )
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun allSceneKeys_noSuchContainer_throws() {
+ val underTest = fakeSceneContainerRepository()
+ underTest.allSceneKeys("nonExistingContainer")
+ }
+
+ @Test
+ fun currentScene() = runTest {
+ val underTest = fakeSceneContainerRepository()
+ val currentScene by collectLastValue(underTest.currentScene("container1"))
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
+
+ underTest.setCurrentScene("container1", SceneModel(SceneKey.Shade))
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun currentScene_noSuchContainer_throws() {
+ val underTest = fakeSceneContainerRepository()
+ underTest.currentScene("nonExistingContainer")
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun setCurrentScene_noSuchContainer_throws() {
+ val underTest = fakeSceneContainerRepository()
+ underTest.setCurrentScene("nonExistingContainer", SceneModel(SceneKey.Shade))
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun setCurrentScene_noSuchSceneInContainer_throws() {
+ val underTest =
+ fakeSceneContainerRepository(
+ setOf(
+ fakeSceneContainerConfig("container1"),
+ fakeSceneContainerConfig(
+ "container2",
+ listOf(SceneKey.QuickSettings, SceneKey.LockScreen)
+ ),
+ )
+ )
+ underTest.setCurrentScene("container2", SceneModel(SceneKey.Shade))
+ }
+
+ @Test
+ fun isVisible() = runTest {
+ val underTest = fakeSceneContainerRepository()
+ val isVisible by collectLastValue(underTest.isVisible("container1"))
+ assertThat(isVisible).isTrue()
+
+ underTest.setVisible("container1", false)
+ assertThat(isVisible).isFalse()
+
+ underTest.setVisible("container1", true)
+ assertThat(isVisible).isTrue()
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun isVisible_noSuchContainer_throws() {
+ val underTest = fakeSceneContainerRepository()
+ underTest.isVisible("nonExistingContainer")
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun setVisible_noSuchContainer_throws() {
+ val underTest = fakeSceneContainerRepository()
+ underTest.setVisible("nonExistingContainer", false)
+ }
+
+ @Test
+ fun sceneTransitionProgress() = runTest {
+ val underTest = fakeSceneContainerRepository()
+ val sceneTransitionProgress by
+ collectLastValue(underTest.sceneTransitionProgress("container1"))
+ assertThat(sceneTransitionProgress).isEqualTo(1f)
+
+ underTest.setSceneTransitionProgress("container1", 0.1f)
+ assertThat(sceneTransitionProgress).isEqualTo(0.1f)
+
+ underTest.setSceneTransitionProgress("container1", 0.9f)
+ assertThat(sceneTransitionProgress).isEqualTo(0.9f)
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun sceneTransitionProgress_noSuchContainer_throws() {
+ val underTest = fakeSceneContainerRepository()
+ underTest.sceneTransitionProgress("nonExistingContainer")
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
new file mode 100644
index 000000000000..c5ce09246862
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class, ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.scene.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.scene.data.repository.fakeSceneContainerRepository
+import com.android.systemui.scene.data.repository.fakeSceneKeys
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class SceneInteractorTest : SysuiTestCase() {
+
+ private val underTest =
+ SceneInteractor(
+ repository = fakeSceneContainerRepository(),
+ )
+
+ @Test
+ fun allSceneKeys() {
+ assertThat(underTest.allSceneKeys("container1")).isEqualTo(fakeSceneKeys())
+ }
+
+ @Test
+ fun sceneTransitions() = runTest {
+ val currentScene by collectLastValue(underTest.currentScene("container1"))
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
+
+ underTest.setCurrentScene("container1", SceneModel(SceneKey.Shade))
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
+ }
+
+ @Test
+ fun sceneTransitionProgress() = runTest {
+ val progress by collectLastValue(underTest.sceneTransitionProgress("container1"))
+ assertThat(progress).isEqualTo(1f)
+
+ underTest.setSceneTransitionProgress("container1", 0.55f)
+ assertThat(progress).isEqualTo(0.55f)
+ }
+
+ @Test
+ fun isVisible() = runTest {
+ val isVisible by collectLastValue(underTest.isVisible("container1"))
+ assertThat(isVisible).isTrue()
+
+ underTest.setVisible("container1", false)
+ assertThat(isVisible).isFalse()
+
+ underTest.setVisible("container1", true)
+ assertThat(isVisible).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
new file mode 100644
index 000000000000..ab61ddddaeab
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.scene.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.scene.data.repository.fakeSceneContainerRepository
+import com.android.systemui.scene.data.repository.fakeSceneKeys
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class SceneContainerViewModelTest : SysuiTestCase() {
+ private val interactor =
+ SceneInteractor(
+ repository = fakeSceneContainerRepository(),
+ )
+ private val underTest =
+ SceneContainerViewModel(
+ interactor = interactor,
+ containerName = "container1",
+ )
+
+ @Test
+ fun isVisible() = runTest {
+ val isVisible by collectLastValue(underTest.isVisible)
+ assertThat(isVisible).isTrue()
+
+ interactor.setVisible("container1", false)
+ assertThat(isVisible).isFalse()
+
+ interactor.setVisible("container1", true)
+ assertThat(isVisible).isTrue()
+ }
+
+ @Test
+ fun allSceneKeys() {
+ assertThat(underTest.allSceneKeys).isEqualTo(fakeSceneKeys())
+ }
+
+ @Test
+ fun sceneTransition() = runTest {
+ val currentScene by collectLastValue(underTest.currentScene)
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
+
+ underTest.setCurrentScene(SceneModel(SceneKey.Shade))
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt
index f0dbc60cae1c..1340a47ab6de 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt
@@ -31,10 +31,18 @@ class FakeTrustRepository : TrustRepository {
override val isCurrentUserActiveUnlockAvailable: StateFlow<Boolean> =
_isCurrentUserActiveUnlockAvailable.asStateFlow()
+ private val _isCurrentUserTrustManaged = MutableStateFlow(false)
+ override val isCurrentUserTrustManaged: StateFlow<Boolean>
+ get() = _isCurrentUserTrustManaged
+
fun setCurrentUserTrusted(trust: Boolean) {
_isCurrentUserTrusted.value = trust
}
+ fun setCurrentUserTrustManaged(value: Boolean) {
+ _isCurrentUserTrustManaged.value = value
+ }
+
fun setCurrentUserActiveUnlockAvailable(available: Boolean) {
_isCurrentUserActiveUnlockAvailable.value = available
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index fee20c89c106..9a519fac2d26 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -203,19 +203,27 @@ public class MagnificationController implements WindowMagnificationManager.Callb
private void updateMagnificationUIControls(int displayId, int mode) {
final boolean isActivated = isActivated(displayId, mode);
- final boolean showUIControls;
+ final boolean showModeSwitchButton;
+ final boolean enableSettingsPanel;
synchronized (mLock) {
- showUIControls = isActivated && mMagnificationCapabilities
- == Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
+ showModeSwitchButton = isActivated
+ && mMagnificationCapabilities == ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
+ enableSettingsPanel = isActivated
+ && (mMagnificationCapabilities == ACCESSIBILITY_MAGNIFICATION_MODE_ALL
+ || mMagnificationCapabilities == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
}
- if (showUIControls) {
- // we only need to show magnification button, the settings panel showing should be
- // triggered only on sysui side.
+
+ if (showModeSwitchButton) {
getWindowMagnificationMgr().showMagnificationButton(displayId, mode);
} else {
- getWindowMagnificationMgr().removeMagnificationSettingsPanel(displayId);
getWindowMagnificationMgr().removeMagnificationButton(displayId);
}
+
+ if (!enableSettingsPanel) {
+ // Whether the settings panel needs to be shown is controlled in system UI.
+ // Here, we only guarantee that the settings panel is closed when it is not needed.
+ getWindowMagnificationMgr().removeMagnificationSettingsPanel(displayId);
+ }
}
/** Returns {@code true} if the platform supports window magnification feature. */
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 3a7aa855ea90..7a3b1190cffa 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -813,7 +813,7 @@ public class BackupManagerService extends IBackupManager.Stub {
}
UserBackupManagerService userBackupManagerService =
getServiceForUserIfCallerHasPermission(
- UserHandle.USER_SYSTEM, "hasBackupPassword()");
+ userId, "hasBackupPassword()");
return userBackupManagerService != null && userBackupManagerService.hasBackupPassword();
}
@@ -1515,41 +1515,73 @@ public class BackupManagerService extends IBackupManager.Stub {
@VisibleForTesting
void dumpWithoutCheckingPermission(FileDescriptor fd, PrintWriter pw, String[] args) {
- int userId = binderGetCallingUserId();
- if (!isUserReadyForBackup(userId)) {
- pw.println("Inactive");
+ int argIndex = 0;
+
+ String op = nextArg(args, argIndex);
+ argIndex++;
+
+ if ("--help".equals(op)) {
+ showDumpUsage(pw);
return;
}
-
- if (args != null) {
- for (String arg : args) {
- if ("-h".equals(arg)) {
- pw.println("'dumpsys backup' optional arguments:");
- pw.println(" -h : this help text");
- pw.println(" a[gents] : dump information about defined backup agents");
- pw.println(" transportclients : dump information about transport clients");
- pw.println(" transportstats : dump transport statts");
- pw.println(" users : dump the list of users for which backup service "
- + "is running");
- return;
- } else if ("users".equals(arg.toLowerCase())) {
- pw.print(DUMP_RUNNING_USERS_MESSAGE);
- for (int i = 0; i < mUserServices.size(); i++) {
- pw.print(" " + mUserServices.keyAt(i));
- }
- pw.println();
- return;
+ if ("users".equals(op)) {
+ pw.print(DUMP_RUNNING_USERS_MESSAGE);
+ for (int i = 0; i < mUserServices.size(); i++) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(mUserServices.keyAt(i),
+ "dump()");
+ if (userBackupManagerService != null) {
+ pw.print(" " + userBackupManagerService.getUserId());
}
}
+ pw.println();
+ return;
}
-
- for (int i = 0; i < mUserServices.size(); i++) {
+ if ("--user".equals(op)) {
+ String userArg = nextArg(args, argIndex);
+ argIndex++;
+ if (userArg == null) {
+ showDumpUsage(pw);
+ return;
+ }
+ int userId = UserHandle.parseUserArg(userArg);
UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(mUserServices.keyAt(i), "dump()");
+ getServiceForUserIfCallerHasPermission(userId, "dump()");
if (userBackupManagerService != null) {
userBackupManagerService.dump(fd, pw, args);
}
+ return;
}
+ if (op == null || "agents".startsWith(op) || "transportclients".equals(op)
+ || "transportstats".equals(op)) {
+ for (int i = 0; i < mUserServices.size(); i++) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(mUserServices.keyAt(i), "dump()");
+ if (userBackupManagerService != null) {
+ userBackupManagerService.dump(fd, pw, args);
+ }
+ }
+ return;
+ }
+
+ showDumpUsage(pw);
+ }
+
+ private String nextArg(String[] args, int argIndex) {
+ if (argIndex >= args.length) {
+ return null;
+ }
+ return args[argIndex];
+ }
+
+ private static void showDumpUsage(PrintWriter pw) {
+ pw.println("'dumpsys backup' optional arguments:");
+ pw.println(" --help : this help text");
+ pw.println(" a[gents] : dump information about defined backup agents");
+ pw.println(" transportclients : dump information about transport clients");
+ pw.println(" transportstats : dump transport stats");
+ pw.println(" users : dump the list of users for which backup service is running");
+ pw.println(" --user <userId> : dump information for user userId");
}
/**
@@ -1654,7 +1686,7 @@ public class BackupManagerService extends IBackupManager.Stub {
* @param message A message to include in the exception if it is thrown.
*/
void enforceCallingPermissionOnUserId(@UserIdInt int userId, String message) {
- if (Binder.getCallingUserHandle().getIdentifier() != userId) {
+ if (binderGetCallingUserId() != userId) {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
}
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 7261709d7b8d..b65681104527 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -2797,11 +2797,6 @@ public class UserBackupManagerService {
boolean includeSystem, boolean compress, boolean doKeyValue, String[] pkgList) {
mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "adbBackup");
- final int callingUserHandle = UserHandle.getCallingUserId();
- if (callingUserHandle != UserHandle.USER_SYSTEM) {
- throw new IllegalStateException("Backup supported only for the device owner");
- }
-
// Validate
if (!doAllApps) {
if (!includeShared) {
@@ -2972,11 +2967,6 @@ public class UserBackupManagerService {
public void adbRestore(ParcelFileDescriptor fd) {
mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "adbRestore");
- final int callingUserHandle = UserHandle.getCallingUserId();
- if (callingUserHandle != UserHandle.USER_SYSTEM) {
- throw new IllegalStateException("Restore supported only for the device owner");
- }
-
final long oldId = Binder.clearCallingIdentity();
try {
@@ -3085,7 +3075,7 @@ public class UserBackupManagerService {
"com.android.backupconfirm.BackupRestoreConfirmation");
confIntent.putExtra(FullBackup.CONF_TOKEN_INTENT_EXTRA, token);
confIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
- mContext.startActivityAsUser(confIntent, UserHandle.SYSTEM);
+ mContext.startActivityAsUser(confIntent, UserHandle.of(mUserId));
} catch (ActivityNotFoundException e) {
return false;
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index ca50af8075c6..96766a20c803 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2326,7 +2326,7 @@ public final class ActiveServices {
&& (r.getConnections().size() > 0)
&& (r.mDebugWhileInUseReasonInBindService
!= r.mDebugWhileInUseReasonInStartForeground)) {
- Slog.wtf(TAG, "FGS while-in-use changed (b/276963716): old="
+ logWhileInUseChangeWtf("FGS while-in-use changed (b/276963716): old="
+ reasonCodeToString(r.mDebugWhileInUseReasonInBindService)
+ " new="
+ reasonCodeToString(r.mDebugWhileInUseReasonInStartForeground)
@@ -2589,6 +2589,13 @@ public final class ActiveServices {
}
}
+ /**
+ * It just does a wtf, but extracted to a method, so we can do a signature search on pitot.
+ */
+ private void logWhileInUseChangeWtf(String message) {
+ Slog.wtf(TAG, message);
+ }
+
private boolean withinFgsDeferRateLimit(ServiceRecord sr, final long now) {
// If we're still within the service's deferral period, then by definition
// deferral is not rate limited.
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 59aab4f56404..2803b4b66615 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -200,6 +200,7 @@ class BroadcastProcessQueue {
*/
private boolean mLastDeferredStates;
+ private boolean mUidForeground;
private boolean mUidCached;
private boolean mProcessInstrumented;
private boolean mProcessPersistent;
@@ -409,7 +410,8 @@ class BroadcastProcessQueue {
* {@link BroadcastQueueModernImpl#updateRunnableList}
*/
@CheckResult
- public boolean setProcessAndUidCached(@Nullable ProcessRecord app, boolean uidCached) {
+ public boolean setProcessAndUidState(@Nullable ProcessRecord app, boolean uidForeground,
+ boolean uidCached) {
this.app = app;
// Since we may have just changed our PID, invalidate cached strings
@@ -419,10 +421,12 @@ class BroadcastProcessQueue {
boolean didSomething = false;
if (app != null) {
didSomething |= setUidCached(uidCached);
+ didSomething |= setUidForeground(uidForeground);
didSomething |= setProcessInstrumented(app.getActiveInstrumentation() != null);
didSomething |= setProcessPersistent(app.isPersistent());
} else {
didSomething |= setUidCached(uidCached);
+ didSomething |= setUidForeground(false);
didSomething |= setProcessInstrumented(false);
didSomething |= setProcessPersistent(false);
}
@@ -430,6 +434,22 @@ class BroadcastProcessQueue {
}
/**
+ * Update if the UID this process is belongs to is in "foreground" state, which signals
+ * broadcast dispatch should prioritize delivering broadcasts to this process to minimize any
+ * delays in UI updates.
+ */
+ @CheckResult
+ private boolean setUidForeground(boolean uidForeground) {
+ if (mUidForeground != uidForeground) {
+ mUidForeground = uidForeground;
+ invalidateRunnableAt();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
* Update if this process is in the "cached" state, typically signaling that
* broadcast dispatch should be paused or delayed.
*/
@@ -994,7 +1014,7 @@ class BroadcastProcessQueue {
static final int REASON_CONTAINS_RESULT_TO = 15;
static final int REASON_CONTAINS_INSTRUMENTED = 16;
static final int REASON_CONTAINS_MANIFEST = 17;
- static final int REASON_FOREGROUND_ACTIVITIES = 18;
+ static final int REASON_FOREGROUND = 18;
@IntDef(flag = false, prefix = { "REASON_" }, value = {
REASON_EMPTY,
@@ -1014,7 +1034,7 @@ class BroadcastProcessQueue {
REASON_CONTAINS_RESULT_TO,
REASON_CONTAINS_INSTRUMENTED,
REASON_CONTAINS_MANIFEST,
- REASON_FOREGROUND_ACTIVITIES,
+ REASON_FOREGROUND,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Reason {}
@@ -1038,7 +1058,7 @@ class BroadcastProcessQueue {
case REASON_CONTAINS_RESULT_TO: return "CONTAINS_RESULT_TO";
case REASON_CONTAINS_INSTRUMENTED: return "CONTAINS_INSTRUMENTED";
case REASON_CONTAINS_MANIFEST: return "CONTAINS_MANIFEST";
- case REASON_FOREGROUND_ACTIVITIES: return "FOREGROUND_ACTIVITIES";
+ case REASON_FOREGROUND: return "FOREGROUND";
default: return Integer.toString(reason);
}
}
@@ -1077,11 +1097,9 @@ class BroadcastProcessQueue {
} else if (mProcessInstrumented) {
mRunnableAt = runnableAt + constants.DELAY_URGENT_MILLIS;
mRunnableAtReason = REASON_INSTRUMENTED;
- } else if (app != null && app.hasForegroundActivities()) {
- // TODO: Listen for uid state changes to check when an uid goes in and out of
- // the TOP state.
+ } else if (mUidForeground) {
mRunnableAt = runnableAt + constants.DELAY_URGENT_MILLIS;
- mRunnableAtReason = REASON_FOREGROUND_ACTIVITIES;
+ mRunnableAtReason = REASON_FOREGROUND;
} else if (mCountOrdered > 0) {
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_CONTAINS_ORDERED;
@@ -1351,7 +1369,11 @@ class BroadcastProcessQueue {
@NeverCompile
private void dumpProcessState(@NonNull IndentingPrintWriter pw) {
final StringBuilder sb = new StringBuilder();
+ if (mUidForeground) {
+ sb.append("FG");
+ }
if (mUidCached) {
+ if (sb.length() > 0) sb.append("|");
sb.append("CACHED");
}
if (mProcessInstrumented) {
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 059239df3a7f..d9b315794ea3 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -212,6 +212,17 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
new AtomicReference<>();
/**
+ * Map from UID to its last known "foreground" state. A UID is considered to be in
+ * "foreground" state when it's procState is {@link ActivityManager#PROCESS_STATE_TOP}.
+ * <p>
+ * We manually maintain this data structure since the lifecycle of
+ * {@link ProcessRecord} and {@link BroadcastProcessQueue} can be
+ * mismatched.
+ */
+ @GuardedBy("mService")
+ private final SparseBooleanArray mUidForeground = new SparseBooleanArray();
+
+ /**
* Map from UID to its last known "cached" state.
* <p>
* We manually maintain this data structure since the lifecycle of
@@ -1284,11 +1295,24 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
return UserHandle.getUserId(q.uid) == userId;
};
broadcastPredicate = BROADCAST_PREDICATE_ANY;
+
+ cleanupUserStateLocked(mUidCached, userId);
+ cleanupUserStateLocked(mUidForeground, userId);
}
return forEachMatchingBroadcast(queuePredicate, broadcastPredicate,
mBroadcastConsumerSkip, true);
}
+ @GuardedBy("mService")
+ private void cleanupUserStateLocked(@NonNull SparseBooleanArray uidState, int userId) {
+ for (int i = uidState.size() - 1; i >= 0; --i) {
+ final int uid = uidState.keyAt(i);
+ if (UserHandle.getUserId(uid) == userId) {
+ uidState.removeAt(i);
+ }
+ }
+ }
+
private static final Predicate<BroadcastProcessQueue> QUEUE_PREDICATE_ANY =
(q) -> true;
private static final BroadcastPredicate BROADCAST_PREDICATE_ANY =
@@ -1404,6 +1428,19 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
mService.registerUidObserver(new UidObserver() {
@Override
+ public void onUidStateChanged(int uid, int procState, long procStateSeq,
+ int capability) {
+ synchronized (mService) {
+ if (procState == ActivityManager.PROCESS_STATE_TOP) {
+ mUidForeground.put(uid, true);
+ } else {
+ mUidForeground.delete(uid);
+ }
+ refreshProcessQueuesLocked(uid);
+ }
+ }
+
+ @Override
public void onUidCachedChanged(int uid, boolean cached) {
synchronized (mService) {
if (cached) {
@@ -1411,18 +1448,11 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
} else {
mUidCached.delete(uid);
}
-
- BroadcastProcessQueue leaf = mProcessQueues.get(uid);
- while (leaf != null) {
- // Update internal state by refreshing values previously
- // read from any known running process
- setQueueProcess(leaf, leaf.app);
- leaf = leaf.processNameNext;
- }
- enqueueUpdateRunningList();
+ refreshProcessQueuesLocked(uid);
}
}
- }, ActivityManager.UID_OBSERVER_CACHED, 0, "android");
+ }, ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_CACHED,
+ ActivityManager.PROCESS_STATE_TOP, "android");
// Kick off periodic health checks
mLocalHandler.sendEmptyMessage(MSG_CHECK_HEALTH);
@@ -1611,8 +1641,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// warm via this operation, we're going to immediately promote it to
// be running, and any side effect of this operation will then apply
// after it's finished and is returned to the runnable list.
- queue.setProcessAndUidCached(
+ queue.setProcessAndUidState(
mService.getProcessRecordLocked(queue.processName, queue.uid),
+ mUidForeground.get(queue.uid, false),
mUidCached.get(queue.uid, false));
}
}
@@ -1624,12 +1655,29 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
*/
private void setQueueProcess(@NonNull BroadcastProcessQueue queue,
@Nullable ProcessRecord app) {
- if (queue.setProcessAndUidCached(app, mUidCached.get(queue.uid, false))) {
+ if (queue.setProcessAndUidState(app, mUidForeground.get(queue.uid, false),
+ mUidCached.get(queue.uid, false))) {
updateRunnableList(queue);
}
}
/**
+ * Refresh the process queues with the latest process state so that runnableAt
+ * can be updated.
+ */
+ @GuardedBy("mService")
+ private void refreshProcessQueuesLocked(int uid) {
+ BroadcastProcessQueue leaf = mProcessQueues.get(uid);
+ while (leaf != null) {
+ // Update internal state by refreshing values previously
+ // read from any known running process
+ setQueueProcess(leaf, leaf.app);
+ leaf = leaf.processNameNext;
+ }
+ enqueueUpdateRunningList();
+ }
+
+ /**
* Inform other parts of OS that the given broadcast queue has started
* running, typically for internal bookkeeping.
*/
@@ -1950,7 +1998,13 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
ipw.println("Cached UIDs:");
ipw.increaseIndent();
- ipw.println(mUidCached.toString());
+ ipw.println(mUidCached);
+ ipw.decreaseIndent();
+ ipw.println();
+
+ ipw.println("Foreground UIDs:");
+ ipw.increaseIndent();
+ ipw.println(mUidForeground);
ipw.decreaseIndent();
ipw.println();
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 4ec813ecd81c..335d6768c37b 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1072,11 +1072,6 @@ class ProcessRecord implements WindowProcessListener {
return mState.isCached();
}
- @GuardedBy(anyOf = {"mService", "mProcLock"})
- public boolean hasForegroundActivities() {
- return mState.hasForegroundActivities();
- }
-
boolean hasActivities() {
return mWindowProcessController.hasActivities();
}
diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java
index a9a77bf28ebe..c6b15b6dcd7a 100644
--- a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java
+++ b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java
@@ -60,6 +60,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
/**
* System service for managing {@link AmbientContextEvent}s.
@@ -595,7 +596,7 @@ public class AmbientContextManagerService extends
synchronized (mLock) {
for (ClientRequest cr : mExistingClientRequests) {
- if (cr.getPackageName().equals(callingPackage)) {
+ if ((cr != null) && cr.getPackageName().equals(callingPackage)) {
AmbientContextManagerPerUserService service =
getAmbientContextManagerPerUserServiceForEventTypes(
UserHandle.getCallingUserId(),
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index 19d6fa00a270..f012d917b05e 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -27,6 +27,7 @@ import static android.view.WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVI
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
import static android.view.WindowManager.LayoutParams.SoftInputModeFlags;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static com.android.internal.inputmethod.InputMethodDebug.softInputModeToString;
import static com.android.internal.inputmethod.SoftInputShowHideReason.REMOVE_IME_SCREENSHOT_FROM_IMMS;
@@ -195,19 +196,25 @@ public final class ImeVisibilityStateComputer {
mWindowManagerInternal.setInputMethodTargetChangeListener(new ImeTargetChangeListener() {
@Override
public void onImeTargetOverlayVisibilityChanged(IBinder overlayWindowToken,
- boolean visible, boolean removed) {
- mCurVisibleImeLayeringOverlay = (visible && !removed) ? overlayWindowToken : null;
+ @WindowManager.LayoutParams.WindowType int windowType, boolean visible,
+ boolean removed) {
+ mCurVisibleImeLayeringOverlay =
+ // Ignoring the starting window since it's ok to cover the IME target
+ // window in temporary without affecting the IME visibility.
+ (visible && !removed && windowType != TYPE_APPLICATION_STARTING)
+ ? overlayWindowToken : null;
}
@Override
public void onImeInputTargetVisibilityChanged(IBinder imeInputTarget,
boolean visibleRequested, boolean removed) {
- mCurVisibleImeInputTarget = (visibleRequested && !removed) ? imeInputTarget : null;
- if (mCurVisibleImeInputTarget == null && mCurVisibleImeLayeringOverlay != null) {
+ if (mCurVisibleImeInputTarget == imeInputTarget && (!visibleRequested || removed)
+ && mCurVisibleImeLayeringOverlay != null) {
mService.onApplyImeVisibilityFromComputer(imeInputTarget,
new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT,
SoftInputShowHideReason.HIDE_WHEN_INPUT_TARGET_INVISIBLE));
}
+ mCurVisibleImeInputTarget = (visibleRequested && !removed) ? imeInputTarget : null;
}
});
}
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 661715c0eb12..93d6676dd929 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -4745,17 +4745,19 @@ public class BatteryStatsImpl extends BatteryStats {
requestWakelockCpuUpdate();
}
- getUidStatsLocked(mappedUid, elapsedRealtimeMs, uptimeMs)
- .noteStartWakeLocked(pid, name, type, elapsedRealtimeMs);
+ Uid uidStats = getUidStatsLocked(mappedUid, elapsedRealtimeMs, uptimeMs);
+ uidStats.noteStartWakeLocked(pid, name, type, elapsedRealtimeMs);
+
+ int procState = uidStats.mProcessState;
if (wc != null) {
FrameworkStatsLog.write(FrameworkStatsLog.WAKELOCK_STATE_CHANGED, wc.getUids(),
wc.getTags(), getPowerManagerWakeLockLevel(type), name,
- FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__ACQUIRE);
+ FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__ACQUIRE, procState);
} else {
FrameworkStatsLog.write_non_chained(FrameworkStatsLog.WAKELOCK_STATE_CHANGED,
mapIsolatedUid(uid), null, getPowerManagerWakeLockLevel(type), name,
- FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__ACQUIRE);
+ FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__ACQUIRE, procState);
}
}
}
@@ -4796,16 +4798,18 @@ public class BatteryStatsImpl extends BatteryStats {
requestWakelockCpuUpdate();
}
- getUidStatsLocked(mappedUid, elapsedRealtimeMs, uptimeMs)
- .noteStopWakeLocked(pid, name, type, elapsedRealtimeMs);
+ Uid uidStats = getUidStatsLocked(mappedUid, elapsedRealtimeMs, uptimeMs);
+ uidStats.noteStopWakeLocked(pid, name, type, elapsedRealtimeMs);
+
+ int procState = uidStats.mProcessState;
if (wc != null) {
FrameworkStatsLog.write(FrameworkStatsLog.WAKELOCK_STATE_CHANGED, wc.getUids(),
wc.getTags(), getPowerManagerWakeLockLevel(type), name,
- FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__RELEASE);
+ FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__RELEASE, procState);
} else {
FrameworkStatsLog.write_non_chained(FrameworkStatsLog.WAKELOCK_STATE_CHANGED,
mapIsolatedUid(uid), null, getPowerManagerWakeLockLevel(type), name,
- FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__RELEASE);
+ FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__RELEASE, procState);
}
if (mappedUid != uid) {
diff --git a/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java b/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java
index eb6d28e76ff8..73ab7822ac39 100644
--- a/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java
+++ b/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java
@@ -403,10 +403,12 @@ public class CpuWakeupStats {
* This class stores recent unattributed activity history per subsystem.
* The activity is stored as a mapping of subsystem to timestamp to uid to procstate.
*/
- private static final class WakingActivityHistory {
+ @VisibleForTesting
+ static final class WakingActivityHistory {
private static final long WAKING_ACTIVITY_RETENTION_MS = TimeUnit.MINUTES.toMillis(10);
- private SparseArray<TimeSparseArray<SparseIntArray>> mWakingActivity =
+ @VisibleForTesting
+ final SparseArray<TimeSparseArray<SparseIntArray>> mWakingActivity =
new SparseArray<>();
void recordActivity(int subsystem, long elapsedRealtime, SparseIntArray uidProcStates) {
@@ -430,7 +432,6 @@ public class CpuWakeupStats {
uidsToBlame.put(uid, uidProcStates.valueAt(i));
}
}
- wakingActivity.put(elapsedRealtime, uidsToBlame);
}
// Limit activity history per subsystem to the last WAKING_ACTIVITY_RETENTION_MS.
// Note that the last activity is always present, even if it occurred before
diff --git a/services/core/java/com/android/server/wm/ImeTargetChangeListener.java b/services/core/java/com/android/server/wm/ImeTargetChangeListener.java
index 8bc445bc97bb..88b76aaa6992 100644
--- a/services/core/java/com/android/server/wm/ImeTargetChangeListener.java
+++ b/services/core/java/com/android/server/wm/ImeTargetChangeListener.java
@@ -18,6 +18,7 @@ package com.android.server.wm;
import android.annotation.NonNull;
import android.os.IBinder;
+import android.view.WindowManager;
/**
* Callback the IME targeting window visibility change state for
@@ -32,11 +33,13 @@ public interface ImeTargetChangeListener {
* has changed its window visibility.
*
* @param overlayWindowToken the window token of the overlay window.
+ * @param windowType the window type of the overlay window.
* @param visible the visibility of the overlay window, {@code true} means visible
* and {@code false} otherwise.
* @param removed Whether the IME target overlay window has being removed.
*/
default void onImeTargetOverlayVisibilityChanged(@NonNull IBinder overlayWindowToken,
+ @WindowManager.LayoutParams.WindowType int windowType,
boolean visible, boolean removed) {
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 1a2b57cc7d61..25b7df4eda08 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -283,6 +283,7 @@ import android.view.SurfaceControlViewHost;
import android.view.SurfaceSession;
import android.view.TaskTransitionSpec;
import android.view.View;
+import android.view.ViewDebug;
import android.view.WindowContentFrameStats;
import android.view.WindowInsets;
import android.view.WindowInsets.Type.InsetsType;
@@ -1811,7 +1812,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (imMayMove) {
displayContent.computeImeTarget(true /* updateImeTarget */);
if (win.isImeOverlayLayeringTarget()) {
- dispatchImeTargetOverlayVisibilityChanged(client.asBinder(),
+ dispatchImeTargetOverlayVisibilityChanged(client.asBinder(), win.mAttrs.type,
win.isVisibleRequestedOrAdding(), false /* removed */);
}
}
@@ -2521,7 +2522,7 @@ public class WindowManagerService extends IWindowManager.Stub
final boolean winVisibleChanged = win.isVisible() != wasVisible;
if (win.isImeOverlayLayeringTarget() && winVisibleChanged) {
- dispatchImeTargetOverlayVisibilityChanged(client.asBinder(),
+ dispatchImeTargetOverlayVisibilityChanged(client.asBinder(), win.mAttrs.type,
win.isVisible(), false /* removed */);
}
// Notify listeners about IME input target window visibility change.
@@ -3355,15 +3356,17 @@ public class WindowManagerService extends IWindowManager.Stub
});
}
- void dispatchImeTargetOverlayVisibilityChanged(@NonNull IBinder token, boolean visible,
+ void dispatchImeTargetOverlayVisibilityChanged(@NonNull IBinder token,
+ @WindowManager.LayoutParams.WindowType int windowType, boolean visible,
boolean removed) {
if (mImeTargetChangeListener != null) {
if (DEBUG_INPUT_METHOD) {
Slog.d(TAG, "onImeTargetOverlayVisibilityChanged, win=" + mWindowMap.get(token)
- + "visible=" + visible + ", removed=" + removed);
+ + ", type=" + ViewDebug.intToString(WindowManager.LayoutParams.class,
+ "type", windowType) + "visible=" + visible + ", removed=" + removed);
}
mH.post(() -> mImeTargetChangeListener.onImeTargetOverlayVisibilityChanged(token,
- visible, removed));
+ windowType, visible, removed));
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index bab7a78a7286..ba942821f244 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2349,7 +2349,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
super.removeImmediately();
if (isImeOverlayLayeringTarget()) {
- mWmService.dispatchImeTargetOverlayVisibilityChanged(mClient.asBinder(),
+ mWmService.dispatchImeTargetOverlayVisibilityChanged(mClient.asBinder(), mAttrs.type,
false /* visible */, true /* removed */);
}
final DisplayContent dc = getDisplayContent();
diff --git a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
index 94ee0a871448..91dcd50f176a 100644
--- a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
@@ -33,6 +33,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
import static org.testng.Assert.expectThrows;
@@ -118,6 +119,10 @@ public class BackupManagerServiceRoboTest {
mShadowUserManager.addUser(mUserOneId, "mUserOneId", 0);
mShadowUserManager.addUser(mUserTwoId, "mUserTwoId", 0);
+ when(mUserSystemService.getUserId()).thenReturn(UserHandle.USER_SYSTEM);
+ when(mUserOneService.getUserId()).thenReturn(mUserOneId);
+ when(mUserTwoService.getUserId()).thenReturn(mUserTwoId);
+
mShadowContext.grantPermissions(BACKUP);
mShadowContext.grantPermissions(INTERACT_ACROSS_USERS_FULL);
@@ -1469,9 +1474,9 @@ public class BackupManagerServiceRoboTest {
File testFile = createTestFile();
FileDescriptor fileDescriptor = new FileDescriptor();
PrintWriter printWriter = new PrintWriter(testFile);
- String[] args = {"1", "2"};
ShadowBinder.setCallingUserHandle(UserHandle.of(UserHandle.USER_SYSTEM));
+ String[] args = {"--user", "0"};
backupManagerService.dump(fileDescriptor, printWriter, args);
verify(mUserSystemService).dump(fileDescriptor, printWriter, args);
@@ -1485,8 +1490,8 @@ public class BackupManagerServiceRoboTest {
File testFile = createTestFile();
FileDescriptor fileDescriptor = new FileDescriptor();
PrintWriter printWriter = new PrintWriter(testFile);
- String[] args = {"1", "2"};
+ String[] args = {"--user", "10"};
backupManagerService.dump(fileDescriptor, printWriter, args);
verify(mUserOneService, never()).dump(fileDescriptor, printWriter, args);
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
index 3871e1dfd5b0..a38c1626aea1 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
@@ -23,6 +23,7 @@ import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE;
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_WHEN_INPUT_TARGET_INVISIBLE;
@@ -241,8 +242,12 @@ public class ImeVisibilityStateComputerTest extends InputMethodManagerServiceTes
final IBinder testImeTargetOverlay = new Binder();
final IBinder testImeInputTarget = new Binder();
+ // Simulate a test IME input target was visible.
+ mListener.onImeInputTargetVisibilityChanged(testImeInputTarget, true, false);
+
// Simulate a test IME layering target overlay fully occluded the IME input target.
- mListener.onImeTargetOverlayVisibilityChanged(testImeTargetOverlay, true, false);
+ mListener.onImeTargetOverlayVisibilityChanged(testImeTargetOverlay,
+ TYPE_APPLICATION_OVERLAY, true, false);
mListener.onImeInputTargetVisibilityChanged(testImeInputTarget, false, false);
final ArgumentCaptor<IBinder> targetCaptor = ArgumentCaptor.forClass(IBinder.class);
final ArgumentCaptor<ImeVisibilityResult> resultCaptor = ArgumentCaptor.forClass(
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 5d3b91368dcb..581fe4acf219 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -393,9 +393,9 @@ public final class BroadcastQueueModernImplTest {
List.of(makeMockRegisteredReceiver()), false);
enqueueOrReplaceBroadcast(queue, airplaneRecord, 0);
- queue.setProcessAndUidCached(null, false);
+ queue.setProcessAndUidState(null, false, false);
final long notCachedRunnableAt = queue.getRunnableAt();
- queue.setProcessAndUidCached(null, true);
+ queue.setProcessAndUidState(null, false, true);
final long cachedRunnableAt = queue.getRunnableAt();
assertThat(cachedRunnableAt).isGreaterThan(notCachedRunnableAt);
assertFalse(queue.isRunnable());
@@ -420,9 +420,9 @@ public final class BroadcastQueueModernImplTest {
List.of(makeMockRegisteredReceiver()), false);
enqueueOrReplaceBroadcast(queue, airplaneRecord, 0);
- queue.setProcessAndUidCached(null, false);
+ queue.setProcessAndUidState(null, false, false);
final long notCachedRunnableAt = queue.getRunnableAt();
- queue.setProcessAndUidCached(null, true);
+ queue.setProcessAndUidState(null, false, true);
final long cachedRunnableAt = queue.getRunnableAt();
assertThat(cachedRunnableAt).isGreaterThan(notCachedRunnableAt);
assertTrue(queue.isRunnable());
@@ -452,13 +452,13 @@ public final class BroadcastQueueModernImplTest {
// verify that:
// (a) the queue is immediately runnable by existence of a fg-priority broadcast
// (b) the next one up is the fg-priority broadcast despite its later enqueue time
- queue.setProcessAndUidCached(null, false);
+ queue.setProcessAndUidState(null, false, false);
assertTrue(queue.isRunnable());
assertThat(queue.getRunnableAt()).isAtMost(airplaneRecord.enqueueClockTime);
assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked());
assertEquals(queue.peekNextBroadcastRecord(), airplaneRecord);
- queue.setProcessAndUidCached(null, true);
+ queue.setProcessAndUidState(null, false, true);
assertTrue(queue.isRunnable());
assertThat(queue.getRunnableAt()).isAtMost(airplaneRecord.enqueueClockTime);
assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked());
@@ -515,6 +515,28 @@ public final class BroadcastQueueModernImplTest {
assertEquals(BroadcastProcessQueue.REASON_MAX_PENDING, queue.getRunnableAtReason());
}
+ @Test
+ public void testRunnableAt_uidForeground() {
+ final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants, PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+
+ final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
+ final BroadcastRecord timeTickRecord = makeBroadcastRecord(timeTick,
+ List.of(makeMockRegisteredReceiver()));
+ enqueueOrReplaceBroadcast(queue, timeTickRecord, 0);
+
+ assertThat(queue.getRunnableAt()).isGreaterThan(timeTickRecord.enqueueTime);
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, queue.getRunnableAtReason());
+
+ queue.setProcessAndUidState(mProcess, true, false);
+ assertThat(queue.getRunnableAt()).isLessThan(timeTickRecord.enqueueTime);
+ assertEquals(BroadcastProcessQueue.REASON_FOREGROUND, queue.getRunnableAtReason());
+
+ queue.setProcessAndUidState(mProcess, false, false);
+ assertThat(queue.getRunnableAt()).isGreaterThan(timeTickRecord.enqueueTime);
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, queue.getRunnableAtReason());
+ }
+
/**
* Verify that a cached process that would normally be delayed becomes
* immediately runnable when the given broadcast is enqueued.
@@ -522,7 +544,7 @@ public final class BroadcastQueueModernImplTest {
private void doRunnableAt_Cached(BroadcastRecord testRecord, int testRunnableAtReason) {
final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
- queue.setProcessAndUidCached(null, true);
+ queue.setProcessAndUidState(null, false, true);
final BroadcastRecord lazyRecord = makeBroadcastRecord(
new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED),
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 3a8d2c92eaff..ad5f0d7233ca 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -25,12 +25,15 @@ import static com.android.server.am.BroadcastProcessQueue.reasonToString;
import static com.android.server.am.BroadcastRecord.deliveryStateToString;
import static com.android.server.am.BroadcastRecord.isReceiverEquals;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -2132,4 +2135,75 @@ public class BroadcastQueueTest {
waitForIdle();
verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, airplane);
}
+
+ @Test
+ public void testBroadcastDelivery_uidForeground() throws Exception {
+ // Legacy stack doesn't support prioritization to foreground app.
+ Assume.assumeTrue(mImpl == Impl.MODERN);
+
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+ final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
+
+ mUidObserver.onUidStateChanged(receiverGreenApp.info.uid,
+ ActivityManager.PROCESS_STATE_TOP, 0, ActivityManager.PROCESS_CAPABILITY_NONE);
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
+
+ final BroadcastFilter receiverBlue = makeRegisteredReceiver(receiverBlueApp);
+ final BroadcastFilter receiverGreen = makeRegisteredReceiver(receiverGreenApp);
+ final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, callerApp,
+ List.of(receiverBlue));
+ final BroadcastRecord timeTickRecord = makeBroadcastRecord(timeTick, callerApp,
+ List.of(receiverBlue, receiverGreen));
+
+ enqueueBroadcast(airplaneRecord);
+ enqueueBroadcast(timeTickRecord);
+
+ waitForIdle();
+ // Verify that broadcasts to receiverGreenApp gets scheduled first.
+ assertThat(getReceiverScheduledTime(timeTickRecord, receiverGreen))
+ .isLessThan(getReceiverScheduledTime(airplaneRecord, receiverBlue));
+ assertThat(getReceiverScheduledTime(timeTickRecord, receiverGreen))
+ .isLessThan(getReceiverScheduledTime(timeTickRecord, receiverBlue));
+ }
+
+ @Test
+ public void testPrioritizedBroadcastDelivery_uidForeground() throws Exception {
+ // Legacy stack doesn't support prioritization to foreground app.
+ Assume.assumeTrue(mImpl == Impl.MODERN);
+
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+ final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
+
+ mUidObserver.onUidStateChanged(receiverGreenApp.info.uid,
+ ActivityManager.PROCESS_STATE_TOP, 0, ActivityManager.PROCESS_CAPABILITY_NONE);
+
+ final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
+
+ final BroadcastFilter receiverBlue = makeRegisteredReceiver(receiverBlueApp, 10);
+ final BroadcastFilter receiverGreen = makeRegisteredReceiver(receiverGreenApp, 5);
+ final BroadcastRecord prioritizedRecord = makeBroadcastRecord(timeTick, callerApp,
+ List.of(receiverBlue, receiverGreen));
+
+ enqueueBroadcast(prioritizedRecord);
+
+ waitForIdle();
+ // Verify that uid foreground-ness does not impact that delivery of prioritized broadcast.
+ // That is, broadcast to receiverBlueApp gets scheduled before the one to receiverGreenApp.
+ assertThat(getReceiverScheduledTime(prioritizedRecord, receiverGreen))
+ .isGreaterThan(getReceiverScheduledTime(prioritizedRecord, receiverBlue));
+ }
+
+ private long getReceiverScheduledTime(@NonNull BroadcastRecord r, @NonNull Object receiver) {
+ for (int i = 0; i < r.receivers.size(); ++i) {
+ if (isReceiverEquals(receiver, r.receivers.get(i))) {
+ return r.scheduledTime[i];
+ }
+ }
+ fail(receiver + "not found in " + r);
+ return -1;
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/BackupManagerServiceTest.java
index b203cf640097..f99e156ed139 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/BackupManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/BackupManagerServiceTest.java
@@ -63,6 +63,7 @@ import com.android.server.SystemService;
import com.android.server.backup.utils.RandomAccessFileUtils;
import org.junit.After;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -615,6 +616,53 @@ public class BackupManagerServiceTest {
verify(mNonSystemUserBackupManagerService, never()).dump(any(), any(), any());
}
+ @Test
+ public void testDumpForOneUser_callerDoesNotHaveInteractAcrossUsersFullPermission_ignored() {
+ createBackupManagerServiceAndUnlockSystemUser();
+ mService.setBackupServiceActive(NON_SYSTEM_USER, true);
+ simulateUserUnlocked(NON_SYSTEM_USER);
+
+ doThrow(new SecurityException())
+ .when(mContextMock)
+ .enforceCallingOrSelfPermission(
+ eq(Manifest.permission.INTERACT_ACROSS_USERS_FULL), anyString());
+
+ String[] args = new String[]{"--user", Integer.toString(NON_SYSTEM_USER)};
+ Assert.assertThrows(SecurityException.class,
+ () -> mService.dumpWithoutCheckingPermission(mFileDescriptorStub, mPrintWriterMock,
+ args));
+
+ verify(mNonSystemUserBackupManagerService, never()).dump(any(), any(), any());
+ }
+
+ @Test
+ public void
+ testDumpForOneUser_callerHasInteractAcrossUsersFullPermission_dumpsOnlySpecifiedUser() {
+ createBackupManagerServiceAndUnlockSystemUser();
+ mService.setBackupServiceActive(NON_SYSTEM_USER, true);
+ simulateUserUnlocked(NON_SYSTEM_USER);
+
+ String[] args = new String[]{"--user", Integer.toString(UserHandle.USER_SYSTEM)};
+ mService.dumpWithoutCheckingPermission(mFileDescriptorStub, mPrintWriterMock, args);
+
+ verify(mSystemUserBackupManagerService).dump(any(), any(), any());
+ }
+
+ @Test
+ public void testDumpForAllUsers_callerHasInteractAcrossUsersFullPermission_dumpsAllUsers() {
+ createBackupManagerServiceAndUnlockSystemUser();
+ mService.setBackupServiceActive(NON_SYSTEM_USER, true);
+ simulateUserUnlocked(NON_SYSTEM_USER);
+
+ String[] args = new String[]{"users"};
+ mService.dumpWithoutCheckingPermission(mFileDescriptorStub, mPrintWriterMock, args);
+
+ // Check that dump() invocations are not called on user's Backup service,
+ // as 'dumpsys backup users' only list users for whom Backup service is running.
+ verify(mSystemUserBackupManagerService, never()).dump(any(), any(), any());
+ verify(mNonSystemUserBackupManagerService, never()).dump(any(), any(), any());
+ }
+
/**
* Test that {@link BackupManagerService#dump(FileDescriptor, PrintWriter, String[])} dumps
* system user information before non-system user information.
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index 5f2db795f8bc..37ebdda9efc8 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -593,6 +593,10 @@ public class MagnificationControllerTest {
// The second time is triggered when magnification spec is changed.
verify(mWindowMagnificationManager, times(2)).showMagnificationButton(eq(TEST_DISPLAY),
eq(MODE_FULLSCREEN));
+ // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel
+ // in current capability and mode, and the magnification is activated.
+ verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel(
+ eq(TEST_DISPLAY));
}
@Test
@@ -758,6 +762,10 @@ public class MagnificationControllerTest {
// The second time is triggered when accessibility action performed.
verify(mWindowMagnificationManager, times(2)).showMagnificationButton(eq(TEST_DISPLAY),
eq(MODE_WINDOW));
+ // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel
+ // in current capability and mode, and the magnification is activated.
+ verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel(
+ eq(TEST_DISPLAY));
}
@Test
@@ -772,6 +780,10 @@ public class MagnificationControllerTest {
// The first time is triggered when window mode is activated.
// The second time is triggered when accessibility action performed.
verify(mWindowMagnificationManager, times(2)).removeMagnificationButton(eq(TEST_DISPLAY));
+ // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel
+ // in current capability and mode, and the magnification is activated.
+ verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel(
+ eq(TEST_DISPLAY));
}
@Test public void activateWindowMagnification_triggerCallback() throws RemoteException {
@@ -952,6 +964,10 @@ public class MagnificationControllerTest {
// The third time is triggered when user interaction changed.
verify(mWindowMagnificationManager, times(3)).showMagnificationButton(eq(TEST_DISPLAY),
eq(MODE_FULLSCREEN));
+ // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel
+ // in current capability and mode, and the magnification is activated.
+ verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel(
+ eq(TEST_DISPLAY));
}
@Test
@@ -966,6 +982,10 @@ public class MagnificationControllerTest {
// The third time is triggered when user interaction changed.
verify(mWindowMagnificationManager, times(3)).showMagnificationButton(eq(TEST_DISPLAY),
eq(MODE_FULLSCREEN));
+ // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel
+ // in current capability and mode, and the magnification is activated.
+ verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel(
+ eq(TEST_DISPLAY));
}
@Test
@@ -979,6 +999,10 @@ public class MagnificationControllerTest {
// The second time is triggered when user interaction changed.
verify(mWindowMagnificationManager, times(2)).showMagnificationButton(eq(TEST_DISPLAY),
eq(MODE_WINDOW));
+ // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel
+ // in current capability and mode, and the magnification is activated.
+ verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel(
+ eq(TEST_DISPLAY));
}
@Test
@@ -992,6 +1016,10 @@ public class MagnificationControllerTest {
// The second time is triggered when user interaction changed.
verify(mWindowMagnificationManager, times(2)).showMagnificationButton(eq(TEST_DISPLAY),
eq(MODE_WINDOW));
+ // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel
+ // in current capability and mode, and the magnification is activated.
+ verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel(
+ eq(TEST_DISPLAY));
}
@Test
@@ -1006,11 +1034,16 @@ public class MagnificationControllerTest {
verify(mWindowMagnificationManager, never()).showMagnificationButton(eq(TEST_DISPLAY),
eq(MODE_FULLSCREEN));
+ // The first time is triggered when fullscreen mode is activated.
+ // The second time is triggered when magnification spec is changed.
+ verify(mWindowMagnificationManager, times(2)).removeMagnificationSettingsPanel(
+ eq(TEST_DISPLAY));
}
@Test
- public void onTouchInteractionChanged_fullscreenNotActivated_notShowMagnificationButton()
+ public void
+ onTouchInteractionChanged_fullscreenNotActivated_notShowMagnificationButton()
throws RemoteException {
setMagnificationModeSettings(MODE_FULLSCREEN);
@@ -1019,6 +1052,8 @@ public class MagnificationControllerTest {
verify(mWindowMagnificationManager, never()).showMagnificationButton(eq(TEST_DISPLAY),
eq(MODE_FULLSCREEN));
+ verify(mWindowMagnificationManager, times(2)).removeMagnificationSettingsPanel(
+ eq(TEST_DISPLAY));
}
@Test
@@ -1028,6 +1063,10 @@ public class MagnificationControllerTest {
verify(mWindowMagnificationManager).showMagnificationButton(eq(TEST_DISPLAY),
eq(MODE_WINDOW));
+ // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel
+ // in current capability and mode, and the magnification is activated.
+ verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel(
+ eq(TEST_DISPLAY));
}
@Test
@@ -1042,25 +1081,32 @@ public class MagnificationControllerTest {
// The third time is triggered when fullscreen mode activation state is updated.
verify(mWindowMagnificationManager, times(3)).showMagnificationButton(eq(TEST_DISPLAY),
eq(MODE_FULLSCREEN));
+ // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel
+ // in current capability and mode, and the magnification is activated.
+ verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel(
+ eq(TEST_DISPLAY));
}
@Test
- public void disableWindowMode_windowEnabled_removeMagnificationButton()
+ public void disableWindowMode_windowEnabled_removeMagnificationButtonAndSettingsPanel()
throws RemoteException {
setMagnificationEnabled(MODE_WINDOW);
mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, false);
verify(mWindowMagnificationManager).removeMagnificationButton(eq(TEST_DISPLAY));
+ verify(mWindowMagnificationManager).removeMagnificationSettingsPanel(eq(TEST_DISPLAY));
}
@Test
- public void onFullScreenDeactivated_fullScreenEnabled_removeMagnificationButton()
+ public void
+ onFullScreenDeactivated_fullScreenEnabled_removeMagnificationButtonAneSettingsPanel()
throws RemoteException {
setMagnificationEnabled(MODE_FULLSCREEN);
mScreenMagnificationController.reset(TEST_DISPLAY, /* animate= */ true);
verify(mWindowMagnificationManager).removeMagnificationButton(eq(TEST_DISPLAY));
+ verify(mWindowMagnificationManager).removeMagnificationSettingsPanel(eq(TEST_DISPLAY));
}
@Test
@@ -1077,6 +1123,8 @@ public class MagnificationControllerTest {
// The third time is triggered when the disable-magnification callback is triggered.
verify(mWindowMagnificationManager, times(3)).showMagnificationButton(eq(TEST_DISPLAY),
eq(MODE_FULLSCREEN));
+ // It is triggered when the disable-magnification callback is triggered.
+ verify(mWindowMagnificationManager).removeMagnificationSettingsPanel(eq(TEST_DISPLAY));
}
@Test
@@ -1096,6 +1144,8 @@ public class MagnificationControllerTest {
// The second time is triggered when the disable-magnification callback is triggered.
verify(mWindowMagnificationManager, times(2)).showMagnificationButton(eq(TEST_DISPLAY),
eq(MODE_WINDOW));
+ // It is triggered when the disable-magnification callback is triggered.
+ verify(mWindowMagnificationManager).removeMagnificationSettingsPanel(eq(TEST_DISPLAY));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java
new file mode 100644
index 000000000000..cba7dbe2d02b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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.server.power.stats.wakeups;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.util.SparseIntArray;
+import android.util.TimeSparseArray;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.server.power.stats.wakeups.CpuWakeupStats.WakingActivityHistory;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class WakingActivityHistoryTest {
+
+ private static boolean areSame(SparseIntArray a, SparseIntArray b) {
+ if (a == b) {
+ return true;
+ }
+ if (a == null || b == null) {
+ return false;
+ }
+ final int lenA = a.size();
+ if (b.size() != lenA) {
+ return false;
+ }
+ for (int i = 0; i < lenA; i++) {
+ if (a.keyAt(i) != b.keyAt(i)) {
+ return false;
+ }
+ if (a.valueAt(i) != b.valueAt(i)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Test
+ public void recordActivityAppendsUids() {
+ final WakingActivityHistory history = new WakingActivityHistory();
+ final int subsystem = 42;
+ final long timestamp = 54;
+
+ final SparseIntArray uids = new SparseIntArray();
+ uids.put(1, 3);
+ uids.put(5, 2);
+
+ history.recordActivity(subsystem, timestamp, uids);
+
+ assertThat(history.mWakingActivity.size()).isEqualTo(1);
+ assertThat(history.mWakingActivity.contains(subsystem)).isTrue();
+ assertThat(history.mWakingActivity.contains(subsystem + 1)).isFalse();
+ assertThat(history.mWakingActivity.contains(subsystem - 1)).isFalse();
+
+ final TimeSparseArray<SparseIntArray> recordedHistory = history.mWakingActivity.get(
+ subsystem);
+
+ assertThat(recordedHistory.size()).isEqualTo(1);
+ assertThat(recordedHistory.indexOfKey(timestamp - 1)).isLessThan(0);
+ assertThat(recordedHistory.indexOfKey(timestamp)).isAtLeast(0);
+ assertThat(recordedHistory.indexOfKey(timestamp + 1)).isLessThan(0);
+
+ SparseIntArray recordedUids = recordedHistory.get(timestamp);
+ assertThat(recordedUids).isNotSameInstanceAs(uids);
+ assertThat(areSame(recordedUids, uids)).isTrue();
+
+ uids.put(1, 7);
+ uids.clear();
+ uids.put(10, 12);
+ uids.put(17, 53);
+
+ history.recordActivity(subsystem, timestamp, uids);
+
+ recordedUids = recordedHistory.get(timestamp);
+
+ assertThat(recordedUids.size()).isEqualTo(4);
+ assertThat(recordedUids.indexOfKey(1)).isAtLeast(0);
+ assertThat(recordedUids.get(5, -1)).isEqualTo(2);
+ assertThat(recordedUids.get(10, -1)).isEqualTo(12);
+ assertThat(recordedUids.get(17, -1)).isEqualTo(53);
+ }
+
+ @Test
+ public void recordActivityDoesNotDeleteExistingUids() {
+ final WakingActivityHistory history = new WakingActivityHistory();
+ final int subsystem = 42;
+ long timestamp = 101;
+
+ final SparseIntArray uids = new SparseIntArray();
+ uids.put(1, 17);
+ uids.put(15, 2);
+ uids.put(62, 31);
+
+ history.recordActivity(subsystem, timestamp, uids);
+
+ assertThat(history.mWakingActivity.size()).isEqualTo(1);
+ assertThat(history.mWakingActivity.contains(subsystem)).isTrue();
+ assertThat(history.mWakingActivity.contains(subsystem + 1)).isFalse();
+ assertThat(history.mWakingActivity.contains(subsystem - 1)).isFalse();
+
+ final TimeSparseArray<SparseIntArray> recordedHistory = history.mWakingActivity.get(
+ subsystem);
+
+ assertThat(recordedHistory.size()).isEqualTo(1);
+ assertThat(recordedHistory.indexOfKey(timestamp - 1)).isLessThan(0);
+ assertThat(recordedHistory.indexOfKey(timestamp)).isAtLeast(0);
+ assertThat(recordedHistory.indexOfKey(timestamp + 1)).isLessThan(0);
+
+ SparseIntArray recordedUids = recordedHistory.get(timestamp);
+ assertThat(recordedUids).isNotSameInstanceAs(uids);
+ assertThat(areSame(recordedUids, uids)).isTrue();
+
+ uids.delete(1);
+ uids.delete(15);
+ uids.put(85, 39);
+
+ history.recordActivity(subsystem, timestamp, uids);
+ recordedUids = recordedHistory.get(timestamp);
+
+ assertThat(recordedUids.size()).isEqualTo(4);
+ assertThat(recordedUids.get(1, -1)).isEqualTo(17);
+ assertThat(recordedUids.get(15, -1)).isEqualTo(2);
+ assertThat(recordedUids.get(62, -1)).isEqualTo(31);
+ assertThat(recordedUids.get(85, -1)).isEqualTo(39);
+
+ uids.clear();
+ history.recordActivity(subsystem, timestamp, uids);
+ recordedUids = recordedHistory.get(timestamp);
+
+ assertThat(recordedUids.size()).isEqualTo(4);
+ assertThat(recordedUids.get(1, -1)).isEqualTo(17);
+ assertThat(recordedUids.get(15, -1)).isEqualTo(2);
+ assertThat(recordedUids.get(62, -1)).isEqualTo(31);
+ assertThat(recordedUids.get(85, -1)).isEqualTo(39);
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index ee1afcf318fa..0ddd3135506e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -1390,7 +1390,8 @@ public class WindowStateTests extends WindowTestsBase {
private boolean mIsVisibleForImeInputTarget;
@Override
- public void onImeTargetOverlayVisibilityChanged(IBinder overlayWindowToken, boolean visible,
+ public void onImeTargetOverlayVisibilityChanged(IBinder overlayWindowToken,
+ @WindowManager.LayoutParams.WindowType int windowType, boolean visible,
boolean removed) {
mImeTargetToken = overlayWindowToken;
mIsVisibleForImeTargetOverlay = visible;
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
index 31fab89d1d4e..7ec2d9fd7b23 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
@@ -265,13 +265,6 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
/** Model is loaded, recognition is active. */
ACTIVE,
/**
- * Model is active as far as the client is concerned, but loaded as far as the
- * layers are concerned. This condition occurs when a recognition event that indicates
- * the recognition for this model arrived from the underlying layer, but had not been
- * delivered to the caller (most commonly, for permission reasons).
- */
- INTERCEPTED,
- /**
* Model has been preemptively unloaded by the HAL.
*/
PREEMPTED,
@@ -483,18 +476,6 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
throw new IllegalStateException("Invalid handle: " + modelHandle);
}
// stopRecognition is idempotent - no need to check model state.
-
- // From here on, every exception isn't client's fault.
- try {
- // If the activity state is INTERCEPTED, we skip delegating the command, but
- // still consider the call valid.
- if (modelState.activityState == ModelState.Activity.INTERCEPTED) {
- modelState.activityState = ModelState.Activity.LOADED;
- return;
- }
- } catch (Exception e) {
- throw handleException(e);
- }
}
// Calling the delegate's stop must be done without the lock.
@@ -518,27 +499,6 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
}
}
- private void restartIfIntercepted(int modelHandle) {
- synchronized (SoundTriggerMiddlewareValidation.this) {
- // State validation.
- if (mState == ModuleStatus.DETACHED) {
- return;
- }
- ModelState modelState = mLoadedModels.get(modelHandle);
- if (modelState == null
- || modelState.activityState != ModelState.Activity.INTERCEPTED) {
- return;
- }
- try {
- mDelegate.startRecognition(modelHandle, modelState.config);
- modelState.activityState = ModelState.Activity.ACTIVE;
- Log.i(TAG, "Restarted intercepted model " + modelHandle);
- } catch (Exception e) {
- Log.i(TAG, "Failed to restart intercepted model " + modelHandle, e);
- }
- }
- }
-
@Override
public void forceRecognitionEvent(int modelHandle) {
// Input validation (always valid).
@@ -742,18 +702,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
mCallback.onRecognition(modelHandle, event, captureSession);
} catch (Exception e) {
Log.w(TAG, "Client callback exception.", e);
- synchronized (SoundTriggerMiddlewareValidation.this) {
- ModelState modelState = mLoadedModels.get(modelHandle);
- if (event.recognitionEvent.status != RecognitionStatus.FORCED) {
- modelState.activityState = ModelState.Activity.INTERCEPTED;
- // If we failed to deliver an actual event to the client, they would
- // never know to restart it whenever circumstances change. Thus, we
- // restart it here. We do this from a separate thread to avoid any
- // race conditions.
- new Thread(() -> restartIfIntercepted(modelHandle)).start();
- }
- }
- }
+ }
}
@Override
@@ -771,18 +720,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
mCallback.onPhraseRecognition(modelHandle, event, captureSession);
} catch (Exception e) {
Log.w(TAG, "Client callback exception.", e);
- synchronized (SoundTriggerMiddlewareValidation.this) {
- ModelState modelState = mLoadedModels.get(modelHandle);
- if (!event.phraseRecognitionEvent.common.recognitionStillActive) {
- modelState.activityState = ModelState.Activity.INTERCEPTED;
- // If we failed to deliver an actual event to the client, they would
- // never know to restart it whenever circumstances change. Thus, we
- // restart it here. We do this from a separate thread to avoid any
- // race conditions.
- new Thread(() -> restartIfIntercepted(modelHandle)).start();
- }
- }
- }
+ }
}
@Override
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java
index c390e42f348e..33f4d465abab 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java
@@ -275,7 +275,12 @@ public final class KnownNetwork implements Parcelable {
dest.writeInt(mNetworkSource);
dest.writeString(mSsid);
dest.writeArraySet(mSecurityTypes);
- mNetworkProviderInfo.writeToParcel(dest, flags);
+ if (mNetworkProviderInfo != null) {
+ dest.writeBoolean(true);
+ mNetworkProviderInfo.writeToParcel(dest, flags);
+ } else {
+ dest.writeBoolean(false);
+ }
dest.writeBundle(mExtras);
}
@@ -286,9 +291,15 @@ public final class KnownNetwork implements Parcelable {
*/
@NonNull
public static KnownNetwork readFromParcel(@NonNull Parcel in) {
- return new KnownNetwork(in.readInt(), in.readString(),
- (ArraySet<Integer>) in.readArraySet(null),
- NetworkProviderInfo.readFromParcel(in), in.readBundle());
+ int networkSource = in.readInt();
+ String mSsid = in.readString();
+ ArraySet<Integer> securityTypes = (ArraySet<Integer>) in.readArraySet(null);
+ if (in.readBoolean()) {
+ return new KnownNetwork(networkSource, mSsid, securityTypes,
+ NetworkProviderInfo.readFromParcel(in), in.readBundle());
+ }
+ return new KnownNetwork(networkSource, mSsid, securityTypes, null,
+ in.readBundle());
}
@NonNull
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java
index af3afa88f5e0..5ad3ede8498d 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java
@@ -161,7 +161,7 @@ public final class SharedConnectivitySettingsState implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- mInstantTetherSettingsPendingIntent.writeToParcel(dest, 0);
+ PendingIntent.writePendingIntentOrNullToParcel(mInstantTetherSettingsPendingIntent, dest);
dest.writeBoolean(mInstantTetherEnabled);
dest.writeBundle(mExtras);
}
@@ -173,7 +173,7 @@ public final class SharedConnectivitySettingsState implements Parcelable {
*/
@NonNull
public static SharedConnectivitySettingsState readFromParcel(@NonNull Parcel in) {
- PendingIntent pendingIntent = PendingIntent.CREATOR.createFromParcel(in);
+ PendingIntent pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(in);
boolean instantTetherEnabled = in.readBoolean();
Bundle extras = in.readBundle();
return new SharedConnectivitySettingsState(instantTetherEnabled, pendingIntent, extras);