diff options
49 files changed, 1272 insertions, 205 deletions
diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java index 61b56877589b..599f1a8be233 100644 --- a/core/java/android/app/AppCompatTaskInfo.java +++ b/core/java/android/app/AppCompatTaskInfo.java @@ -27,6 +27,7 @@ import android.os.Parcelable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Objects; /** * Stores App Compat information about a particular Task. @@ -58,16 +59,11 @@ public class AppCompatTaskInfo implements Parcelable { public int topActivityLetterboxHeight = PROPERTY_VALUE_UNSET; /** - * Contains the current app height of the letterboxed activity if available or - * {@link TaskInfo#PROPERTY_VALUE_UNSET} otherwise. + * Contains the app bounds of the top activity or size compat mode + * bounds when in size compat mode. If null, contains bounds. */ - public int topActivityLetterboxAppHeight = PROPERTY_VALUE_UNSET; - - /** - * Contains the current app width of the letterboxed activity if available or - * {@link TaskInfo#PROPERTY_VALUE_UNSET} otherwise. - */ - public int topActivityLetterboxAppWidth = PROPERTY_VALUE_UNSET; + @NonNull + public final Rect topActivityAppBounds = new Rect(); /** * Contains the top activity bounds when the activity is letterboxed. @@ -350,8 +346,7 @@ public class AppCompatTaskInfo implements Parcelable { && topActivityLetterboxVerticalPosition == that.topActivityLetterboxVerticalPosition && topActivityLetterboxWidth == that.topActivityLetterboxWidth && topActivityLetterboxHeight == that.topActivityLetterboxHeight - && topActivityLetterboxAppWidth == that.topActivityLetterboxAppWidth - && topActivityLetterboxAppHeight == that.topActivityLetterboxAppHeight + && topActivityAppBounds.equals(that.topActivityAppBounds) && topActivityLetterboxHorizontalPosition == that.topActivityLetterboxHorizontalPosition && cameraCompatTaskInfo.equalsForTaskOrganizer(that.cameraCompatTaskInfo); @@ -371,8 +366,7 @@ public class AppCompatTaskInfo implements Parcelable { == that.topActivityLetterboxHorizontalPosition && topActivityLetterboxWidth == that.topActivityLetterboxWidth && topActivityLetterboxHeight == that.topActivityLetterboxHeight - && topActivityLetterboxAppWidth == that.topActivityLetterboxAppWidth - && topActivityLetterboxAppHeight == that.topActivityLetterboxAppHeight + && topActivityAppBounds.equals(that.topActivityAppBounds) && cameraCompatTaskInfo.equalsForCompatUi(that.cameraCompatTaskInfo); } @@ -385,8 +379,7 @@ public class AppCompatTaskInfo implements Parcelable { topActivityLetterboxHorizontalPosition = source.readInt(); topActivityLetterboxWidth = source.readInt(); topActivityLetterboxHeight = source.readInt(); - topActivityLetterboxAppWidth = source.readInt(); - topActivityLetterboxAppHeight = source.readInt(); + topActivityAppBounds.set(Objects.requireNonNull(source.readTypedObject(Rect.CREATOR))); topActivityLetterboxBounds = source.readTypedObject(Rect.CREATOR); cameraCompatTaskInfo = source.readTypedObject(CameraCompatTaskInfo.CREATOR); } @@ -401,8 +394,7 @@ public class AppCompatTaskInfo implements Parcelable { dest.writeInt(topActivityLetterboxHorizontalPosition); dest.writeInt(topActivityLetterboxWidth); dest.writeInt(topActivityLetterboxHeight); - dest.writeInt(topActivityLetterboxAppWidth); - dest.writeInt(topActivityLetterboxAppHeight); + dest.writeTypedObject(topActivityAppBounds, flags); dest.writeTypedObject(topActivityLetterboxBounds, flags); dest.writeTypedObject(cameraCompatTaskInfo, flags); } @@ -421,8 +413,7 @@ public class AppCompatTaskInfo implements Parcelable { + topActivityLetterboxHorizontalPosition + " topActivityLetterboxWidth=" + topActivityLetterboxWidth + " topActivityLetterboxHeight=" + topActivityLetterboxHeight - + " topActivityLetterboxAppWidth=" + topActivityLetterboxAppWidth - + " topActivityLetterboxAppHeight=" + topActivityLetterboxAppHeight + + " topActivityAppBounds=" + topActivityAppBounds + " isUserFullscreenOverrideEnabled=" + isUserFullscreenOverrideEnabled() + " isSystemFullscreenOverrideEnabled=" + isSystemFullscreenOverrideEnabled() + " hasMinAspectRatioOverride=" + hasMinAspectRatioOverride() diff --git a/core/java/android/hardware/display/DisplayTopology.java b/core/java/android/hardware/display/DisplayTopology.java index 211aefffa34c..ba5dfc094afb 100644 --- a/core/java/android/hardware/display/DisplayTopology.java +++ b/core/java/android/hardware/display/DisplayTopology.java @@ -129,14 +129,38 @@ public final class DisplayTopology implements Parcelable { } /** + * Update the size of a display and normalize the topology. + * @param displayId The logical display ID + * @param width The new width + * @param height The new height + * @return True if the topology has changed. + */ + public boolean updateDisplay(int displayId, float width, float height) { + TreeNode display = findDisplay(displayId, mRoot); + if (display == null) { + return false; + } + if (floatEquals(display.mWidth, width) && floatEquals(display.mHeight, height)) { + return false; + } + display.mWidth = width; + display.mHeight = height; + normalize(); + Slog.i(TAG, "Display with ID " + displayId + " updated, new width: " + width + + ", new height: " + height); + return true; + } + + /** * Remove a display from the topology. * The default topology is created from the remaining displays, as if they were reconnected * one by one. * @param displayId The logical display ID + * @return True if the display was present in the topology and removed. */ - public void removeDisplay(int displayId) { + public boolean removeDisplay(int displayId) { if (findDisplay(displayId, mRoot) == null) { - return; + return false; } Queue<TreeNode> queue = new ArrayDeque<>(); queue.add(mRoot); @@ -159,6 +183,7 @@ public final class DisplayTopology implements Parcelable { } else { Slog.i(TAG, "Display with ID " + displayId + " removed"); } + return true; } /** @@ -685,12 +710,12 @@ public final class DisplayTopology implements Parcelable { /** * The width of the display in density-independent pixels (dp). */ - private final float mWidth; + private float mWidth; /** * The height of the display in density-independent pixels (dp). */ - private final float mHeight; + private float mHeight; /** * The position of this display relative to its parent. diff --git a/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig b/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig index 20d193ea2351..f709ed7f57cd 100644 --- a/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig +++ b/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig @@ -12,5 +12,5 @@ flag { name: "ccapi_baklava_enabled" namespace: "machine_learning" description: "Feature flag for baklava content capture API" - bug: "309411951" + bug: "380381249" } diff --git a/core/java/android/window/WindowTokenClientController.java b/core/java/android/window/WindowTokenClientController.java index 11019324acd8..fcd7dfbb1769 100644 --- a/core/java/android/window/WindowTokenClientController.java +++ b/core/java/android/window/WindowTokenClientController.java @@ -148,6 +148,9 @@ public class WindowTokenClientController { info = wms.attachWindowContextToDisplayContent(mAppThread, client, displayId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); + } catch (Exception e) { + Log.e(TAG, "Failed attachToDisplayContent", e); + return false; } if (info == null) { return false; diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig index 801698caff0e..0d04961569e6 100644 --- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig +++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig @@ -58,13 +58,6 @@ flag { } flag { - name: "user_min_aspect_ratio_app_default" - namespace: "large_screen_experiences_app_compat" - description: "Whether the API PackageManager.USER_MIN_ASPECT_RATIO_APP_DEFAULT is available" - bug: "310816437" -} - -flag { name: "allow_hide_scm_button" namespace: "large_screen_experiences_app_compat" description: "Whether we should allow hiding the size compat restart button" diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 9bd52372e6c4..39ddea614ee4 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -25,6 +25,8 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED import static android.security.Flags.reportPrimaryAuthAttempts; import static android.security.Flags.shouldTrustManagerListenForPrimaryAuth; +import static com.android.internal.widget.flags.Flags.hideLastCharWithPhysicalInput; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -42,6 +44,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.UserInfo; +import android.hardware.input.InputManagerGlobal; import android.os.Build; import android.os.Handler; import android.os.Looper; @@ -59,6 +62,7 @@ import android.util.Log; import android.util.SparseBooleanArray; import android.util.SparseIntArray; import android.util.SparseLongArray; +import android.view.InputDevice; import com.android.internal.annotations.VisibleForTesting; import com.android.server.LocalServices; @@ -1097,12 +1101,20 @@ public class LockPatternUtils { return type == CREDENTIAL_TYPE_PATTERN; } + private boolean hasActivePointerDeviceAttached() { + return !getEnabledNonTouchInputDevices(InputDevice.SOURCE_CLASS_POINTER).isEmpty(); + } + /** * @return Whether the visible pattern is enabled. */ @UnsupportedAppUsage public boolean isVisiblePatternEnabled(int userId) { - return getBoolean(Settings.Secure.LOCK_PATTERN_VISIBLE, true, userId); + boolean defaultValue = true; + if (hideLastCharWithPhysicalInput()) { + defaultValue = !hasActivePointerDeviceAttached(); + } + return getBoolean(Settings.Secure.LOCK_PATTERN_VISIBLE, defaultValue, userId); } /** @@ -1116,11 +1128,39 @@ public class LockPatternUtils { return getString(Settings.Secure.LOCK_PATTERN_VISIBLE, userId) != null; } + private List<InputDevice> getEnabledNonTouchInputDevices(int source) { + final InputManagerGlobal inputManager = InputManagerGlobal.getInstance(); + final int[] inputIds = inputManager.getInputDeviceIds(); + List<InputDevice> matchingDevices = new ArrayList<InputDevice>(); + for (final int deviceId : inputIds) { + final InputDevice inputDevice = inputManager.getInputDevice(deviceId); + if (!inputDevice.isEnabled()) continue; + if (inputDevice.supportsSource(InputDevice.SOURCE_TOUCHSCREEN)) continue; + if (inputDevice.isVirtual()) continue; + if (!inputDevice.supportsSource(source)) continue; + matchingDevices.add(inputDevice); + } + return matchingDevices; + } + + private boolean hasPhysicalKeyboardActive() { + final List<InputDevice> keyboards = + getEnabledNonTouchInputDevices(InputDevice.SOURCE_KEYBOARD); + for (final InputDevice keyboard : keyboards) { + if (keyboard.isFullKeyboard()) return true; + } + return false; + } + /** * @return Whether enhanced pin privacy is enabled. */ public boolean isPinEnhancedPrivacyEnabled(int userId) { - return getBoolean(LOCK_PIN_ENHANCED_PRIVACY, false, userId); + boolean defaultValue = false; + if (hideLastCharWithPhysicalInput()) { + defaultValue = hasPhysicalKeyboardActive(); + } + return getBoolean(LOCK_PIN_ENHANCED_PRIVACY, defaultValue, userId); } /** diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 027113a75f6b..5acdf32a9f84 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -482,11 +482,22 @@ cc_library_shared_for_libandroid_runtime { "libbinder", "libhidlbase", // libhwbinder is in here ], + version_script: "platform/linux/libandroid_runtime_export.txt", + }, + darwin: { + host_ldlibs: [ + "-framework AppKit", + ], + dist: { + targets: ["layoutlib_jni"], + dir: "layoutlib_native/darwin", + }, + exported_symbols_list: "platform/darwin/libandroid_runtime_export.exp", }, linux_glibc_x86_64: { ldflags: ["-static-libgcc"], dist: { - targets: ["layoutlib"], + targets: ["layoutlib_jni"], dir: "layoutlib_native/linux", tag: "stripped_all", }, diff --git a/core/jni/platform/darwin/libandroid_runtime_export.exp b/core/jni/platform/darwin/libandroid_runtime_export.exp new file mode 100644 index 000000000000..00a7585719ea --- /dev/null +++ b/core/jni/platform/darwin/libandroid_runtime_export.exp @@ -0,0 +1,38 @@ +# +# Copyright (C) 2024 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. +# + +# symbols needed for the JNI operations +_JNI_OnLoad +_ANativeWindow* + +# symbols needed to link with layoutlib_jni +___android_log* +__ZNK7android7RefBase* +__ZN7android4base9SetLogger* +__ZN7android4base10SetAborter* +__ZN7android4base11GetProperty* +__ZN7android4Rect* +__ZN7android5Fence* +__ZN7android7RefBase* +__ZN7android7String* +__ZN7android10VectorImpl* +__ZN7android11BufferQueue* +__ZN7android14AndroidRuntime* +__ZN7android14sp_report_raceEv* +__ZN7android15KeyCharacterMap* +__ZN7android15InputDeviceInfo* +__ZN7android31android_view_InputDevice_create* +__ZN7android53android_view_Surface_createFromIGraphicBufferProducer* diff --git a/core/jni/platform/linux/libandroid_runtime_export.txt b/core/jni/platform/linux/libandroid_runtime_export.txt new file mode 100644 index 000000000000..19e3478d5d38 --- /dev/null +++ b/core/jni/platform/linux/libandroid_runtime_export.txt @@ -0,0 +1,43 @@ +# +# Copyright (C) 2024 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. +# + +{ + global: + # symbols needed for the JNI operations + JNI_OnLoad; + ANativeWindow*; + + # symbols needed to link with layoutlib_jni + __android_log*; + _ZNK7android7RefBase*; + _ZN7android4base9SetLogger*; + _ZN7android4base10SetAborter*; + _ZN7android4base11GetProperty*; + _ZN7android4Rect*; + _ZN7android5Fence*; + _ZN7android7RefBase*; + _ZN7android7String*; + _ZN7android10VectorImpl*; + _ZN7android11BufferQueue*; + _ZN7android14AndroidRuntime*; + _ZN7android14sp_report_raceEv*; + _ZN7android15KeyCharacterMap*; + _ZN7android15InputDeviceInfo*; + _ZN7android31android_view_InputDevice_create*; + _ZN7android53android_view_Surface_createFromIGraphicBufferProducer*; + local: + *; +}; diff --git a/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt b/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt index 4a227d8ff1ef..255d09b854bd 100644 --- a/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt +++ b/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt @@ -86,7 +86,7 @@ class DisplayTopologyTest { verifyDisplay(display1, displayId1, width1, height1, noOfChildren = 1) val display2 = display1.children[0] - verifyDisplay(display1.children[0], displayId2, width2, height2, POSITION_TOP, + verifyDisplay(display2, displayId2, width2, height2, POSITION_TOP, offset = width1 / 2 - width2 / 2, noOfChildren = 1) var display = display2 @@ -99,6 +99,76 @@ class DisplayTopologyTest { } @Test + fun updateDisplay() { + val displayId = 1 + val width = 800f + val height = 600f + + val newWidth = 1000f + val newHeight = 500f + topology.addDisplay(displayId, width, height) + assertThat(topology.updateDisplay(displayId, newWidth, newHeight)).isTrue() + + assertThat(topology.primaryDisplayId).isEqualTo(displayId) + verifyDisplay(topology.root!!, displayId, newWidth, newHeight, noOfChildren = 0) + } + + @Test + fun updateDisplay_notUpdated() { + val displayId = 1 + val width = 800f + val height = 600f + topology.addDisplay(displayId, width, height) + + // Same size + assertThat(topology.updateDisplay(displayId, width, height)).isFalse() + + // Display doesn't exist + assertThat(topology.updateDisplay(/* displayId= */ 100, width, height)).isFalse() + + assertThat(topology.primaryDisplayId).isEqualTo(displayId) + verifyDisplay(topology.root!!, displayId, width, height, noOfChildren = 0) + } + + @Test + fun updateDisplayDoesNotAffectDefaultTopology() { + val width1 = 700f + val height = 600f + topology.addDisplay(/* displayId= */ 1, width1, height) + + val width2 = 800f + val noOfDisplays = 30 + for (i in 2..noOfDisplays) { + topology.addDisplay(/* displayId= */ i, width2, height) + } + + val displaysToUpdate = arrayOf(3, 7, 18) + val newWidth = 1000f + val newHeight = 1500f + for (i in displaysToUpdate) { + assertThat(topology.updateDisplay(/* displayId= */ i, newWidth, newHeight)).isTrue() + } + + assertThat(topology.primaryDisplayId).isEqualTo(1) + + val display1 = topology.root!! + verifyDisplay(display1, id = 1, width1, height, noOfChildren = 1) + + val display2 = display1.children[0] + verifyDisplay(display2, id = 2, width2, height, POSITION_TOP, + offset = width1 / 2 - width2 / 2, noOfChildren = 1) + + var display = display2 + for (i in 3..noOfDisplays) { + display = display.children[0] + // The last display should have no children + verifyDisplay(display, id = i, if (i in displaysToUpdate) newWidth else width2, + if (i in displaysToUpdate) newHeight else height, POSITION_RIGHT, offset = 0f, + noOfChildren = if (i < noOfDisplays) 1 else 0) + } + } + + @Test fun removeDisplays() { val displayId1 = 1 val width1 = 800f @@ -117,7 +187,7 @@ class DisplayTopologyTest { } var removedDisplays = arrayOf(20) - topology.removeDisplay(20) + assertThat(topology.removeDisplay(20)).isTrue() assertThat(topology.primaryDisplayId).isEqualTo(displayId1) @@ -139,11 +209,11 @@ class DisplayTopologyTest { noOfChildren = if (i < noOfDisplays) 1 else 0) } - topology.removeDisplay(22) + assertThat(topology.removeDisplay(22)).isTrue() removedDisplays += 22 - topology.removeDisplay(23) + assertThat(topology.removeDisplay(23)).isTrue() removedDisplays += 23 - topology.removeDisplay(25) + assertThat(topology.removeDisplay(25)).isTrue() removedDisplays += 25 assertThat(topology.primaryDisplayId).isEqualTo(displayId1) @@ -174,7 +244,7 @@ class DisplayTopologyTest { val height = 600f topology.addDisplay(displayId, width, height) - topology.removeDisplay(displayId) + assertThat(topology.removeDisplay(displayId)).isTrue() assertThat(topology.primaryDisplayId).isEqualTo(Display.INVALID_DISPLAY) assertThat(topology.root).isNull() @@ -187,7 +257,7 @@ class DisplayTopologyTest { val height = 600f topology.addDisplay(displayId, width, height) - topology.removeDisplay(3) + assertThat(topology.removeDisplay(3)).isFalse() assertThat(topology.primaryDisplayId).isEqualTo(displayId) verifyDisplay(topology.root!!, displayId, width, height, noOfChildren = 0) @@ -203,7 +273,7 @@ class DisplayTopologyTest { topology = DisplayTopology(/* root= */ null, displayId2) topology.addDisplay(displayId1, width, height) topology.addDisplay(displayId2, width, height) - topology.removeDisplay(displayId2) + assertThat(topology.removeDisplay(displayId2)).isTrue() assertThat(topology.primaryDisplayId).isEqualTo(displayId1) verifyDisplay(topology.root!!, displayId1, width, height, noOfChildren = 0) diff --git a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java index 00b4f464de50..d1fbc77cbd46 100644 --- a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java +++ b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java @@ -31,6 +31,7 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.doNothing; @@ -44,20 +45,27 @@ import android.content.ComponentName; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.UserInfo; +import android.hardware.input.IInputManager; +import android.hardware.input.InputManagerGlobal; import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.flag.junit.SetFlagsRule; import android.platform.test.ravenwood.RavenwoodRule; import android.provider.Settings; import android.test.mock.MockContentResolver; +import android.view.InputDevice; import androidx.test.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.util.test.FakeSettingsProvider; +import com.android.internal.widget.flags.Flags; import com.google.android.collect.Lists; @@ -76,6 +84,8 @@ import java.util.concurrent.CompletableFuture; public class LockPatternUtilsTest { @Rule public final RavenwoodRule mRavenwood = new RavenwoodRule(); + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private ILockSettings mLockSettings; private static final int USER_ID = 1; @@ -395,4 +405,156 @@ public class LockPatternUtilsTest { } }; } + + private InputManagerGlobal.TestSession configureExternalHardwareTest(InputDevice[] devices) + throws RemoteException { + final Context context = new ContextWrapper(InstrumentationRegistry.getTargetContext()); + final ILockSettings ils = mock(ILockSettings.class); + when(ils.getBoolean(anyString(), anyBoolean(), anyInt())).thenThrow(RemoteException.class); + mLockPatternUtils = new LockPatternUtils(context, ils); + + IInputManager inputManagerMock = mock(IInputManager.class); + + int[] deviceIds = new int[devices.length]; + + for (int i = 0; i < devices.length; i++) { + when(inputManagerMock.getInputDevice(i)).thenReturn(devices[i]); + } + + when(inputManagerMock.getInputDeviceIds()).thenReturn(deviceIds); + InputManagerGlobal.TestSession session = + InputManagerGlobal.createTestSession(inputManagerMock); + + return session; + } + + @Test + @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT) + public void isPinEnhancedPrivacyEnabled_noDevicesAttached() throws RemoteException { + InputManagerGlobal.TestSession session = configureExternalHardwareTest(new InputDevice[0]); + assertFalse(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID)); + session.close(); + } + + @Test + @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT) + public void isPinEnhancedPrivacyEnabled_noEnabledDeviceAttached() throws RemoteException { + InputDevice.Builder builder = new InputDevice.Builder(); + builder.setEnabled(false); + InputManagerGlobal.TestSession session = + configureExternalHardwareTest(new InputDevice[]{builder.build()}); + assertFalse(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID)); + session.close(); + } + + @Test + @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT) + public void isPinEnhancedPrivacyEnabled_withoutHwKeyboard() throws RemoteException { + InputDevice.Builder builder = new InputDevice.Builder(); + builder.setEnabled(true).setSources(InputDevice.SOURCE_TOUCHSCREEN); + InputManagerGlobal.TestSession session = + configureExternalHardwareTest(new InputDevice[]{builder.build()}); + assertFalse(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID)); + session.close(); + } + + @Test + @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT) + public void isPinEnhancedPrivacyEnabled_withoutFullHwKeyboard() throws RemoteException { + InputDevice.Builder builder = new InputDevice.Builder(); + builder + .setEnabled(true) + .setSources(InputDevice.SOURCE_KEYBOARD) + .setKeyboardType(InputDevice.KEYBOARD_TYPE_NON_ALPHABETIC); + InputManagerGlobal.TestSession session = + configureExternalHardwareTest(new InputDevice[]{builder.build()}); + assertFalse(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID)); + session.close(); + } + + @Test + @DisableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT) + public void isPinEnhancedPrivacyEnabled_withHwKeyboardOldDefault() throws RemoteException { + InputDevice.Builder builder = new InputDevice.Builder(); + builder + .setEnabled(true) + .setSources(InputDevice.SOURCE_KEYBOARD) + .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC); + InputManagerGlobal.TestSession session = + configureExternalHardwareTest(new InputDevice[]{builder.build()}); + assertFalse(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID)); + session.close(); + } + + @Test + @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT) + public void isPinEnhancedPrivacyEnabled_withHwKeyboard() throws RemoteException { + InputDevice.Builder builder = new InputDevice.Builder(); + builder + .setEnabled(true) + .setSources(InputDevice.SOURCE_KEYBOARD) + .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC); + InputManagerGlobal.TestSession session = + configureExternalHardwareTest(new InputDevice[]{builder.build()}); + assertTrue(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID)); + session.close(); + } + + @Test + @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT) + public void isVisiblePatternEnabled_noDevices() throws RemoteException { + InputManagerGlobal.TestSession session = configureExternalHardwareTest(new InputDevice[0]); + assertTrue(mLockPatternUtils.isVisiblePatternEnabled(USER_ID)); + session.close(); + } + + @Test + @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT) + public void isVisiblePatternEnabled_noEnabledDevices() throws RemoteException { + InputDevice.Builder builder = new InputDevice.Builder(); + builder.setEnabled(false); + InputManagerGlobal.TestSession session = + configureExternalHardwareTest(new InputDevice[]{builder.build()}); + assertTrue(mLockPatternUtils.isVisiblePatternEnabled(USER_ID)); + session.close(); + } + + @Test + @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT) + public void isVisiblePatternEnabled_noPointingDevices() throws RemoteException { + InputDevice.Builder builder = new InputDevice.Builder(); + builder + .setEnabled(true) + .setSources(InputDevice.SOURCE_TOUCHSCREEN); + InputManagerGlobal.TestSession session = + configureExternalHardwareTest(new InputDevice[]{builder.build()}); + assertTrue(mLockPatternUtils.isVisiblePatternEnabled(USER_ID)); + session.close(); + } + + @Test + @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT) + public void isVisiblePatternEnabled_externalPointingDevice() throws RemoteException { + InputDevice.Builder builder = new InputDevice.Builder(); + builder + .setEnabled(true) + .setSources(InputDevice.SOURCE_CLASS_POINTER); + InputManagerGlobal.TestSession session = + configureExternalHardwareTest(new InputDevice[]{builder.build()}); + assertFalse(mLockPatternUtils.isVisiblePatternEnabled(USER_ID)); + session.close(); + } + + @Test + @DisableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT) + public void isVisiblePatternEnabled_externalPointingDeviceOldDefault() throws RemoteException { + InputDevice.Builder builder = new InputDevice.Builder(); + builder + .setEnabled(true) + .setSources(InputDevice.SOURCE_CLASS_POINTER); + InputManagerGlobal.TestSession session = + configureExternalHardwareTest(new InputDevice[]{builder.build()}); + assertTrue(mLockPatternUtils.isVisiblePatternEnabled(USER_ID)); + session.close(); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt index 606a729305b4..90191345147c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt @@ -82,7 +82,7 @@ fun calculateInitialBounds( // For portrait resizeable activities, respect apps fullscreen width but // apply ideal size height. Size( - taskInfo.appCompatTaskInfo.topActivityLetterboxAppWidth, + taskInfo.appCompatTaskInfo.topActivityAppBounds.width(), idealSize.height, ) } else { @@ -104,7 +104,7 @@ fun calculateInitialBounds( // apply custom app width. Size( customPortraitWidthForLandscapeApp, - taskInfo.appCompatTaskInfo.topActivityLetterboxAppHeight, + taskInfo.appCompatTaskInfo.topActivityAppBounds.height(), ) } else { // For portrait resizeable activities, simply apply ideal size. @@ -196,13 +196,8 @@ fun maximizeSizeGivenAspectRatio( /** Calculates the aspect ratio of an activity from its fullscreen bounds. */ fun calculateAspectRatio(taskInfo: RunningTaskInfo): Float { - val appLetterboxWidth = taskInfo.appCompatTaskInfo.topActivityLetterboxAppWidth - val appLetterboxHeight = taskInfo.appCompatTaskInfo.topActivityLetterboxAppHeight - if (taskInfo.appCompatTaskInfo.isTopActivityLetterboxed || !taskInfo.canChangeAspectRatio) { - return maxOf(appLetterboxWidth, appLetterboxHeight) / - minOf(appLetterboxWidth, appLetterboxHeight).toFloat() - } - val appBounds = taskInfo.configuration.windowConfiguration.appBounds ?: return 1f + if (taskInfo.appCompatTaskInfo.topActivityAppBounds.isEmpty) return 1f + val appBounds = taskInfo.appCompatTaskInfo.topActivityAppBounds return maxOf(appBounds.height(), appBounds.width()) / minOf(appBounds.height(), appBounds.width()).toFloat() } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java index 37296531ee34..fd387d1811fb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java @@ -350,7 +350,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, } cancelPhysicsAnimation(); mMenuController.hideMenu(ANIM_TYPE_DISMISS, false /* resize */); - mPipScheduler.scheduleRemovePip(); + mPipScheduler.removePipAfterAnimation(); } /** Sets the movement bounds to use to constrain PIP position animations. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java index 7f673d2efc68..4461a5c6a70c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java @@ -122,26 +122,34 @@ public class PipScheduler { * Schedules exit PiP via expand transition. */ public void scheduleExitPipViaExpand() { - mMainExecutor.execute(() -> { - if (!mPipTransitionState.isInPip()) return; - WindowContainerTransaction wct = getExitPipViaExpandTransaction(); - if (wct != null) { + WindowContainerTransaction wct = getExitPipViaExpandTransaction(); + if (wct != null) { + mMainExecutor.execute(() -> { mPipTransitionController.startExitTransition(TRANSIT_EXIT_PIP, wct, null /* destinationBounds */); - } - }); + }); + } + } + + // TODO: Optimize this by running the animation as part of the transition + /** Runs remove PiP animation and schedules remove PiP transition after the animation ends. */ + public void removePipAfterAnimation() { + SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); + PipAlphaAnimator animator = mPipAlphaAnimatorSupplier.get(mContext, + mPipTransitionState.getPinnedTaskLeash(), tx, PipAlphaAnimator.FADE_OUT); + animator.setAnimationEndCallback(this::scheduleRemovePipImmediately); + animator.start(); } /** Schedules remove PiP transition. */ - public void scheduleRemovePip() { - mMainExecutor.execute(() -> { - if (!mPipTransitionState.isInPip()) return; - WindowContainerTransaction wct = getRemovePipTransaction(); - if (wct != null) { + private void scheduleRemovePipImmediately() { + WindowContainerTransaction wct = getRemovePipTransaction(); + if (wct != null) { + mMainExecutor.execute(() -> { mPipTransitionController.startExitTransition(TRANSIT_REMOVE_PIP, wct, null /* destinationBounds */); - } - }); + }); + } } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index 2e38449d4584..acb5622b041c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java @@ -278,8 +278,7 @@ public class PipTransition extends PipTransitionController implements } if (isRemovePipTransition(info)) { - mPipTransitionState.setState(PipTransitionState.EXITING_PIP); - return startRemoveAnimation(info, startTransaction, finishTransaction, finishCallback); + return removePipImmediately(info, startTransaction, finishTransaction, finishCallback); } return false; } @@ -669,18 +668,13 @@ public class PipTransition extends PipTransitionController implements return true; } - private boolean startRemoveAnimation(@NonNull TransitionInfo info, + private boolean removePipImmediately(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { - TransitionInfo.Change pipChange = getChangeByToken(info, - mPipTransitionState.getPipTaskToken()); - mFinishCallback = finishCallback; - PipAlphaAnimator animator = new PipAlphaAnimator(mContext, pipChange.getLeash(), - startTransaction, PipAlphaAnimator.FADE_OUT); - finishTransaction.setAlpha(pipChange.getLeash(), 0f); - animator.setAnimationEndCallback(this::finishTransition); - animator.start(); + startTransaction.apply(); + finishCallback.onTransitionFinished(null); + mPipTransitionState.setState(PipTransitionState.EXITED_PIP); return true; } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 4f37180baa37..e1c2153014fa 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -4160,8 +4160,7 @@ class DesktopTasksControllerTest : ShellTestCase() { screenOrientation = SCREEN_ORIENTATION_LANDSCAPE configuration.windowConfiguration.appBounds = bounds } - appCompatTaskInfo.topActivityLetterboxAppWidth = bounds.width() - appCompatTaskInfo.topActivityLetterboxAppHeight = bounds.height() + appCompatTaskInfo.topActivityAppBounds.set(0, 0, bounds.width(), bounds.height()) isResizeable = false } @@ -4879,15 +4878,19 @@ class DesktopTasksControllerTest : ShellTestCase() { appCompatTaskInfo.isSystemFullscreenOverrideEnabled = enableSystemFullscreenOverride if (deviceOrientation == ORIENTATION_LANDSCAPE) { - configuration.windowConfiguration.appBounds = - Rect(0, 0, DISPLAY_DIMENSION_LONG, DISPLAY_DIMENSION_SHORT) - appCompatTaskInfo.topActivityLetterboxAppWidth = DISPLAY_DIMENSION_LONG - appCompatTaskInfo.topActivityLetterboxAppHeight = DISPLAY_DIMENSION_SHORT + appCompatTaskInfo.topActivityAppBounds.set( + 0, + 0, + DISPLAY_DIMENSION_LONG, + DISPLAY_DIMENSION_SHORT, + ) } else { - configuration.windowConfiguration.appBounds = - Rect(0, 0, DISPLAY_DIMENSION_SHORT, DISPLAY_DIMENSION_LONG) - appCompatTaskInfo.topActivityLetterboxAppWidth = DISPLAY_DIMENSION_SHORT - appCompatTaskInfo.topActivityLetterboxAppHeight = DISPLAY_DIMENSION_LONG + appCompatTaskInfo.topActivityAppBounds.set( + 0, + 0, + DISPLAY_DIMENSION_SHORT, + DISPLAY_DIMENSION_LONG, + ) } if (shouldLetterbox) { @@ -4897,17 +4900,15 @@ class DesktopTasksControllerTest : ShellTestCase() { screenOrientation == SCREEN_ORIENTATION_PORTRAIT ) { // Letterbox to portrait size - appCompatTaskInfo.setTopActivityLetterboxed(true) - appCompatTaskInfo.topActivityLetterboxAppWidth = 1200 - appCompatTaskInfo.topActivityLetterboxAppHeight = 1600 + appCompatTaskInfo.isTopActivityLetterboxed = true + appCompatTaskInfo.topActivityAppBounds.set(0, 0, 1200, 1600) } else if ( deviceOrientation == ORIENTATION_PORTRAIT && screenOrientation == SCREEN_ORIENTATION_LANDSCAPE ) { // Letterbox to landscape size - appCompatTaskInfo.setTopActivityLetterboxed(true) - appCompatTaskInfo.topActivityLetterboxAppWidth = 1600 - appCompatTaskInfo.topActivityLetterboxAppHeight = 1200 + appCompatTaskInfo.isTopActivityLetterboxed = true + appCompatTaskInfo.topActivityAppBounds.set(0, 0, 1600, 1200) } } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java index a8aa25700c7e..3fe8c109807a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java @@ -120,22 +120,15 @@ public class PipSchedulerTest { @Test public void scheduleExitPipViaExpand_nullTaskToken_noop() { setNullPipTaskToken(); - when(mMockPipTransitionState.isInPip()).thenReturn(true); mPipScheduler.scheduleExitPipViaExpand(); - verify(mMockMainExecutor, times(1)).execute(mRunnableArgumentCaptor.capture()); - assertNotNull(mRunnableArgumentCaptor.getValue()); - mRunnableArgumentCaptor.getValue().run(); - - verify(mMockPipTransitionController, never()) - .startExitTransition(eq(TRANSIT_EXIT_PIP), any(), isNull()); + verify(mMockMainExecutor, never()).execute(any()); } @Test public void scheduleExitPipViaExpand_exitTransitionCalled() { setMockPipTaskToken(); - when(mMockPipTransitionState.isInPip()).thenReturn(true); mPipScheduler.scheduleExitPipViaExpand(); @@ -149,13 +142,20 @@ public class PipSchedulerTest { @Test public void removePipAfterAnimation() { + //TODO: Update once this is changed to run animation as part of transition setMockPipTaskToken(); - when(mMockPipTransitionState.isInPip()).thenReturn(true); - mPipScheduler.scheduleRemovePip(); + mPipScheduler.removePipAfterAnimation(); + verify(mMockAlphaAnimator, times(1)) + .setAnimationEndCallback(mRunnableArgumentCaptor.capture()); + assertNotNull(mRunnableArgumentCaptor.getValue()); + verify(mMockAlphaAnimator, times(1)).start(); + + mRunnableArgumentCaptor.getValue().run(); verify(mMockMainExecutor, times(1)).execute(mRunnableArgumentCaptor.capture()); assertNotNull(mRunnableArgumentCaptor.getValue()); + mRunnableArgumentCaptor.getValue().run(); verify(mMockPipTransitionController, times(1)) diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java index 1c4def39eaa0..e01cb84f60ae 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java @@ -16,6 +16,19 @@ package com.android.providers.settings; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_DEVICE_SPECIFIC_CONFIG; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_GLOBAL; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_LOCALE; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_LOCK_SETTINGS; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_NETWORK_POLICIES; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SECURE; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SIM_SPECIFIC_SETTINGS; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SIM_SPECIFIC_SETTINGS_2; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SOFTAP_CONFIG; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SYSTEM; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_WIFI_NEW_CONFIG; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_WIFI_SETTINGS_BACKUP_DATA; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -99,22 +112,6 @@ public class SettingsBackupAgent extends BackupAgentHelper { private static final int NULL_SIZE = -1; private static final float FONT_SCALE_DEF_VALUE = 1.0f; - private static final String KEY_SYSTEM = "system"; - private static final String KEY_SECURE = "secure"; - private static final String KEY_GLOBAL = "global"; - private static final String KEY_LOCALE = "locale"; - private static final String KEY_LOCK_SETTINGS = "lock_settings"; - private static final String KEY_SOFTAP_CONFIG = "softap_config"; - private static final String KEY_NETWORK_POLICIES = "network_policies"; - private static final String KEY_WIFI_NEW_CONFIG = "wifi_new_config"; - private static final String KEY_DEVICE_SPECIFIC_CONFIG = "device_specific_config"; - private static final String KEY_SIM_SPECIFIC_SETTINGS = "sim_specific_settings"; - // Restoring sim-specific data backed up from newer Android version to Android 12 was causing a - // fatal crash. Creating a backup with a different key will prevent Android 12 versions from - // restoring this data. - private static final String KEY_SIM_SPECIFIC_SETTINGS_2 = "sim_specific_settings_2"; - private static final String KEY_WIFI_SETTINGS_BACKUP_DATA = "wifi_settings_backup_data"; - // Versioning of the state file. Increment this version // number any time the set of state items is altered. private static final int STATE_VERSION = 9; @@ -257,6 +254,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE); if (com.android.server.backup.Flags.enableMetricsSettingsBackupAgents()) { mBackupRestoreEventLogger = this.getBackupRestoreEventLogger(); + mSettingsHelper.setBackupRestoreEventLogger(mBackupRestoreEventLogger); numberOfSettingsPerKey = new HashMap<>(); areAgentMetricsEnabled = true; } @@ -412,9 +410,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { mSettingsHelper .setLocaleData( localeData, - size, - mBackupRestoreEventLogger, - KEY_LOCALE); + size); break; case KEY_WIFI_CONFIG : @@ -552,8 +548,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { if (nBytes > buffer.length) buffer = new byte[nBytes]; in.readFully(buffer, 0, nBytes); mSettingsHelper - .setLocaleData( - buffer, nBytes, mBackupRestoreEventLogger, KEY_LOCALE); + .setLocaleData(buffer, nBytes); // Restore older backups performing the necessary migrations. if (version < FULL_BACKUP_ADDED_WIFI_NEW) { diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupRestoreKeys.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupRestoreKeys.java new file mode 100644 index 000000000000..745c2fb5409d --- /dev/null +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupRestoreKeys.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2019 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.providers.settings; + +import android.net.Uri; +import android.provider.Settings; + +/** + * Class to store the keys used for backup and restore. + */ +final class SettingsBackupRestoreKeys { + static final String KEY_UNKNOWN = "unknown"; + static final String KEY_SYSTEM = "system"; + static final String KEY_SECURE = "secure"; + static final String KEY_GLOBAL = "global"; + static final String KEY_LOCALE = "locale"; + static final String KEY_LOCK_SETTINGS = "lock_settings"; + static final String KEY_SOFTAP_CONFIG = "softap_config"; + static final String KEY_NETWORK_POLICIES = "network_policies"; + static final String KEY_WIFI_NEW_CONFIG = "wifi_new_config"; + static final String KEY_DEVICE_SPECIFIC_CONFIG = "device_specific_config"; + static final String KEY_SIM_SPECIFIC_SETTINGS = "sim_specific_settings"; + // Restoring sim-specific data backed up from newer Android version to Android 12 was causing a + // fatal crash. Creating a backup with a different key will prevent Android 12 versions from + // restoring this data. + static final String KEY_SIM_SPECIFIC_SETTINGS_2 = "sim_specific_settings_2"; + static final String KEY_WIFI_SETTINGS_BACKUP_DATA = "wifi_settings_backup_data"; + + /** + * Returns the key corresponding to the given URI. + * + * @param uri The URI of the setting's destination. + * @return The key corresponding to the given URI, or KEY_UNKNOWN if the URI is not recognized. + */ + static String getKeyFromUri(Uri uri) { + if (uri.equals(Settings.Secure.CONTENT_URI)) { + return KEY_SECURE; + } else if (uri.equals(Settings.System.CONTENT_URI)) { + return KEY_SYSTEM; + } else if (uri.equals(Settings.Global.CONTENT_URI)) { + return KEY_GLOBAL; + } else { + return KEY_UNKNOWN; + } + } + +}
\ No newline at end of file diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java index 924c151a99a0..ab8d739feb43 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java @@ -17,6 +17,7 @@ package com.android.providers.settings; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.IActivityManager; import android.app.backup.BackupRestoreEventLogger; @@ -84,6 +85,7 @@ public class SettingsHelper { private Context mContext; private AudioManager mAudioManager; private TelephonyManager mTelephonyManager; + @Nullable private BackupRestoreEventLogger mBackupRestoreEventLogger; /** * A few settings elements are special in that a restore of those values needs to @@ -741,11 +743,8 @@ public class SettingsHelper { * * @param data the comma separated BCP-47 language tags in bytes. * @param size the size of the data in bytes. - * @param backupRestoreEventLogger the logger to log the restore event. - * @param dataType the data type of the setting for logging purposes. */ - /* package */ void setLocaleData( - byte[] data, int size, BackupRestoreEventLogger backupRestoreEventLogger, String dataType) { + /* package */ void setLocaleData(byte[] data, int size) { final Configuration conf = mContext.getResources().getConfiguration(); // Replace "_" with "-" to deal with older backups. @@ -772,15 +771,15 @@ public class SettingsHelper { am.updatePersistentConfigurationWithAttribution(config, mContext.getOpPackageName(), mContext.getAttributionTag()); - if (Flags.enableMetricsSettingsBackupAgents()) { - backupRestoreEventLogger - .logItemsRestored(dataType, localeList.size()); + if (Flags.enableMetricsSettingsBackupAgents() && mBackupRestoreEventLogger != null) { + mBackupRestoreEventLogger + .logItemsRestored(SettingsBackupRestoreKeys.KEY_LOCALE, localeList.size()); } } catch (RemoteException e) { - if (Flags.enableMetricsSettingsBackupAgents()) { - backupRestoreEventLogger + if (Flags.enableMetricsSettingsBackupAgents() && mBackupRestoreEventLogger != null) { + mBackupRestoreEventLogger .logItemsRestoreFailed( - dataType, + SettingsBackupRestoreKeys.KEY_LOCALE, localeList.size(), ERROR_REMOTE_EXCEPTION_SETTING_LOCALE_DATA); } @@ -795,4 +794,13 @@ public class SettingsHelper { AudioManager am = new AudioManager(mContext); am.reloadAudioSettings(); } + + /** + * Sets the backup restore event logger. + * + * @param backupRestoreEventLogger the logger to log B&R metrics. + */ + void setBackupRestoreEventLogger(BackupRestoreEventLogger backupRestoreEventLogger) { + mBackupRestoreEventLogger = backupRestoreEventLogger; + } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 55f48e3e367f..f1f03c31f718 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -120,6 +120,7 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.accessibility.util.AccessibilityUtils; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageMonitor; import com.android.internal.display.RefreshRateSettingsUtils; import com.android.internal.os.BackgroundThread; @@ -2914,6 +2915,14 @@ public class SettingsProvider extends ContentProvider { }; } + @VisibleForTesting + void injectServices(UserManager userManager, IPackageManager packageManager, + SystemConfigManager sysConfigManager) { + mUserManager = userManager; + mPackageManager = packageManager; + mSysConfigManager = sysConfigManager; + } + private static final class Arguments { private static final Pattern WHERE_PATTERN_WITH_PARAM_NO_BRACKETS = Pattern.compile("[\\s]*name[\\s]*=[\\s]*\\?[\\s]*"); @@ -3080,6 +3089,7 @@ public class SettingsProvider extends ContentProvider { private static final String SSAID_USER_KEY = "userkey"; + @GuardedBy("mLock") private final SparseArray<SettingsState> mSettingsStates = new SparseArray<>(); private GenerationRegistry mGenerationRegistry; @@ -3992,6 +4002,14 @@ public class SettingsProvider extends ContentProvider { } } + @VisibleForTesting + void injectSettings(SettingsState settings, int type, int userId) { + int key = makeKey(type, userId); + synchronized (mLock) { + mSettingsStates.put(key, settings); + } + } + private final class MyHandler extends Handler { private static final int MSG_NOTIFY_URI_CHANGED = 1; private static final int MSG_NOTIFY_DATA_CHANGED = 2; @@ -4023,12 +4041,21 @@ public class SettingsProvider extends ContentProvider { } } - private final class UpgradeController { + @VisibleForTesting + final class UpgradeController { private static final int SETTINGS_VERSION = 226; private final int mUserId; + private final Injector mInjector; + public UpgradeController(int userId) { + this(/* injector= */ null, userId); + } + + @VisibleForTesting + UpgradeController(Injector injector, int userId) { + mInjector = injector == null ? new Injector() : injector; mUserId = userId; } @@ -6136,8 +6163,8 @@ public class SettingsProvider extends ContentProvider { systemSettings.getSettingLocked(Settings.System.PEAK_REFRESH_RATE); final Setting minRefreshRateSetting = systemSettings.getSettingLocked(Settings.System.MIN_REFRESH_RATE); - float highestRefreshRate = RefreshRateSettingsUtils - .findHighestRefreshRateForDefaultDisplay(getContext()); + float highestRefreshRate = + mInjector.findHighestRefreshRateForDefaultDisplay(getContext()); if (!peakRefreshRateSetting.isNull()) { try { @@ -6318,6 +6345,14 @@ public class SettingsProvider extends ContentProvider { private long getBitMask(int capability) { return 1 << (capability - 1); } + + @VisibleForTesting + static class Injector { + float findHighestRefreshRateForDefaultDisplay(Context context) { + return RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay( + context); + } + } } /** diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index 5cd534e62ea9..bf3afeda448e 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -107,7 +107,7 @@ import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon; * the same lock to grab the current state to write to disk. * </p> */ -final class SettingsState { +public class SettingsState { private static final boolean DEBUG = false; private static final boolean DEBUG_PERSISTENCE = false; @@ -1838,7 +1838,7 @@ final class SettingsState { } } - class Setting { + public class Setting { private String name; private String value; private String defaultValue; diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupRestoreKeysTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupRestoreKeysTest.java new file mode 100644 index 000000000000..ef537e8c7fc0 --- /dev/null +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupRestoreKeysTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2019 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.providers.settings; + +import static com.google.common.truth.Truth.assertThat; + +import android.net.Uri; +import android.provider.Settings; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for {@link SettingsBackupRestoreKeys}. + */ +@RunWith(AndroidJUnit4.class) +public class SettingsBackupRestoreKeysTest { + + @Test + public void getKeyFromUri_secureUri_returnsSecureKey() { + assertThat(SettingsBackupRestoreKeys.getKeyFromUri(Settings.Secure.CONTENT_URI)) + .isEqualTo(SettingsBackupRestoreKeys.KEY_SECURE); + } + + @Test + public void getKeyFromUri_systemUri_returnsSystemKey() { + assertThat(SettingsBackupRestoreKeys.getKeyFromUri(Settings.System.CONTENT_URI)) + .isEqualTo(SettingsBackupRestoreKeys.KEY_SYSTEM); + } + + @Test + public void getKeyFromUri_globalUri_returnsGlobalKey() { + assertThat(SettingsBackupRestoreKeys.getKeyFromUri(Settings.Global.CONTENT_URI)) + .isEqualTo(SettingsBackupRestoreKeys.KEY_GLOBAL); + } + + @Test + public void getKeyFromUri_unknownUri_returnsUnknownKey() { + assertThat(SettingsBackupRestoreKeys.getKeyFromUri(Uri.parse("content://unknown"))) + .isEqualTo(SettingsBackupRestoreKeys.KEY_UNKNOWN); + } +}
\ No newline at end of file diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/UpgradeControllerTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/UpgradeControllerTest.java new file mode 100644 index 000000000000..26ff376f828e --- /dev/null +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/UpgradeControllerTest.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2024 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.providers.settings; + +import static android.provider.Settings.System.MIN_REFRESH_RATE; +import static android.provider.Settings.System.PEAK_REFRESH_RATE; + +import static com.android.providers.settings.SettingsProvider.SETTINGS_TYPE_GLOBAL; +import static com.android.providers.settings.SettingsProvider.SETTINGS_TYPE_SECURE; +import static com.android.providers.settings.SettingsProvider.SETTINGS_TYPE_SYSTEM; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; +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 android.content.Context; +import android.content.ContextWrapper; +import android.content.pm.IPackageManager; +import android.os.Looper; +import android.os.SystemConfigManager; +import android.os.UserHandle; +import android.os.UserManager; + +import androidx.test.core.app.ApplicationProvider; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class UpgradeControllerTest { + private static final int USER_ID = UserHandle.USER_SYSTEM; + private static final float HIGHEST_REFRESH_RATE = 130f; + + private final Context mContext = + spy(new ContextWrapper(ApplicationProvider.getApplicationContext())); + private final SettingsProvider.SettingsRegistry.UpgradeController.Injector mInjector = + new SettingsProvider.SettingsRegistry.UpgradeController.Injector() { + @Override + float findHighestRefreshRateForDefaultDisplay(Context context) { + return HIGHEST_REFRESH_RATE; + } + }; + private final SettingsProvider mSettingsProvider = new SettingsProvider() { + @Override + public boolean onCreate() { + return true; + } + }; + private final SettingsProvider.SettingsRegistry mSettingsRegistry = + mSettingsProvider.new SettingsRegistry(Looper.getMainLooper()); + private final SettingsProvider.SettingsRegistry.UpgradeController mUpgradeController = + mSettingsRegistry.new UpgradeController(mInjector, USER_ID); + + @Mock + private UserManager mUserManager; + + @Mock + private IPackageManager mPackageManager; + + @Mock + private SystemConfigManager mSysConfigManager; + + @Mock + private SettingsState mSystemSettings; + + @Mock + private SettingsState mSecureSettings; + + @Mock + private SettingsState mGlobalSettings; + + @Mock + private SettingsState.Setting mMockSetting; + + @Mock + private SettingsState.Setting mPeakRefreshRateSetting; + + @Mock + private SettingsState.Setting mMinRefreshRateSetting; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mSettingsProvider.attachInfoForTesting(mContext, /* info= */ null); + mSettingsProvider.injectServices(mUserManager, mPackageManager, mSysConfigManager); + when(mSystemSettings.getSettingLocked(any())).thenReturn(mMockSetting); + when(mSecureSettings.getSettingLocked(any())).thenReturn(mMockSetting); + when(mGlobalSettings.getSettingLocked(any())).thenReturn(mMockSetting); + when(mMockSetting.isNull()).thenReturn(true); + when(mMockSetting.getValue()).thenReturn("0"); + + when(mSystemSettings.getSettingLocked(PEAK_REFRESH_RATE)) + .thenReturn(mPeakRefreshRateSetting); + when(mSystemSettings.getSettingLocked(MIN_REFRESH_RATE)) + .thenReturn(mMinRefreshRateSetting); + + mSettingsRegistry.injectSettings(mSystemSettings, SETTINGS_TYPE_SYSTEM, USER_ID); + mSettingsRegistry.injectSettings(mSecureSettings, SETTINGS_TYPE_SECURE, USER_ID); + mSettingsRegistry.injectSettings(mGlobalSettings, SETTINGS_TYPE_GLOBAL, USER_ID); + + // Lowest version so that all upgrades are run + when(mSecureSettings.getVersionLocked()).thenReturn(118); + } + + @Test + public void testUpgrade_refreshRateSettings_defaultValues() { + when(mPeakRefreshRateSetting.isNull()).thenReturn(true); + when(mMinRefreshRateSetting.isNull()).thenReturn(true); + + mUpgradeController.upgradeIfNeededLocked(); + + // Should remain unchanged + verify(mSystemSettings, never()).insertSettingLocked(eq(PEAK_REFRESH_RATE), + /* value= */ any(), /* tag= */ any(), /* makeDefault= */ anyBoolean(), + /* packageName= */ any()); + verify(mSystemSettings, never()).insertSettingLocked(eq(MIN_REFRESH_RATE), + /* value= */ any(), /* tag= */ any(), /* makeDefault= */ anyBoolean(), + /* packageName= */ any()); + } + + @Test + public void testUpgrade_refreshRateSettings_enabled() { + when(mPeakRefreshRateSetting.isNull()).thenReturn(false); + when(mMinRefreshRateSetting.isNull()).thenReturn(false); + when(mPeakRefreshRateSetting.getValue()).thenReturn(String.valueOf(HIGHEST_REFRESH_RATE)); + when(mMinRefreshRateSetting.getValue()).thenReturn(String.valueOf(HIGHEST_REFRESH_RATE)); + + mUpgradeController.upgradeIfNeededLocked(); + + // Highest refresh rate gets converted to infinity + verify(mSystemSettings).insertSettingLocked(eq(PEAK_REFRESH_RATE), + eq(String.valueOf(Float.POSITIVE_INFINITY)), /* tag= */ any(), + /* makeDefault= */ anyBoolean(), /* packageName= */ any()); + verify(mSystemSettings).insertSettingLocked(eq(MIN_REFRESH_RATE), + eq(String.valueOf(Float.POSITIVE_INFINITY)), /* tag= */ any(), + /* makeDefault= */ anyBoolean(), /* packageName= */ any()); + } + + @Test + public void testUpgrade_refreshRateSettings_disabled() { + when(mPeakRefreshRateSetting.isNull()).thenReturn(false); + when(mMinRefreshRateSetting.isNull()).thenReturn(false); + when(mPeakRefreshRateSetting.getValue()).thenReturn("70f"); + when(mMinRefreshRateSetting.getValue()).thenReturn("70f"); + + mUpgradeController.upgradeIfNeededLocked(); + + // Should remain unchanged + verify(mSystemSettings, never()).insertSettingLocked(eq(PEAK_REFRESH_RATE), + /* value= */ any(), /* tag= */ any(), /* makeDefault= */ anyBoolean(), + /* packageName= */ any()); + verify(mSystemSettings, never()).insertSettingLocked(eq(MIN_REFRESH_RATE), + /* value= */ any(), /* tag= */ any(), /* makeDefault= */ anyBoolean(), + /* packageName= */ any()); + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt index 8a5c96da5ac6..0438b811513e 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt @@ -180,7 +180,6 @@ object OverlayShade { object Dimensions { val PanelCornerRadius = 46.dp - val OverscrollLimit = 32.dp } object Shapes { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java index b88861756889..5c6657b98801 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java @@ -8,6 +8,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -231,7 +232,8 @@ public class InternetAdapterTest extends SysuiTestCase { mViewHolder.onWifiClick(mWifiEntry, mock(View.class)); - verify(mSpyContext).startActivity(any()); + verify(mInternetDialogController).startActivityForDialog(any()); + verify(mSpyContext, never()).startActivity(any()); } @Test diff --git a/packages/SystemUI/res/drawable/audio_bars_idle.xml b/packages/SystemUI/res/drawable/audio_bars_idle.xml new file mode 100644 index 000000000000..92a24755fece --- /dev/null +++ b/packages/SystemUI/res/drawable/audio_bars_idle.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2021 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="168dp" + android:height="168dp" + android:viewportWidth="168" + android:viewportHeight="168"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_2_G" + android:translateX="121.161" + android:translateY="83.911"> + <path + android:name="_R_G_L_2_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M-37.16 -5.87 C-33.94,-5.87 -31.32,-3.32 -31.2,-0.13 C-31.2,-0.06 -31.2,0.02 -31.2,0.09 C-31.2,0.16 -31.2,0.23 -31.2,0.29 C-31.31,3.49 -33.94,6.05 -37.16,6.05 C-40.39,6.05 -43.01,3.49 -43.12,0.29 C-43.12,0.23 -43.12,0.16 -43.12,0.09 C-43.12,0.01 -43.12,-0.07 -43.12,-0.15 C-42.99,-3.33 -40.37,-5.87 -37.16,-5.87c " /> + </group> + <group + android:name="_R_G_L_1_G" + android:translateX="102.911" + android:translateY="83.911"> + <path + android:name="_R_G_L_1_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M-37.16 -5.87 C-33.94,-5.87 -31.32,-3.32 -31.2,-0.13 C-31.2,-0.06 -31.2,0.02 -31.2,0.09 C-31.2,0.16 -31.2,0.23 -31.2,0.29 C-31.31,3.49 -33.94,6.05 -37.16,6.05 C-40.39,6.05 -43.01,3.49 -43.12,0.29 C-43.12,0.23 -43.12,0.16 -43.12,0.09 C-43.12,0.01 -43.12,-0.07 -43.12,-0.15 C-42.99,-3.33 -40.37,-5.87 -37.16,-5.87c " /> + </group> + <group + android:name="_R_G_L_0_G" + android:translateX="139.661" + android:translateY="83.911"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M-37.16 -5.87 C-33.94,-5.87 -31.32,-3.32 -31.2,-0.13 C-31.2,-0.06 -31.2,0.02 -31.2,0.09 C-31.2,0.16 -31.2,0.23 -31.2,0.29 C-31.31,3.49 -33.94,6.05 -37.16,6.05 C-40.39,6.05 -43.01,3.49 -43.12,0.29 C-43.12,0.23 -43.12,0.16 -43.12,0.09 C-43.12,0.01 -43.12,-0.07 -43.12,-0.15 C-42.99,-3.33 -40.37,-5.87 -37.16,-5.87c " /> + </group> + </group> + <group android:name="time_group" /> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml index a3bad8f012ac..5ccedeafcb59 100644 --- a/packages/SystemUI/res/layout/volume_dialog.xml +++ b/packages/SystemUI/res/layout/volume_dialog.xml @@ -56,8 +56,8 @@ android:layout_marginTop="@dimen/volume_dialog_components_spacing" android:background="@drawable/ripple_drawable_20dp" android:contentDescription="@string/accessibility_volume_settings" + android:scaleType="centerInside" android:soundEffectsEnabled="false" - android:src="@drawable/horizontal_ellipsis" android:tint="@androidprv:color/materialColorPrimary" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="@id/volume_dialog_main_slider_container" diff --git a/packages/SystemUI/res/raw/audio_bars_in.json b/packages/SystemUI/res/raw/audio_bars_in.json new file mode 100644 index 000000000000..c90a59c47d64 --- /dev/null +++ b/packages/SystemUI/res/raw/audio_bars_in.json @@ -0,0 +1 @@ +{"v":"5.7.13","fr":60,"ip":0,"op":18,"w":168,"h":168,"nm":"audio_bars_in","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 5","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":5,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[120.75,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"t":17,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-22.725],[5.957,-16.983],[5.961,0],[5.958,17.391],[0,23.149],[-5.958,17.39],[-5.961,0],[-5.957,-16.998]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":5,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[102.5,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"t":17,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-38.225],[5.957,-32.483],[5.961,0],[5.958,32.016],[0,37.774],[-5.958,32.015],[-5.961,0],[-5.957,-32.498]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[65.75,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"t":17,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-25.1],[5.957,-19.358],[5.961,0],[5.958,19.516],[0,25.274],[-5.958,19.515],[-5.961,0],[-5.957,-19.373]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[84,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"t":17,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-18.6],[5.957,-12.858],[5.961,0],[5.958,13.141],[0,18.899],[-5.958,13.14],[-5.961,0],[-5.957,-12.873]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":5,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[47.25,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"t":17,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-13.475],[5.957,-7.733],[5.961,0],[5.958,6.766],[0,12.524],[-5.958,6.765],[-5.961,0],[-5.957,-7.748]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":5,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file diff --git a/packages/SystemUI/res/raw/audio_bars_out.json b/packages/SystemUI/res/raw/audio_bars_out.json new file mode 100644 index 000000000000..5eab65e057ab --- /dev/null +++ b/packages/SystemUI/res/raw/audio_bars_out.json @@ -0,0 +1 @@ +{"v":"5.7.13","fr":60,"ip":0,"op":31,"w":168,"h":168,"nm":"audio_bars_out","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 5","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":5,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[120.75,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-22.725],[5.957,-16.983],[5.961,0],[5.958,17.391],[0,23.149],[-5.958,17.39],[-5.961,0],[-5.957,-16.998]],"c":true}]},{"t":30,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":5,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[102.5,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-38.225],[5.957,-32.483],[5.961,0],[5.958,32.016],[0,37.774],[-5.958,32.015],[-5.961,0],[-5.957,-32.498]],"c":true}]},{"t":30,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[65.75,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-25.1],[5.957,-19.358],[5.961,0],[5.958,19.516],[0,25.274],[-5.958,19.515],[-5.961,0],[-5.957,-19.373]],"c":true}]},{"t":30,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[84,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-18.6],[5.957,-12.858],[5.961,0],[5.958,13.141],[0,18.899],[-5.958,13.14],[-5.961,0],[-5.957,-12.873]],"c":true}]},{"t":30,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":5,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[47.25,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-13.475],[5.957,-7.733],[5.961,0],[5.958,6.766],[0,12.524],[-5.958,6.765],[-5.961,0],[-5.957,-7.748]],"c":true}]},{"t":30,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":5,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file diff --git a/packages/SystemUI/res/raw/audio_bars_playing.json b/packages/SystemUI/res/raw/audio_bars_playing.json new file mode 100644 index 000000000000..6ee8e1915f36 --- /dev/null +++ b/packages/SystemUI/res/raw/audio_bars_playing.json @@ -0,0 +1 @@ +{"v":"5.7.13","fr":60,"ip":0,"op":121,"w":168,"h":168,"nm":"audio_bars_playing","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[120.75,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-22.725],[5.957,-16.983],[5.961,0],[5.958,17.391],[0,23.149],[-5.958,17.39],[-5.961,0],[-5.957,-16.998]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":38,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[-0.016,-14.1],[5.941,-8.358],[5.961,0],[5.958,8.516],[0,14.274],[-5.958,8.515],[-5.961,0],[-5.972,-8.373]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":70,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-22.725],[5.957,-16.983],[5.961,0],[5.958,17.391],[0,23.149],[-5.958,17.39],[-5.961,0],[-5.957,-16.998]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":102,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[-0.016,-14.1],[5.941,-8.358],[5.961,0],[5.958,8.516],[0,14.274],[-5.958,8.515],[-5.961,0],[-5.972,-8.373]],"c":true}]},{"t":120,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-22.725],[5.957,-16.983],[5.961,0],[5.958,17.391],[0,23.149],[-5.958,17.39],[-5.961,0],[-5.957,-16.998]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":121,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[102.5,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-38.225],[5.957,-32.483],[5.961,0],[5.958,32.016],[0,37.774],[-5.958,32.015],[-5.961,0],[-5.957,-32.498]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":32,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-19.1],[5.957,-13.358],[5.961,0],[5.958,13.641],[0,19.399],[-5.958,13.64],[-5.961,0],[-5.957,-13.373]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":65,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-38.225],[5.957,-32.483],[5.961,0],[5.958,32.016],[0,37.774],[-5.958,32.015],[-5.961,0],[-5.957,-32.498]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":97,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-19.1],[5.957,-13.358],[5.961,0],[5.958,13.641],[0,19.399],[-5.958,13.64],[-5.961,0],[-5.957,-13.373]],"c":true}]},{"t":120,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-38.225],[5.957,-32.483],[5.961,0],[5.958,32.016],[0,37.774],[-5.958,32.015],[-5.961,0],[-5.957,-32.498]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":121,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[65.75,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-25.1],[5.957,-19.358],[5.961,0],[5.958,19.516],[0,25.274],[-5.958,19.515],[-5.961,0],[-5.957,-19.373]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":29,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-15.85],[5.957,-10.108],[5.961,0],[5.958,10.516],[0,16.274],[-5.958,10.515],[-5.961,0],[-5.957,-10.123]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":59,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-25.1],[5.957,-19.358],[5.961,0],[5.958,19.516],[0,25.274],[-5.958,19.515],[-5.961,0],[-5.957,-19.373]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":91,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-15.85],[5.957,-10.108],[5.961,0],[5.958,10.516],[0,16.274],[-5.958,10.515],[-5.961,0],[-5.957,-10.123]],"c":true}]},{"t":120,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-25.1],[5.957,-19.358],[5.961,0],[5.958,19.516],[0,25.274],[-5.958,19.515],[-5.961,0],[-5.957,-19.373]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":121,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[84,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-18.6],[5.957,-12.858],[5.961,0],[5.958,13.141],[0,18.899],[-5.958,13.14],[-5.961,0],[-5.957,-12.873]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":24,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-9.225],[5.957,-3.483],[5.961,0],[5.958,3.766],[0,9.524],[-5.958,3.765],[-5.961,0],[-5.957,-3.498]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":54,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-18.6],[5.957,-12.858],[5.961,0],[5.958,13.141],[0,18.899],[-5.958,13.14],[-5.961,0],[-5.957,-12.873]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":86,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-9.225],[5.957,-3.483],[5.961,0],[5.958,3.766],[0,9.524],[-5.958,3.765],[-5.961,0],[-5.957,-3.498]],"c":true}]},{"t":120,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-18.6],[5.957,-12.858],[5.961,0],[5.958,13.141],[0,18.899],[-5.958,13.14],[-5.961,0],[-5.957,-12.873]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":121,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[47.25,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-13.475],[5.957,-7.733],[5.961,0],[5.958,6.766],[0,12.524],[-5.958,6.765],[-5.961,0],[-5.957,-7.748]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":19,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":48,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-13.475],[5.957,-7.733],[5.961,0],[5.958,6.766],[0,12.524],[-5.958,6.765],[-5.961,0],[-5.957,-7.748]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":81,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"t":120,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-13.475],[5.957,-7.733],[5.961,0],[5.958,6.766],[0,12.524],[-5.958,6.765],[-5.961,0],[-5.957,-7.748]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":121,"st":0,"bm":0}],"markers":[{"tm":60,"cm":"1","dr":0}]}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/lottie/LottieTaskExt.kt b/packages/SystemUI/src/com/android/systemui/lottie/LottieTaskExt.kt new file mode 100644 index 000000000000..dd2525f5ca45 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/lottie/LottieTaskExt.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 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.lottie + +import com.airbnb.lottie.LottieComposition +import com.airbnb.lottie.LottieListener +import com.airbnb.lottie.LottieTask +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlinx.coroutines.suspendCancellableCoroutine + +/** + * Suspends until [LottieTask] is finished with a result or a failure. + * + * @return result of the [LottieTask] when it's successful + */ +suspend fun LottieTask<LottieComposition>.await() = + suspendCancellableCoroutine<LottieComposition> { continuation -> + val resultListener = + LottieListener<LottieComposition> { result -> + with(continuation) { if (!isCancelled && !isCompleted) resume(result) } + } + val failureListener = + LottieListener<Throwable> { throwable -> + with(continuation) { + if (!isCancelled && !isCompleted) resumeWithException(throwable) + } + } + addListener(resultListener) + addFailureListener(failureListener) + continuation.invokeOnCancellation { + removeListener(resultListener) + removeFailureListener(failureListener) + } + } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java index 19b45d50c594..7516ca030d4b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java @@ -193,7 +193,7 @@ public class InternetAdapter extends RecyclerView.Adapter<InternetAdapter.Intern if (mJob == null) { mJob = WifiUtils.checkWepAllowed(mContext, mCoroutineScope, wifiEntry.getSsid(), WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG, intent -> { - mInternetDialogController.startActivity(intent, view); + mInternetDialogController.startActivityForDialog(intent); return null; }, () -> { wifiConnect(wifiEntry, view); @@ -211,7 +211,7 @@ public class InternetAdapter extends RecyclerView.Adapter<InternetAdapter.Intern true /* connectForCaller */); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); - mContext.startActivity(intent); + mInternetDialogController.startActivityForDialog(intent); return; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java index dbe1ae90b3f6..7036ef914a1c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java @@ -781,6 +781,10 @@ public class InternetDialogController implements AccessPointController.AccessPoi mActivityStarter.postStartActivityDismissingKeyguard(intent, 0, controller); } + void startActivityForDialog(Intent intent) { + mActivityStarter.startActivity(intent, false /* dismissShade */); + } + void launchNetworkSetting(View view) { startActivity(getSettingsIntent(), view); } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt index 2e1f82d56fc4..70e342f3eefb 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt @@ -17,45 +17,28 @@ package com.android.systemui.volume.dialog.settings.ui.binder import android.view.View -import com.android.systemui.lifecycle.WindowLifecycleState -import com.android.systemui.lifecycle.repeatWhenAttached -import com.android.systemui.lifecycle.setSnapshotBinding -import com.android.systemui.lifecycle.viewModel +import android.widget.ImageButton import com.android.systemui.res.R import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope import com.android.systemui.volume.dialog.settings.ui.viewmodel.VolumeDialogSettingsButtonViewModel import javax.inject.Inject -import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @VolumeDialogScope class VolumeDialogSettingsButtonViewBinder @Inject -constructor(private val viewModelFactory: VolumeDialogSettingsButtonViewModel.Factory) { +constructor(private val viewModel: VolumeDialogSettingsButtonViewModel) { - fun bind(view: View) { - with(view) { - val button = requireViewById<View>(R.id.volume_dialog_settings) - repeatWhenAttached { - viewModel( - traceName = "VolumeDialogViewBinder", - minWindowLifecycleState = WindowLifecycleState.ATTACHED, - factory = { viewModelFactory.create() }, - ) { viewModel -> - setSnapshotBinding { - viewModel.isVisible - .onEach { isVisible -> - visibility = if (isVisible) View.VISIBLE else View.GONE - } - .launchIn(this) + fun CoroutineScope.bind(view: View) { + val button = view.requireViewById<ImageButton>(R.id.volume_dialog_settings) + viewModel.isVisible + .onEach { isVisible -> button.visibility = if (isVisible) View.VISIBLE else View.GONE } + .launchIn(this) - button.setOnClickListener { viewModel.onButtonClicked() } - } + viewModel.icon.onEach { button.setImageDrawable(it) }.launchIn(this) - awaitCancellation() - } - } - } + button.setOnClickListener { viewModel.onButtonClicked() } } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/viewmodel/VolumeDialogSettingsButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/viewmodel/VolumeDialogSettingsButtonViewModel.kt index 015d773b2c02..03442dbcde66 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/viewmodel/VolumeDialogSettingsButtonViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/viewmodel/VolumeDialogSettingsButtonViewModel.kt @@ -14,27 +14,206 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.volume.dialog.settings.ui.viewmodel -import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.ColorFilter +import android.graphics.drawable.Drawable +import android.media.session.PlaybackState +import androidx.annotation.ColorInt +import com.airbnb.lottie.LottieComposition +import com.airbnb.lottie.LottieCompositionFactory +import com.airbnb.lottie.LottieDrawable +import com.airbnb.lottie.LottieProperty +import com.airbnb.lottie.SimpleColorFilter +import com.airbnb.lottie.model.KeyPath +import com.airbnb.lottie.value.LottieValueCallback +import com.android.internal.R as internalR +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.UiBackground +import com.android.systemui.lottie.await +import com.android.systemui.res.R +import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog import com.android.systemui.volume.dialog.settings.domain.VolumeDialogSettingsButtonInteractor -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject +import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor +import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor +import com.android.systemui.volume.panel.shared.model.filterData +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.resume +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.FlowCollector +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.buffer +import kotlinx.coroutines.flow.distinctUntilChangedBy +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.runningFold +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.transform +import kotlinx.coroutines.suspendCancellableCoroutine class VolumeDialogSettingsButtonViewModel -@AssistedInject -constructor(private val interactor: VolumeDialogSettingsButtonInteractor) { +@Inject +constructor( + @Application private val context: Context, + @UiBackground private val uiBgCoroutineContext: CoroutineContext, + @VolumeDialog private val coroutineScope: CoroutineScope, + mediaOutputInteractor: MediaOutputInteractor, + private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor, + private val interactor: VolumeDialogSettingsButtonInteractor, +) { + + @SuppressLint("UseCompatLoadingForDrawables") + private val drawables: Flow<Drawables> = + flow { + val color = context.getColor(internalR.color.materialColorPrimary) + emit( + Drawables( + start = + LottieCompositionFactory.fromRawRes(context, R.raw.audio_bars_in) + .await() + .toDrawable { setColor(color) }, + playing = + LottieCompositionFactory.fromRawRes(context, R.raw.audio_bars_playing) + .await() + .toDrawable { + repeatCount = LottieDrawable.INFINITE + repeatMode = LottieDrawable.RESTART + setColor(color) + }, + stop = + LottieCompositionFactory.fromRawRes(context, R.raw.audio_bars_out) + .await() + .toDrawable { setColor(color) }, + idle = context.getDrawable(R.drawable.audio_bars_idle)!!, + ) + ) + } + .buffer() + .flowOn(uiBgCoroutineContext) + .stateIn(coroutineScope, SharingStarted.Eagerly, null) + .filterNotNull() val isVisible = interactor.isVisible + val icon: Flow<Drawable> = + mediaOutputInteractor.defaultActiveMediaSession + .filterData() + .flatMapLatest { session -> + if (session == null) { + flowOf(null) + } else { + mediaDeviceSessionInteractor.playbackState(session) + } + } + .runningFold(null) { playbackStates: PlaybackStates?, playbackState: PlaybackState? -> + val isCurrentActive = playbackState?.isActive ?: false + if (playbackStates != null && isCurrentActive == playbackState?.isActive) { + return@runningFold playbackStates + } + playbackStates?.copy( + isPreviousActive = playbackStates.isCurrentActive, + isCurrentActive = isCurrentActive, + ) ?: PlaybackStates(isPreviousActive = null, isCurrentActive = isCurrentActive) + } + .filterNotNull() + // only apply the most recent state if we wait for the animation. + .buffer(capacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) + // distinct again because the changed state might've been dropped by the buffer + .distinctUntilChangedBy { it.isCurrentActive } + .transform { emitDrawables(it) } + .runningFold(null) { previous: Drawable?, current: Drawable -> + // wait for the previous animation to finish before starting the new one + // this also waits for the current loop of the playing animation to finish + (previous as? LottieDrawable)?.awaitFinish() + (current as? LottieDrawable)?.start() + current + } + .filterNotNull() + + private suspend fun FlowCollector<Drawable>.emitDrawables(playbackStates: PlaybackStates) { + val animations = drawables.first() + val stateChanged = + playbackStates.isPreviousActive != null && + playbackStates.isPreviousActive != playbackStates.isCurrentActive + if (playbackStates.isCurrentActive) { + if (stateChanged) { + emit(animations.start) + } + emit(animations.playing) + } else { + if (stateChanged) { + emit(animations.stop) + } + emit(animations.idle) + } + } fun onButtonClicked() { interactor.onButtonClicked() } - @VolumeDialogScope - @AssistedFactory - interface Factory { + private data class PlaybackStates(val isPreviousActive: Boolean?, val isCurrentActive: Boolean) + + private data class Drawables( + val start: LottieDrawable, + val playing: LottieDrawable, + val stop: LottieDrawable, + val idle: Drawable, + ) +} + +private fun LottieComposition.toDrawable(setup: LottieDrawable.() -> Unit = {}): LottieDrawable = + LottieDrawable().also { drawable -> + drawable.composition = this + drawable.setup() + } - fun create(): VolumeDialogSettingsButtonViewModel +/** Suspends until current loop of the repeating animation is finished */ +private suspend fun LottieDrawable.awaitFinish() = suspendCancellableCoroutine { continuation -> + if (!isRunning) { + continuation.resume(Unit) + return@suspendCancellableCoroutine } + val listener = + object : AnimatorListenerAdapter() { + override fun onAnimationRepeat(animation: Animator) { + continuation.resume(Unit) + removeAnimatorListener(this) + } + + override fun onAnimationEnd(animation: Animator) { + continuation.resume(Unit) + removeAnimatorListener(this) + } + + override fun onAnimationCancel(animation: Animator) { + continuation.resume(Unit) + removeAnimatorListener(this) + } + } + addAnimatorListener(listener) + continuation.invokeOnCancellation { removeAnimatorListener(listener) } +} + +/** + * Overrides colors of the [LottieDrawable] to a specified [color] + * + * @see com.airbnb.lottie.LottieAnimationView + */ +private fun LottieDrawable.setColor(@ColorInt color: Int) { + val callback = LottieValueCallback<ColorFilter>(SimpleColorFilter(color)) + addValueCallback(KeyPath("**"), LottieProperty.COLOR_FILTER, callback) } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt index f30524638150..faf06b942cab 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt @@ -26,7 +26,7 @@ import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderStateModel import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel import com.android.systemui.volume.dialog.ui.utils.JankListenerFactory -import com.android.systemui.volume.dialog.ui.utils.awaitAnimation +import com.android.systemui.volume.dialog.ui.utils.suspendAnimate import com.google.android.material.slider.LabelFormatter import com.google.android.material.slider.Slider import javax.inject.Inject @@ -84,5 +84,5 @@ private suspend fun Slider.setValueAnimated( interpolator = DecelerateInterpolator() addListener(jankListener) } - .awaitAnimation<Float> { value = it } + .suspendAnimate<Float> { value = it } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt index 10cf615ce0ce..5f124806dac7 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt @@ -22,6 +22,7 @@ import android.animation.ValueAnimator import android.view.ViewPropertyAnimator import androidx.dynamicanimation.animation.DynamicAnimation import androidx.dynamicanimation.animation.SpringAnimation +import com.airbnb.lottie.LottieDrawable import kotlin.coroutines.resume import kotlinx.coroutines.CancellableContinuation import kotlinx.coroutines.suspendCancellableCoroutine @@ -66,7 +67,7 @@ suspend fun ViewPropertyAnimator.suspendAnimate( * is cancelled. */ @Suppress("UNCHECKED_CAST") -suspend fun <T> ValueAnimator.awaitAnimation(onValueChanged: (T) -> Unit) { +suspend fun <T> ValueAnimator.suspendAnimate(onValueChanged: (T) -> Unit) { suspendCancellableCoroutine { continuation -> addListener( object : AnimatorListenerAdapter() { @@ -103,6 +104,29 @@ suspend fun SpringAnimation.suspendAnimate( } } +/** + * Starts the animation and suspends until it's finished. Cancels the animation if the running + * coroutine is cancelled. + */ +suspend fun LottieDrawable.suspendAnimate() = suspendCancellableCoroutine { continuation -> + val listener = + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + continuation.resumeIfCan(Unit) + } + + override fun onAnimationCancel(animation: Animator) { + continuation.resumeIfCan(Unit) + } + } + addAnimatorListener(listener) + start() + continuation.invokeOnCancellation { + removeAnimatorListener(listener) + stop() + } +} + private fun <T> CancellableContinuation<T>.resumeIfCan(value: T) { if (!isCancelled && !isCompleted) { resume(value) diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt index 6e1ebc820b08..12e624cae4d4 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt @@ -19,10 +19,10 @@ package com.android.systemui.volume.panel.component.mediaoutput.domain.interacto import android.media.session.MediaController import android.media.session.PlaybackState import com.android.settingslib.volume.data.repository.MediaControllerRepository +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaControllerChangeModel import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession -import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope import javax.inject.Inject import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -38,7 +38,7 @@ import kotlinx.coroutines.withContext /** Allows to observe and change [MediaDeviceSession] state. */ @OptIn(ExperimentalCoroutinesApi::class) -@VolumePanelScope +@SysUISingleton class MediaDeviceSessionInteractor @Inject constructor( diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt index b3848a6d7817..2973e11c365d 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt @@ -24,12 +24,13 @@ import androidx.annotation.WorkerThread import com.android.settingslib.media.MediaDevice import com.android.settingslib.volume.data.repository.LocalMediaRepository import com.android.settingslib.volume.data.repository.MediaControllerRepository +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.util.concurrency.Execution import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSessions import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession -import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope import com.android.systemui.volume.panel.shared.model.Result import com.android.systemui.volume.panel.shared.model.filterData import com.android.systemui.volume.panel.shared.model.wrapInResult @@ -54,13 +55,13 @@ import kotlinx.coroutines.withContext /** Provides observable models about the current media session state. */ @OptIn(ExperimentalCoroutinesApi::class) -@VolumePanelScope +@SysUISingleton class MediaOutputInteractor @Inject constructor( private val localMediaRepositoryFactory: LocalMediaRepositoryFactory, private val packageManager: PackageManager, - @VolumePanelScope private val coroutineScope: CoroutineScope, + @Application private val coroutineScope: CoroutineScope, @Background private val backgroundCoroutineContext: CoroutineContext, mediaControllerRepository: MediaControllerRepository, private val mediaControllerInteractor: MediaControllerInteractor, @@ -77,7 +78,7 @@ constructor( .onStart { emit(activeSessions) } } .map { getMediaControllers(it) } - .stateIn(coroutineScope, SharingStarted.Eagerly, MediaControllers(null, null)) + .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), MediaControllers(null, null)) /** [MediaDeviceSessions] that contains currently active sessions. */ val activeMediaDeviceSessions: Flow<MediaDeviceSessions> = @@ -89,7 +90,11 @@ constructor( ) } .flowOn(backgroundCoroutineContext) - .stateIn(coroutineScope, SharingStarted.Eagerly, MediaDeviceSessions(null, null)) + .stateIn( + coroutineScope, + SharingStarted.WhileSubscribed(), + MediaDeviceSessions(null, null), + ) /** Returns the default [MediaDeviceSession] from [activeMediaDeviceSessions] */ val defaultActiveMediaSession: StateFlow<Result<MediaDeviceSession?>> = @@ -104,7 +109,7 @@ constructor( } .wrapInResult() .flowOn(backgroundCoroutineContext) - .stateIn(coroutineScope, SharingStarted.Eagerly, Result.Loading()) + .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), Result.Loading()) private val localMediaRepository: Flow<LocalMediaRepository> = defaultActiveMediaSession diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java index b26f0a6e71a3..782b24825bcf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java @@ -557,6 +557,13 @@ public class InternetDialogDelegateControllerTest extends SysuiTestCase { } @Test + public void startActivityForDialog_always_startActivityWithoutDismissShade() { + mInternetDialogController.startActivityForDialog(mock(Intent.class)); + + verify(mActivityStarter).startActivity(any(Intent.class), eq(false) /* dismissShade */); + } + + @Test public void launchWifiDetailsSetting_withNoWifiEntryKey_doNothing() { mInternetDialogController.launchWifiDetailsSetting(null /* key */, mDialogLaunchView); diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index 87f87c76725e..c82933c5069e 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -158,6 +158,7 @@ public class SettingsToPropertiesMapper { "aoc", "app_widgets", "arc_next", + "art_cloud", "art_mainline", "art_performance", "attack_tools", diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 2985ad330bc6..5740e16dc886 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -2400,6 +2400,10 @@ public final class DisplayManagerService extends SystemService { sendDisplayEventIfEnabledLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED); applyDisplayChangedLocked(display); + + if (mDisplayTopologyCoordinator != null) { + mDisplayTopologyCoordinator.onDisplayChanged(display.getDisplayInfoLocked()); + } } private void applyDisplayChangedLocked(@NonNull LogicalDisplay display) { diff --git a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java index 5b78726cc421..461a9f3f2a0d 100644 --- a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java +++ b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java @@ -85,13 +85,26 @@ class DisplayTopologyCoordinator { } /** + * Update the topology with display changes. + * @param info The new display info + */ + void onDisplayChanged(DisplayInfo info) { + synchronized (mSyncRoot) { + if (mTopology.updateDisplay(info.displayId, getWidth(info), getHeight(info))) { + sendTopologyUpdateLocked(); + } + } + } + + /** * Remove a display from the topology. * @param displayId The logical display ID */ void onDisplayRemoved(int displayId) { synchronized (mSyncRoot) { - mTopology.removeDisplay(displayId); - sendTopologyUpdateLocked(); + if (mTopology.removeDisplay(displayId)) { + sendTopologyUpdateLocked(); + } } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index bf57f56df7c2..0aff1de72cb1 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -77,7 +77,6 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLAS import static com.android.server.wm.ActivityTaskManagerService.ANIMATE; import static com.android.server.wm.ActivityTaskManagerService.H.FIRST_SUPERVISOR_TASK_MSG; import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE; -import static com.android.server.wm.ActivityTaskManagerService.isPip2ExperimentEnabled; import static com.android.server.wm.ClientLifecycleManager.shouldDispatchLaunchActivityItemIndependently; import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_ALLOWLISTED; import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE; @@ -2526,7 +2525,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { void scheduleUpdatePictureInPictureModeIfNeeded(Task task, Rect targetRootTaskBounds) { task.forAllActivities(r -> { if (!r.attachedToProcess()) return; - if (!isPip2ExperimentEnabled()) mPipModeChangedActivities.add(r); + mPipModeChangedActivities.add(r); // If we are scheduling pip change, then remove this activity from multi-window // change list as the processing of pip change will make sure multi-window changed // message is processed in the right order relative to pip changed. @@ -2535,7 +2534,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { mPipModeChangedTargetRootTaskBounds = targetRootTaskBounds; - if (!isPip2ExperimentEnabled() && !mHandler.hasMessages(REPORT_PIP_MODE_CHANGED_MSG)) { + if (!mHandler.hasMessages(REPORT_PIP_MODE_CHANGED_MSG)) { mHandler.sendEmptyMessage(REPORT_PIP_MODE_CHANGED_MSG); } } diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java index 0369a0ff4c76..9f88bc952351 100644 --- a/services/core/java/com/android/server/wm/AppCompatUtils.java +++ b/services/core/java/com/android/server/wm/AppCompatUtils.java @@ -164,15 +164,13 @@ final class AppCompatUtils { appCompatTaskInfo.setIsFromLetterboxDoubleTap(reachabilityOverrides.isFromDoubleTap()); + appCompatTaskInfo.topActivityAppBounds.set(getAppBounds(top)); final boolean isTopActivityLetterboxed = top.areBoundsLetterboxed(); appCompatTaskInfo.setTopActivityLetterboxed(isTopActivityLetterboxed); if (isTopActivityLetterboxed) { final Rect bounds = top.getBounds(); - final Rect appBounds = getAppBounds(top); appCompatTaskInfo.topActivityLetterboxWidth = bounds.width(); appCompatTaskInfo.topActivityLetterboxHeight = bounds.height(); - appCompatTaskInfo.topActivityLetterboxAppWidth = appBounds.width(); - appCompatTaskInfo.topActivityLetterboxAppHeight = appBounds.height(); // TODO(b/379824541) Remove duplicate information. appCompatTaskInfo.topActivityLetterboxBounds = bounds; // We need to consider if letterboxed or pillarboxed. @@ -281,8 +279,7 @@ final class AppCompatUtils { info.topActivityLetterboxHorizontalPosition = TaskInfo.PROPERTY_VALUE_UNSET; info.topActivityLetterboxWidth = TaskInfo.PROPERTY_VALUE_UNSET; info.topActivityLetterboxHeight = TaskInfo.PROPERTY_VALUE_UNSET; - info.topActivityLetterboxAppHeight = TaskInfo.PROPERTY_VALUE_UNSET; - info.topActivityLetterboxAppWidth = TaskInfo.PROPERTY_VALUE_UNSET; + info.topActivityAppBounds.setEmpty(); info.topActivityLetterboxBounds = null; info.cameraCompatTaskInfo.freeformCameraCompatMode = CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_UNSPECIFIED; diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 9d9c53dfe0f4..db62cebf7603 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2945,7 +2945,7 @@ public class WindowManagerService extends IWindowManager.Stub final DisplayContent dc = mRoot.getDisplayContent(displayId); if (dc == null) { if (callingPid != MY_PID) { - throw new WindowManager.InvalidDisplayException( + throw new IllegalArgumentException( "attachWindowContextToDisplayContent: trying to attach to a" + " non-existing display:" + displayId); } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index e45ada9438ae..fb197c566b7d 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -80,7 +80,6 @@ import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WINDOW_ORG import static com.android.server.wm.ActivityRecord.State.PAUSING; import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission; -import static com.android.server.wm.ActivityTaskManagerService.isPip2ExperimentEnabled; import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS; import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK; import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG; @@ -717,8 +716,6 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } if (forceHiddenForPip) { wc.asTask().setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, true /* set */); - } - if (forceHiddenForPip && !isPip2ExperimentEnabled()) { // When removing pip, make sure that onStop is sent to the app ahead of // onPictureInPictureModeChanged. // See also PinnedStackTests#testStopBeforeMultiWindowCallbacksOnDismiss diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt index 5d427139a857..c65024f8f9d5 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt @@ -26,6 +26,7 @@ import org.junit.Test import org.mockito.ArgumentMatchers.anyFloat import org.mockito.ArgumentMatchers.anyInt import org.mockito.kotlin.any +import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify @@ -43,7 +44,7 @@ class DisplayTopologyCoordinatorTest { @Before fun setUp() { - displayInfo.displayId = 2 + displayInfo.displayId = Display.DEFAULT_DISPLAY displayInfo.logicalWidth = 300 displayInfo.logicalHeight = 200 displayInfo.logicalDensityDpi = 100 @@ -90,6 +91,44 @@ class DisplayTopologyCoordinatorTest { } @Test + fun updateDisplay() { + whenever(mockTopology.updateDisplay(eq(Display.DEFAULT_DISPLAY), anyFloat(), anyFloat())) + .thenReturn(true) + + coordinator.onDisplayChanged(displayInfo) + + verify(mockTopologyChangedCallback).invoke(mockTopologyCopy) + } + + @Test + fun updateDisplay_notChanged() { + whenever(mockTopology.updateDisplay(eq(Display.DEFAULT_DISPLAY), anyFloat(), anyFloat())) + .thenReturn(false) + + coordinator.onDisplayChanged(displayInfo) + + verify(mockTopologyChangedCallback, never()).invoke(any()) + } + + @Test + fun removeDisplay() { + whenever(mockTopology.removeDisplay(Display.DEFAULT_DISPLAY)).thenReturn(true) + + coordinator.onDisplayRemoved(Display.DEFAULT_DISPLAY) + + verify(mockTopologyChangedCallback).invoke(mockTopologyCopy) + } + + @Test + fun removeDisplay_notChanged() { + whenever(mockTopology.removeDisplay(Display.DEFAULT_DISPLAY)).thenReturn(false) + + coordinator.onDisplayRemoved(Display.DEFAULT_DISPLAY) + + verify(mockTopologyChangedCallback, never()).invoke(any()) + } + + @Test fun getTopology_copy() { assertThat(coordinator.topology).isEqualTo(mockTopologyCopy) } |