diff options
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 Binary files differnew file mode 100644 index 000000000000..6d7c59cccfc7 --- /dev/null +++ b/core/res/res/drawable-hdpi/pointer_handwriting.png diff --git a/core/res/res/drawable-mdpi/pointer_handwriting.png b/core/res/res/drawable-mdpi/pointer_handwriting.png Binary files differnew file mode 100644 index 000000000000..b36241bec84e --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_handwriting.png diff --git a/core/res/res/drawable-xhdpi/pointer_handwriting.png b/core/res/res/drawable-xhdpi/pointer_handwriting.png Binary files differnew file mode 100644 index 000000000000..dea1972a6216 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_handwriting.png diff --git a/core/res/res/drawable-xxhdpi/pointer_handwriting.png b/core/res/res/drawable-xxhdpi/pointer_handwriting.png Binary files differnew file mode 100644 index 000000000000..870c40206e3c --- /dev/null +++ b/core/res/res/drawable-xxhdpi/pointer_handwriting.png 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); |