Merge "[Inline Reply] Cover heads up and pinned in isExpanded" into main
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 9e2872f..1d40c53 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -105,6 +105,7 @@
field public static final String CAMERA_OPEN_CLOSE_LISTENER = "android.permission.CAMERA_OPEN_CLOSE_LISTENER";
field @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") public static final String CAMERA_PRIVACY_ALLOWLIST = "android.permission.CAMERA_PRIVACY_ALLOWLIST";
field public static final String CAPTURE_AUDIO_HOTWORD = "android.permission.CAPTURE_AUDIO_HOTWORD";
+ field @FlaggedApi("android.os.allow_consentless_bugreport_delegated_consent") public static final String CAPTURE_CONSENTLESS_BUGREPORT_DELEGATED_CONSENT = "android.permission.CAPTURE_CONSENTLESS_BUGREPORT_DELEGATED_CONSENT";
field public static final String CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD = "android.permission.CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD";
field public static final String CAPTURE_MEDIA_OUTPUT = "android.permission.CAPTURE_MEDIA_OUTPUT";
field public static final String CAPTURE_TUNER_AUDIO_INPUT = "android.permission.CAPTURE_TUNER_AUDIO_INPUT";
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 497d47a..87acbbf 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -385,7 +385,7 @@
/**
* @hide
*/
- public static final int CACHE_USER_DATA_SIZE = 4;
+ public static final int CACHE_USER_DATA_SIZE = 32;
private static final class AccountKeyData {
final public Account account;
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 5956e2b..80764af 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1128,7 +1128,7 @@
private static final PropertyInvalidatedCache<Integer, GetPackagesForUidResult>
mGetPackagesForUidCache =
new PropertyInvalidatedCache<Integer, GetPackagesForUidResult>(
- 32, CACHE_KEY_PACKAGES_FOR_UID_PROPERTY) {
+ 1024, CACHE_KEY_PACKAGES_FOR_UID_PROPERTY) {
@Override
public GetPackagesForUidResult recompute(Integer uid) {
try {
diff --git a/core/java/android/app/compat/ChangeIdStateCache.java b/core/java/android/app/compat/ChangeIdStateCache.java
index dea4e9c8..7948cec 100644
--- a/core/java/android/app/compat/ChangeIdStateCache.java
+++ b/core/java/android/app/compat/ChangeIdStateCache.java
@@ -32,7 +32,7 @@
public final class ChangeIdStateCache
extends PropertyInvalidatedCache<ChangeIdStateQuery, Boolean> {
private static final String CACHE_KEY = "cache_key.is_compat_change_enabled";
- private static final int MAX_ENTRIES = 64;
+ private static final int MAX_ENTRIES = 2048;
private static boolean sDisabled = false;
private volatile IPlatformCompat mPlatformCompat;
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 9a04ded..ba3f3c4 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -89,12 +89,10 @@
* @param item A single message that can contain a client activity/window request/callback.
*/
public void addTransactionItem(@NonNull ClientTransactionItem item) {
- if (Flags.bundleClientTransactionFlag()) {
- if (mTransactionItems == null) {
- mTransactionItems = new ArrayList<>();
- }
- mTransactionItems.add(item);
+ if (mTransactionItems == null) {
+ mTransactionItems = new ArrayList<>();
}
+ mTransactionItems.add(item);
// TODO(b/324203798): cleanup after remove UnsupportedAppUsage
// Populate even if mTransactionItems is set to support the UnsupportedAppUsage.
diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
index 9b53461..0c1e7a3 100644
--- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java
+++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
@@ -20,7 +20,6 @@
import static android.view.Display.INVALID_DISPLAY;
import static com.android.window.flags.Flags.activityWindowInfoFlag;
-import static com.android.window.flags.Flags.bundleClientTransactionFlag;
import static java.util.Objects.requireNonNull;
@@ -196,7 +195,7 @@
/** Called before updating the Configuration of the given {@code context}. */
public void onContextConfigurationPreChanged(@NonNull Context context) {
- if (!bundleClientTransactionFlag() || ActivityThread.isSystem()) {
+ if (ActivityThread.isSystem()) {
// Not enable for system server.
return;
}
@@ -212,7 +211,7 @@
/** Called after updating the Configuration of the given {@code context}. */
public void onContextConfigurationPostChanged(@NonNull Context context) {
- if (!bundleClientTransactionFlag() || ActivityThread.isSystem()) {
+ if (ActivityThread.isSystem()) {
// Not enable for system server.
return;
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 282ede3..8c56a9d 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -11436,7 +11436,7 @@
private static final PropertyInvalidatedCache<ApplicationInfoQuery, ApplicationInfo>
sApplicationInfoCache =
new PropertyInvalidatedCache<ApplicationInfoQuery, ApplicationInfo>(
- 32, PermissionManager.CACHE_KEY_PACKAGE_INFO,
+ 2048, PermissionManager.CACHE_KEY_PACKAGE_INFO,
"getApplicationInfo") {
@Override
public ApplicationInfo recompute(ApplicationInfoQuery query) {
@@ -11537,7 +11537,7 @@
private static final PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>
sPackageInfoCache =
new PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>(
- 64, PermissionManager.CACHE_KEY_PACKAGE_INFO,
+ 2048, PermissionManager.CACHE_KEY_PACKAGE_INFO,
"getPackageInfo") {
@Override
public PackageInfo recompute(PackageInfoQuery query) {
diff --git a/core/java/android/os/SharedMemory.java b/core/java/android/os/SharedMemory.java
index cba4423..c801fabf 100644
--- a/core/java/android/os/SharedMemory.java
+++ b/core/java/android/os/SharedMemory.java
@@ -25,6 +25,8 @@
import dalvik.system.VMRuntime;
+import libcore.io.IoUtils;
+
import java.io.Closeable;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -63,7 +65,7 @@
mMemoryRegistration = new MemoryRegistration(mSize);
mCleaner = Cleaner.create(mFileDescriptor,
- new Closer(mFileDescriptor.getInt$(), mMemoryRegistration));
+ new Closer(mFileDescriptor, mMemoryRegistration));
}
/**
@@ -276,7 +278,6 @@
*/
@Override
public void close() {
- mFileDescriptor.setInt$(-1);
if (mCleaner != null) {
mCleaner.clean();
mCleaner = null;
@@ -326,21 +327,20 @@
* Cleaner that closes the FD
*/
private static final class Closer implements Runnable {
- private int mFd;
+ private FileDescriptor mFd;
private MemoryRegistration mMemoryReference;
- private Closer(int fd, MemoryRegistration memoryReference) {
+ private Closer(FileDescriptor fd, MemoryRegistration memoryReference) {
mFd = fd;
+ IoUtils.setFdOwner(mFd, this);
mMemoryReference = memoryReference;
}
@Override
public void run() {
- try {
- FileDescriptor fd = new FileDescriptor();
- fd.setInt$(mFd);
- Os.close(fd);
- } catch (ErrnoException e) { /* swallow error */ }
+ IoUtils.closeQuietly(mFd);
+ mFd = null;
+
mMemoryReference.release();
mMemoryReference = null;
}
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index e82c4b0..76cf901 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -201,3 +201,10 @@
description: "Tracing using Perfetto SDK."
bug: "303199244"
}
+
+flag {
+ name: "allow_consentless_bugreport_delegated_consent"
+ namespace: "crumpet"
+ description: "Allow privileged apps to call bugreport generation without enforcing user consent and delegate it to the calling app instead"
+ bug: "324046728"
+}
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index fe7eab7..3954bc2 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -27,14 +27,6 @@
}
flag {
- name: "fix_unlocked_device_required_keys_v2"
- namespace: "hardware_backed_security"
- description: "Fix bugs in behavior of UnlockedDeviceRequired keystore keys"
- bug: "296464083"
- is_fixed_read_only: true
-}
-
-flag {
name: "keyinfo_unlocked_device_required"
is_exported: true
namespace: "hardware_backed_security"
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c71f9bd..6c6add9 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8222,6 +8222,19 @@
<permission android:name="android.permission.CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD"
android:protectionLevel="internal|appop" />
+ <!-- @hide @SystemApi
+ @FlaggedApi(android.os.Flags.FLAG_ALLOW_CONSENTLESS_BUGREPORT_DELEGATED_CONSENT)
+ Allows system applications to capture bugreport directly without consent dialog when using
+ the bugreporting API, and instead use the applications-owned consent page.
+ <p>The application still needs to hold {@link android.permission.DUMP} permission and be
+ bugreport-whitelisted to be able to capture a bugreport using the bugreporting API in the
+ first place.
+ <p>Protection level: signature|privileged
+ <p>Not for use by third-party applications. -->
+ <permission
+ android:name="android.permission.CAPTURE_CONSENTLESS_BUGREPORT_DELEGATED_CONSENT"
+ android:protectionLevel="signature|privileged" />
+
<!-- @SystemApi Allows to call APIs that log process lifecycle events
@hide -->
<permission android:name="android.permission.LOG_FOREGROUND_RESOURCE_USE"
diff --git a/core/tests/coretests/src/android/content/ContentResolverTest.java b/core/tests/coretests/src/android/content/ContentResolverTest.java
index 7b70b41..c8015d4 100644
--- a/core/tests/coretests/src/android/content/ContentResolverTest.java
+++ b/core/tests/coretests/src/android/content/ContentResolverTest.java
@@ -87,7 +87,7 @@
bitmap.compress(Bitmap.CompressFormat.PNG, 90, mImage.getOutputStream());
final AssetFileDescriptor afd = new AssetFileDescriptor(
- new ParcelFileDescriptor(mImage.getFileDescriptor()), 0, mSize, null);
+ ParcelFileDescriptor.dup(mImage.getFileDescriptor()), 0, mSize, null);
when(mProvider.openTypedAssetFile(any(), any(), any(), any(), any())).thenReturn(
afd);
}
diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java
index c3aaf98..f8cb050 100644
--- a/graphics/java/android/graphics/drawable/Icon.java
+++ b/graphics/java/android/graphics/drawable/Icon.java
@@ -494,30 +494,37 @@
BitmapFactory.decodeByteArray(getDataBytes(), getDataOffset(),
getDataLength())));
case TYPE_URI:
- InputStream is = getUriInputStream(context);
- if (is != null) {
- final Bitmap bitmap = BitmapFactory.decodeStream(is);
- if (bitmap == null) {
- Log.w(TAG, "Unable to decode image from URI: " + getUriString());
- if (iconLoadDrawableReturnNullWhenUriDecodeFails()) {
- return null;
+ try (InputStream is = getUriInputStream(context)) {
+ if (is != null) {
+ final Bitmap bitmap = BitmapFactory.decodeStream(is);
+ if (bitmap == null) {
+ Log.w(TAG, "Unable to decode image from URI: " + getUriString());
+ if (iconLoadDrawableReturnNullWhenUriDecodeFails()) {
+ return null;
+ }
}
+ return new BitmapDrawable(context.getResources(), fixMaxBitmapSize(bitmap));
}
- return new BitmapDrawable(context.getResources(), fixMaxBitmapSize(bitmap));
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
}
break;
case TYPE_URI_ADAPTIVE_BITMAP:
- is = getUriInputStream(context);
- if (is != null) {
- final Bitmap bitmap = BitmapFactory.decodeStream(is);
- if (bitmap == null) {
- Log.w(TAG, "Unable to decode image from URI: " + getUriString());
- if (iconLoadDrawableReturnNullWhenUriDecodeFails()) {
- return null;
+ try (InputStream is = getUriInputStream(context)) {
+ if (is != null) {
+ final Bitmap bitmap = BitmapFactory.decodeStream(is);
+ if (bitmap == null) {
+ Log.w(TAG, "Unable to decode image from URI: " + getUriString());
+ if (iconLoadDrawableReturnNullWhenUriDecodeFails()) {
+ return null;
+ }
}
+ return new AdaptiveIconDrawable(
+ null, new BitmapDrawable(context.getResources(),
+ fixMaxBitmapSize(bitmap)));
}
- return new AdaptiveIconDrawable(null, new BitmapDrawable(context.getResources(),
- fixMaxBitmapSize(bitmap)));
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
}
break;
}
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp b/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp
new file mode 100644
index 0000000..c6dbd9b
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp
@@ -0,0 +1,61 @@
+// 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+ default_team: "trendy_team_multitasking_windowing",
+}
+
+android_test {
+ name: "WMShellMultivalentScreenshotTestsOnDevice",
+ srcs: [
+ "src/**/*.kt",
+ ],
+ static_libs: [
+ "WindowManager-Shell",
+ "junit",
+ "androidx.test.runner",
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "truth",
+ "platform-parametric-runner-lib",
+ "platform-screenshot-diff-core",
+ ],
+ libs: [
+ "android.test.base",
+ "android.test.runner",
+ ],
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ ],
+ kotlincflags: ["-Xjvm-default=all"],
+ optimize: {
+ enabled: false,
+ },
+ test_suites: ["device-tests"],
+ platform_apis: true,
+ certificate: "platform",
+ aaptflags: [
+ "--extra-packages",
+ "com.android.wm.shell",
+ ],
+ manifest: "AndroidManifest.xml",
+ asset_dirs: ["goldens/onDevice"],
+}
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifest.xml b/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifest.xml
new file mode 100644
index 0000000..467dc6a
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.wm.shell.multivalentscreenshot">
+
+ <application android:debuggable="true" android:supportsRtl="true" >
+ <uses-library android:name="android.test.runner" />
+ <activity
+ android:name="platform.test.screenshot.ScreenshotActivity"
+ android:exported="true">
+ </activity>
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="Multivalent screenshot tests for WindowManager-Shell"
+ android:targetPackage="com.android.wm.shell.multivalentscreenshot">
+ </instrumentation>
+
+ <!-- this permission is required by Tuner Service in screenshot tests -->
+ <uses-permission android:name="android.permission.MANAGE_USERS" />
+</manifest>
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifestRobolectric.xml b/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifestRobolectric.xml
new file mode 100644
index 0000000..a7a3f13
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifestRobolectric.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.wm.shell.multivalentscreenshot">
+ <application android:debuggable="true" android:supportsRtl="true">
+ <uses-library android:name="android.test.runner" />
+ <activity
+ android:name="platform.test.screenshot.ScreenshotActivity"
+ android:exported="true">
+ </activity>
+ </application>
+</manifest>
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidTest.xml b/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidTest.xml
new file mode 100644
index 0000000..75793ae
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidTest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs Tests for WindowManagerShellLib">
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="install-arg" value="-t" />
+ <option name="test-file-name" value="WMShellMultivalentScreenshotTestsOnDevice.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-tag" value="WMShellMultivalentScreenshotTestsOnDevice" />
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="directory-keys" value="/data/user/0/com.android.wm.shell.multivalentscreenshot/files/wmshell_screenshots" />
+ <option name="collect-on-run-ended-only" value="true" />
+ </metrics_collector>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.wm.shell.multivalentscreenshot" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/dark_portrait_bubbles_education.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/dark_portrait_bubbles_education.png
new file mode 100644
index 0000000..eb28881
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/dark_portrait_bubbles_education.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/light_portrait_bubbles_education.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/light_portrait_bubbles_education.png
new file mode 100644
index 0000000..eb28881
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/light_portrait_bubbles_education.png
Binary files differ
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/robolectric/config/robolectric.properties b/libs/WindowManager/Shell/multivalentScreenshotTests/robolectric/config/robolectric.properties
new file mode 100644
index 0000000..7a0527c
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/robolectric/config/robolectric.properties
@@ -0,0 +1,2 @@
+sdk=NEWEST_SDK
+
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/bubbles/BubbleEducationViewScreenshotTest.kt b/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/bubbles/BubbleEducationViewScreenshotTest.kt
new file mode 100644
index 0000000..d35f493
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/bubbles/BubbleEducationViewScreenshotTest.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.wm.shell.bubbles
+
+import android.view.LayoutInflater
+import com.android.wm.shell.common.bubbles.BubblePopupView
+import com.android.wm.shell.testing.goldenpathmanager.WMShellGoldenPathManager
+import com.android.wm.shell.R
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.Displays
+import platform.test.screenshot.ViewScreenshotTestRule
+import platform.test.screenshot.getEmulatedDevicePathConfig
+
+@RunWith(ParameterizedAndroidJunit4::class)
+class BubbleEducationViewScreenshotTest(emulationSpec: DeviceEmulationSpec) {
+ companion object {
+ @Parameters(name = "{0}")
+ @JvmStatic
+ fun getTestSpecs() = DeviceEmulationSpec.forDisplays(Displays.Phone, isLandscape = false)
+ }
+
+ @get:Rule
+ val screenshotRule =
+ ViewScreenshotTestRule(
+ emulationSpec,
+ WMShellGoldenPathManager(getEmulatedDevicePathConfig(emulationSpec))
+ )
+
+ @Test
+ fun bubblesEducation() {
+ screenshotRule.screenshotTest("bubbles_education") { activity ->
+ activity.actionBar?.hide()
+ val view =
+ LayoutInflater.from(activity)
+ .inflate(R.layout.bubble_bar_stack_education, null) as BubblePopupView
+ view.setup()
+ view
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/testing/goldenpathmanager/WMShellGoldenPathManager.kt b/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/testing/goldenpathmanager/WMShellGoldenPathManager.kt
new file mode 100644
index 0000000..901b79b
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/testing/goldenpathmanager/WMShellGoldenPathManager.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.wm.shell.testing.goldenpathmanager
+
+import android.os.Build
+import androidx.test.platform.app.InstrumentationRegistry
+import platform.test.screenshot.GoldenPathManager
+import platform.test.screenshot.PathConfig
+
+/** A WM Shell specific implementation of [GoldenPathManager]. */
+class WMShellGoldenPathManager(pathConfig: PathConfig) :
+ GoldenPathManager(
+ appContext = InstrumentationRegistry.getInstrumentation().context,
+ assetsPathRelativeToBuildRoot = assetPath,
+ deviceLocalPath = deviceLocalPath,
+ pathConfig = pathConfig,
+ ) {
+
+ private companion object {
+ private const val ASSETS_PATH =
+ "frameworks/base/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice"
+ private const val ASSETS_PATH_ROBO =
+ "frameworks/base/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/" +
+ "robolectric"
+ private val assetPath: String
+ get() = if (Build.FINGERPRINT.contains("robolectric")) ASSETS_PATH_ROBO else ASSETS_PATH
+ private val deviceLocalPath: String
+ get() =
+ InstrumentationRegistry.getInstrumentation()
+ .targetContext
+ .filesDir
+ .absolutePath
+ .toString() + "/wmshell_screenshots"
+ }
+ override fun toString(): String {
+ // This string is appended to all actual/expected screenshots on the device, so make sure
+ // it is a static value.
+ return "WMShellGoldenPathManager"
+ }
+}
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTestsForDevice b/libs/WindowManager/Shell/multivalentScreenshotTestsForDevice
new file mode 120000
index 0000000..e879efc
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentScreenshotTestsForDevice
@@ -0,0 +1 @@
+multivalentScreenshotTests
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
index a3111b3..c9d3dbd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
@@ -324,6 +324,7 @@
enteringHasSameLetterbox = false
lastPostCommitFlingScale = SPRING_SCALE
gestureProgress = 0f
+ triggerBack = false
}
protected fun applyTransform(
@@ -499,10 +500,12 @@
}
override fun onBackCancelled() {
+ triggerBack = false
progressAnimator.onBackCancelled { finishAnimation() }
}
override fun onBackInvoked() {
+ triggerBack = true
progressAnimator.reset()
onGestureCommitted(progressAnimator.velocity)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 14ae3a7..1965382 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -975,7 +975,7 @@
if (!desktopModeTaskRepository.isDesktopModeShowing(task.displayId)) {
KtProtoLog.d(
WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: switch freeform task to fullscreen oon transition" +
+ "DesktopTasksController: bring desktop tasks to front on transition" +
" taskId=%d",
task.taskId
)
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 a1a18a9..14fa0f1 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
@@ -439,14 +439,15 @@
}
@Test
- fun showDesktopApps_dontReorderMinimizedTask() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_desktopWallpaperDisabled_dontReorderMinimizedTask() {
val homeTask = setUpHomeTask()
val freeformTask = setUpFreeformTask()
val minimizedTask = setUpFreeformTask()
+
markTaskHidden(freeformTask)
markTaskHidden(minimizedTask)
desktopModeTaskRepository.minimizeTask(DEFAULT_DISPLAY, minimizedTask.taskId)
-
controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
@@ -457,6 +458,26 @@
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_desktopWallpaperEnabled_dontReorderMinimizedTask() {
+ setUpHomeTask()
+ val freeformTask = setUpFreeformTask()
+ val minimizedTask = setUpFreeformTask()
+
+ markTaskHidden(freeformTask)
+ markTaskHidden(minimizedTask)
+ desktopModeTaskRepository.minimizeTask(DEFAULT_DISPLAY, minimizedTask.taskId)
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(2)
+ // Add desktop wallpaper activity
+ wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ // Reorder freeform task to top, don't reorder the minimized task
+ wct.assertReorderAt(index = 1, freeformTask, toTop = true)
+ }
+
+ @Test
fun getVisibleTaskCount_noTasks_returnsZero() {
assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
}
@@ -645,20 +666,37 @@
}
@Test
- fun moveToDesktop_nonRunningTask_launchesInFreeform() {
- whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
-
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun moveToDesktop_desktopWallpaperDisabled_nonRunningTask_launchesInFreeform() {
val task = createTaskInfo(1)
-
+ whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
controller.moveToDesktop(task.taskId, transitionSource = UNKNOWN)
+
with(getLatestEnterDesktopWct()) {
assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM)
}
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun moveToDesktop_desktopWallpaperEnabled_nonRunningTask_launchesInFreeform() {
+ val task = createTaskInfo(1)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
+ whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
+
+ controller.moveToDesktop(task.taskId, transitionSource = UNKNOWN)
+
+ with(getLatestEnterDesktopWct()) {
+ // Add desktop wallpaper activity
+ assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ // Launch task
+ assertLaunchTaskAt(index = 1, task.taskId, WINDOWING_MODE_FREEFORM)
+ }
+ }
+
+ @Test
fun moveToDesktop_topActivityTranslucent_doesNothing() {
setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
val task =
@@ -776,21 +814,44 @@
}
@Test
- fun moveToDesktop_bringsTasksOverLimit_dontShowBackTask() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun moveToDesktop_desktopWallpaperDisabled_bringsTasksOver_dontShowBackTask() {
val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
- val homeTask = setUpHomeTask()
val freeformTasks = (1..taskLimit).map { _ -> setUpFreeformTask() }
val newTask = setUpFullscreenTask()
+ val homeTask = setUpHomeTask()
controller.moveToDesktop(newTask, transitionSource = UNKNOWN)
val wct = getLatestEnterDesktopWct()
assertThat(wct.hierarchyOps.size).isEqualTo(taskLimit + 1) // visible tasks + home
wct.assertReorderAt(0, homeTask)
- for (i in 1..<taskLimit) { // Skipping freeformTasks[0]
- wct.assertReorderAt(index = i, task = freeformTasks[i])
- }
- wct.assertReorderAt(taskLimit, newTask)
+ wct.assertReorderSequenceInRange(
+ range = 1..<(taskLimit + 1),
+ *freeformTasks.drop(1).toTypedArray(), // Skipping freeformTasks[0]
+ newTask
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun moveToDesktop_desktopWallpaperEnabled_bringsTasksOverLimit_dontShowBackTask() {
+ val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
+ val freeformTasks = (1..taskLimit).map { _ -> setUpFreeformTask() }
+ val newTask = setUpFullscreenTask()
+ setUpHomeTask()
+
+ controller.moveToDesktop(newTask, transitionSource = UNKNOWN)
+
+ val wct = getLatestEnterDesktopWct()
+ assertThat(wct.hierarchyOps.size).isEqualTo(taskLimit + 1) // visible tasks + wallpaper
+ // Add desktop wallpaper activity
+ wct.assertPendingIntentAt(0, desktopWallpaperIntent)
+ wct.assertReorderSequenceInRange(
+ range = 1..<(taskLimit + 1),
+ *freeformTasks.drop(1).toTypedArray(), // Skipping freeformTasks[0]
+ newTask
+ )
}
@Test
@@ -1109,41 +1170,106 @@
}
@Test
- fun handleRequest_freeformTask_freeformNotVisible_reorderedToTop() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_freeformTask_desktopWallpaperDisabled_freeformNotVisible_reorderedToTop() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
val freeformTask1 = setUpFreeformTask()
- markTaskHidden(freeformTask1)
-
val freeformTask2 = createFreeformTask()
+
+ markTaskHidden(freeformTask1)
val result =
controller.handleRequest(Binder(), createTransition(freeformTask2, type = TRANSIT_TO_FRONT))
- assertThat(result?.hierarchyOps?.size).isEqualTo(2)
- result!!.assertReorderAt(1, freeformTask2, toTop = true)
+ assertNotNull(result, "Should handle request")
+ assertThat(result.hierarchyOps?.size).isEqualTo(2)
+ result.assertReorderAt(1, freeformTask2, toTop = true)
}
@Test
- fun handleRequest_freeformTask_noOtherTasks_reorderedToTop() {
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_freeformTask_desktopWallpaperEnabled_freeformNotVisible_reorderedToTop() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+ val freeformTask1 = setUpFreeformTask()
+ val freeformTask2 = createFreeformTask()
+
+ markTaskHidden(freeformTask1)
+ val result =
+ controller.handleRequest(Binder(), createTransition(freeformTask2, type = TRANSIT_TO_FRONT))
+
+ assertNotNull(result, "Should handle request")
+ assertThat(result.hierarchyOps?.size).isEqualTo(3)
+ // Add desktop wallpaper activity
+ result.assertPendingIntentAt(0, desktopWallpaperIntent)
+ // Bring active desktop tasks to front
+ result.assertReorderAt(1, freeformTask1, toTop = true)
+ // Bring new task to front
+ result.assertReorderAt(2, freeformTask2, toTop = true)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_freeformTask_desktopWallpaperDisabled_noOtherTasks_reorderedToTop() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
val task = createFreeformTask()
val result = controller.handleRequest(Binder(), createTransition(task))
- assertThat(result?.hierarchyOps?.size).isEqualTo(1)
- result!!.assertReorderAt(0, task, toTop = true)
+ assertNotNull(result, "Should handle request")
+ assertThat(result.hierarchyOps?.size).isEqualTo(1)
+ result.assertReorderAt(0, task, toTop = true)
}
@Test
- fun handleRequest_freeformTask_freeformOnOtherDisplayOnly_reorderedToTop() {
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_freeformTask_desktopWallpaperEnabled_noOtherTasks_reorderedToTop() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+ val task = createFreeformTask()
+ val result = controller.handleRequest(Binder(), createTransition(task))
+
+ assertNotNull(result, "Should handle request")
+ assertThat(result.hierarchyOps?.size).isEqualTo(2)
+ // Add desktop wallpaper activity
+ result.assertPendingIntentAt(0, desktopWallpaperIntent)
+ // Bring new task to front
+ result.assertReorderAt(1, task, toTop = true)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_freeformTask_dskWallpaperDisabled_freeformOnOtherDisplayOnly_reorderedToTop() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY)
- val taskSecondDisplay = createFreeformTask(displayId = SECOND_DISPLAY)
+ // Second display task
+ createFreeformTask(displayId = SECOND_DISPLAY)
val result = controller.handleRequest(Binder(), createTransition(taskDefaultDisplay))
- assertThat(result?.hierarchyOps?.size).isEqualTo(1)
- result!!.assertReorderAt(0, taskDefaultDisplay, toTop = true)
+
+ assertNotNull(result, "Should handle request")
+ assertThat(result.hierarchyOps?.size).isEqualTo(1)
+ result.assertReorderAt(0, taskDefaultDisplay, toTop = true)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_freeformTask_dskWallpaperEnabled_freeformOnOtherDisplayOnly_reorderedToTop() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+ val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY)
+ // Second display task
+ createFreeformTask(displayId = SECOND_DISPLAY)
+
+ val result = controller.handleRequest(Binder(), createTransition(taskDefaultDisplay))
+
+ assertNotNull(result, "Should handle request")
+ assertThat(result.hierarchyOps?.size).isEqualTo(2)
+ // Add desktop wallpaper activity
+ result.assertPendingIntentAt(0, desktopWallpaperIntent)
+ // Bring new task to front
+ result.assertReorderAt(1, taskDefaultDisplay, toTop = true)
}
@Test
@@ -2041,6 +2167,16 @@
}
}
+/** Checks if the reorder hierarchy operations in [range] correspond to [tasks] list */
+private fun WindowContainerTransaction.assertReorderSequenceInRange(
+ range: IntRange,
+ vararg tasks: RunningTaskInfo
+) {
+ assertThat(hierarchyOps.slice(range).map { it.type to it.container })
+ .containsExactlyElementsIn(tasks.map { HIERARCHY_OP_TYPE_REORDER to it.token.asBinder() })
+ .inOrder()
+}
+
private fun WindowContainerTransaction.assertRemoveAt(index: Int, token: WindowContainerToken) {
assertIndexInBounds(index)
val op = hierarchyOps[index]
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index 5f84862..acfe473 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -45,13 +45,6 @@
}
flag {
- name: "gnss_call_stop_before_set_position_mode"
- namespace: "location"
- description: "Flag for calling stop() before setPositionMode()"
- bug: "306874828"
-}
-
-flag {
name: "gnss_api_measurement_request_work_source"
namespace: "location"
description: "Flag for GnssMeasurementRequest WorkSource API"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 7c2f7fe..d0c4004 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -7,6 +7,7 @@
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
+import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -137,6 +138,9 @@
fade(Communal.Elements.Grid)
}
}
+ // Disable horizontal overscroll. If the scene is overscrolled too soon after showing, this
+ // can lead to inconsistent KeyguardState changes.
+ overscroll(CommunalScenes.Communal, Orientation.Horizontal) {}
}
/**
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
index 7d82920..1ce51af 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
@@ -27,6 +27,7 @@
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler
import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection
+import com.android.systemui.communal.ui.compose.section.CommunalPopupSection
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
@@ -44,81 +45,86 @@
private val lockSection: LockSection,
private val bottomAreaSection: BottomAreaSection,
private val ambientStatusBarSection: AmbientStatusBarSection,
+ private val communalPopupSection: CommunalPopupSection,
) {
@Composable
fun SceneScope.Content(modifier: Modifier = Modifier) {
- Layout(
- modifier = modifier.fillMaxSize(),
- content = {
- Box(modifier = Modifier.fillMaxSize()) {
- with(ambientStatusBarSection) {
- AmbientStatusBar(modifier = Modifier.fillMaxWidth())
+ CommunalTouchableSurface(viewModel = viewModel, modifier = modifier) {
+ Layout(
+ modifier = Modifier.fillMaxSize(),
+ content = {
+ Box(modifier = Modifier.fillMaxSize()) {
+ with(communalPopupSection) { Popup() }
+ with(ambientStatusBarSection) {
+ AmbientStatusBar(modifier = Modifier.fillMaxWidth())
+ }
+ CommunalHub(
+ viewModel = viewModel,
+ interactionHandler = interactionHandler,
+ dialogFactory = dialogFactory,
+ modifier = Modifier.element(Communal.Elements.Grid)
+ )
}
- CommunalHub(
- viewModel = viewModel,
- interactionHandler = interactionHandler,
- dialogFactory = dialogFactory,
- modifier = Modifier.element(Communal.Elements.Grid)
+ with(lockSection) {
+ LockIcon(
+ overrideColor = LocalAndroidColorScheme.current.onPrimaryContainer,
+ modifier = Modifier.element(Communal.Elements.LockIcon)
+ )
+ }
+ with(bottomAreaSection) {
+ IndicationArea(
+ Modifier.element(Communal.Elements.IndicationArea).fillMaxWidth()
+ )
+ }
+ }
+ ) { measurables, constraints ->
+ val communalGridMeasurable = measurables[0]
+ val lockIconMeasurable = measurables[1]
+ val bottomAreaMeasurable = measurables[2]
+
+ val noMinConstraints =
+ constraints.copy(
+ minWidth = 0,
+ minHeight = 0,
+ )
+
+ val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
+ val lockIconBounds =
+ IntRect(
+ left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
+ top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
+ right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
+ bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
+ )
+
+ val bottomAreaPlaceable =
+ bottomAreaMeasurable.measure(
+ noMinConstraints.copy(
+ maxHeight =
+ (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0)
+ )
+ )
+
+ val communalGridPlaceable =
+ communalGridMeasurable.measure(
+ noMinConstraints.copy(maxHeight = lockIconBounds.top)
+ )
+
+ layout(constraints.maxWidth, constraints.maxHeight) {
+ communalGridPlaceable.place(
+ x = 0,
+ y = 0,
+ )
+ lockIconPlaceable.place(
+ x = lockIconBounds.left,
+ y = lockIconBounds.top,
+ )
+ bottomAreaPlaceable.place(
+ x = 0,
+ y = constraints.maxHeight - bottomAreaPlaceable.height,
)
}
- with(lockSection) {
- LockIcon(
- overrideColor = LocalAndroidColorScheme.current.onPrimaryContainer,
- modifier = Modifier.element(Communal.Elements.LockIcon)
- )
- }
- with(bottomAreaSection) {
- IndicationArea(
- Modifier.element(Communal.Elements.IndicationArea).fillMaxWidth()
- )
- }
- }
- ) { measurables, constraints ->
- val communalGridMeasurable = measurables[0]
- val lockIconMeasurable = measurables[1]
- val bottomAreaMeasurable = measurables[2]
-
- val noMinConstraints =
- constraints.copy(
- minWidth = 0,
- minHeight = 0,
- )
-
- val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
- val lockIconBounds =
- IntRect(
- left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
- top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
- right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
- bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
- )
-
- val bottomAreaPlaceable =
- bottomAreaMeasurable.measure(
- noMinConstraints.copy(
- maxHeight = (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0)
- )
- )
-
- val communalGridPlaceable =
- communalGridMeasurable.measure(
- noMinConstraints.copy(maxHeight = lockIconBounds.top)
- )
-
- layout(constraints.maxWidth, constraints.maxHeight) {
- communalGridPlaceable.place(
- x = 0,
- y = 0,
- )
- lockIconPlaceable.place(
- x = lockIconBounds.left,
- y = lockIconBounds.top,
- )
- bottomAreaPlaceable.place(
- x = 0,
- y = constraints.maxHeight - bottomAreaPlaceable.height,
- )
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 9ea435e..e7f0af0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -25,7 +25,6 @@
import android.widget.RemoteViews
import androidx.annotation.VisibleForTesting
import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateFloatAsState
@@ -71,7 +70,6 @@
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.outlined.Edit
-import androidx.compose.material.icons.outlined.TouchApp
import androidx.compose.material.icons.outlined.Widgets
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
@@ -99,14 +97,16 @@
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ColorMatrix
-import androidx.compose.ui.graphics.TransformOrigin
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.input.key.onPreviewKeyEvent
-import androidx.compose.ui.input.pointer.motionEventSpy
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.boundsInWindow
@@ -126,13 +126,11 @@
import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.times
import androidx.compose.ui.viewinterop.AndroidView
-import androidx.compose.ui.window.Popup
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.window.layout.WindowMetricsCalculator
import com.android.compose.animation.Easings.Emphasized
@@ -143,7 +141,6 @@
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.communal.ui.compose.Dimensions.CardOutlineWidth
import com.android.systemui.communal.ui.compose.extensions.allowGestures
import com.android.systemui.communal.ui.compose.extensions.detectLongPressGesture
import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset
@@ -151,7 +148,6 @@
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
-import com.android.systemui.communal.ui.viewmodel.PopupType
import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.res.R
@@ -171,7 +167,6 @@
) {
val communalContent by
viewModel.communalContent.collectAsStateWithLifecycle(initialValue = emptyList())
- val currentPopup by viewModel.currentPopup.collectAsStateWithLifecycle(initialValue = null)
var removeButtonCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) }
var toolbarSize: IntSize? by remember { mutableStateOf(null) }
var gridCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) }
@@ -213,43 +208,29 @@
}
.thenIf(!viewModel.isEditMode && !isEmptyState) {
Modifier.pointerInput(
- gridState,
- contentOffset,
- communalContent,
- gridCoordinates
- ) {
- detectLongPressGesture { offset ->
- // Deduct both grid offset relative to its container and content
- // offset.
- val adjustedOffset =
- gridCoordinates?.let {
- offset - it.positionInWindow() - contentOffset
- }
- val index =
- adjustedOffset?.let { firstIndexAtOffset(gridState, it) }
- // Display the button only when the gesture initiates from widgets,
- // the CTA tile, or an empty area on the screen. UMO/smartspace have
- // their own long-press handlers. To prevent user confusion, we
- // should
- // not display this button.
- if (
- index == null ||
- communalContent[index].isWidgetContent() ||
- communalContent[index] is
- CommunalContentModel.CtaTileInViewMode
- ) {
- viewModel.onShowCustomizeWidgetButton()
+ gridState,
+ contentOffset,
+ communalContent,
+ gridCoordinates
+ ) {
+ detectLongPressGesture { offset ->
+ // Deduct both grid offset relative to its container and content
+ // offset.
+ val adjustedOffset =
+ gridCoordinates?.let {
+ offset - it.positionInWindow() - contentOffset
}
- val key =
- index?.let { keyAtIndexIfEditable(communalContent, index) }
+ val index = adjustedOffset?.let { firstIndexAtOffset(gridState, it) }
+ val key = index?.let { keyAtIndexIfEditable(communalContent, index) }
+ // Handle long-click on widgets and set the selected index
+ // correctly. We only handle widgets here because long click on
+ // empty spaces is handled by CommunalPopupSection.
+ if (key != null) {
+ viewModel.onLongClick()
viewModel.setSelectedKey(key)
}
}
- .onPreviewKeyEvent {
- onKeyEvent(viewModel)
- false
- }
- .motionEventSpy { onMotionEvent(viewModel) }
+ }
},
) {
AccessibilityContainer(viewModel) {
@@ -342,22 +323,6 @@
)
}
}
- if (currentPopup == PopupType.CtaTile) {
- PopupOnDismissCtaTile(viewModel::onHidePopup)
- }
-
- AnimatedVisibility(
- visible = currentPopup == PopupType.CustomizeWidgetButton,
- modifier = Modifier.fillMaxSize()
- ) {
- ButtonToEditWidgets(
- onClick = {
- viewModel.onHidePopup()
- viewModel.onOpenWidgetEditor(selectedKey.value)
- },
- onHide = { viewModel.onHidePopup() }
- )
- }
if (viewModel is CommunalViewModel && dialogFactory != null) {
val isEnableWidgetDialogShowing by
@@ -413,14 +378,6 @@
}
}
-private fun onKeyEvent(viewModel: BaseCommunalViewModel) {
- viewModel.signalUserInteraction()
-}
-
-private fun onMotionEvent(viewModel: BaseCommunalViewModel) {
- viewModel.signalUserInteraction()
-}
-
@Composable
private fun DisclaimerBottomSheetContent(onButtonClicked: () -> Unit) {
val colors = LocalAndroidColorScheme.current
@@ -821,107 +778,6 @@
}
@Composable
-private fun AnimatedVisibilityScope.ButtonToEditWidgets(
- onClick: () -> Unit,
- onHide: () -> Unit,
-) {
- Popup(
- alignment = Alignment.TopCenter,
- offset = IntOffset(0, 40),
- onDismissRequest = onHide,
- ) {
- val colors = LocalAndroidColorScheme.current
- Button(
- modifier =
- Modifier.height(56.dp)
- .graphicsLayer { transformOrigin = TransformOrigin(0f, 0f) }
- .animateEnterExit(
- enter =
- fadeIn(
- initialAlpha = 0f,
- animationSpec = tween(durationMillis = 83, easing = LinearEasing)
- ),
- exit =
- fadeOut(
- animationSpec =
- tween(
- durationMillis = 83,
- delayMillis = 167,
- easing = LinearEasing
- )
- )
- )
- .background(colors.secondary, RoundedCornerShape(50.dp)),
- onClick = onClick,
- ) {
- Row(
- modifier =
- Modifier.animateEnterExit(
- enter =
- fadeIn(
- animationSpec =
- tween(
- durationMillis = 167,
- delayMillis = 83,
- easing = LinearEasing
- )
- ),
- exit =
- fadeOut(
- animationSpec = tween(durationMillis = 167, easing = LinearEasing)
- )
- )
- ) {
- Icon(
- imageVector = Icons.Outlined.Widgets,
- contentDescription = stringResource(R.string.button_to_configure_widgets_text),
- tint = colors.onSecondary,
- modifier = Modifier.size(20.dp)
- )
- Spacer(modifier = Modifier.size(8.dp))
- Text(
- text = stringResource(R.string.button_to_configure_widgets_text),
- style = MaterialTheme.typography.titleSmall,
- color = colors.onSecondary
- )
- }
- }
- }
-}
-
-@Composable
-private fun PopupOnDismissCtaTile(onHidePopup: () -> Unit) {
- Popup(
- alignment = Alignment.TopCenter,
- offset = IntOffset(0, 40),
- onDismissRequest = onHidePopup
- ) {
- val colors = LocalAndroidColorScheme.current
- Row(
- modifier =
- Modifier.height(56.dp)
- .background(colors.secondary, RoundedCornerShape(50.dp))
- .padding(16.dp),
- horizontalArrangement = Arrangement.Center,
- verticalAlignment = Alignment.CenterVertically,
- ) {
- Icon(
- imageVector = Icons.Outlined.TouchApp,
- contentDescription = stringResource(R.string.popup_on_dismiss_cta_tile_text),
- tint = colors.onSecondary,
- modifier = Modifier.size(20.dp)
- )
- Spacer(modifier = Modifier.size(8.dp))
- Text(
- text = stringResource(R.string.popup_on_dismiss_cta_tile_text),
- style = MaterialTheme.typography.titleSmall,
- color = colors.onSecondary,
- )
- }
- }
-}
-
-@Composable
private fun filledButtonColors(): ButtonColors {
val colors = LocalAndroidColorScheme.current
return ButtonDefaults.buttonColors(
@@ -968,13 +824,26 @@
/** Creates an empty card used to highlight a particular spot on the grid. */
@Composable
-fun HighlightedItem(modifier: Modifier = Modifier) {
- Card(
- modifier = modifier,
- colors = CardDefaults.cardColors(containerColor = Color.Transparent),
- border = BorderStroke(CardOutlineWidth, LocalAndroidColorScheme.current.tertiaryFixed),
- shape = RoundedCornerShape(16.dp)
- ) {}
+fun HighlightedItem(modifier: Modifier = Modifier, alpha: Float = 1.0f) {
+ val brush = SolidColor(LocalAndroidColorScheme.current.primaryFixed)
+ Box(
+ modifier =
+ // drawBehind lets us draw outside the bounds of the widgets so that we don't need to
+ // resize grid items to account for the border.
+ modifier.drawBehind {
+ // 8dp of padding between the widget and the highlight on every side.
+ val padding = 8.dp.toPx()
+ drawRoundRect(
+ brush,
+ alpha = alpha,
+ topLeft = Offset(-padding, -padding),
+ size =
+ Size(width = size.width + padding * 2, height = size.height + padding * 2),
+ cornerRadius = CornerRadius(37.dp.toPx()),
+ style = Stroke(width = 3.dp.toPx())
+ )
+ }
+ )
}
/** Presents a CTA tile at the end of the grid, to customize the hub. */
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt
new file mode 100644
index 0000000..3707a87
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.communal.ui.compose
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.key.onPreviewKeyEvent
+import androidx.compose.ui.input.pointer.motionEventSpy
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+
+@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
+@Composable
+fun CommunalTouchableSurface(
+ viewModel: CommunalViewModel,
+ modifier: Modifier = Modifier,
+ content: @Composable BoxScope.() -> Unit,
+) {
+
+ val interactionSource = remember { MutableInteractionSource() }
+
+ Box(
+ modifier =
+ modifier
+ .combinedClickable(
+ onLongClick = viewModel::onLongClick,
+ onClick = viewModel::onClick,
+ interactionSource = interactionSource,
+ indication = null,
+ )
+ .onPreviewKeyEvent {
+ viewModel.signalUserInteraction()
+ false
+ }
+ .motionEventSpy { viewModel.signalUserInteraction() }
+ ) {
+ content()
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
index 33d2cc4..1051095 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
@@ -16,10 +16,9 @@
package com.android.systemui.communal.ui.compose
-import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateFloatAsState
-import androidx.compose.animation.fadeIn
-import androidx.compose.animation.fadeOut
+import androidx.compose.animation.core.spring
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
import androidx.compose.foundation.gestures.scrollBy
@@ -34,13 +33,10 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
-import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.input.pointer.pointerInteropFilter
-import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.toOffset
import androidx.compose.ui.unit.toSize
@@ -107,8 +103,7 @@
get() =
draggingItemLayoutInfo?.let { item ->
draggingItemInitialOffset + draggingItemDraggedDelta - item.offset.toOffset()
- }
- ?: Offset.Zero
+ } ?: Offset.Zero
private val draggingItemLayoutInfo: LazyGridItemInfo?
get() = state.layoutInfo.visibleItemsInfo.firstOrNull { it.index == draggingItemIndex }
@@ -238,7 +233,6 @@
}
/** Wrap LazyGrid item with additional modifier needed for drag and drop. */
-@OptIn(ExperimentalComposeUiApi::class)
@ExperimentalFoundationApi
@Composable
fun LazyGridItemScope.DraggableItem(
@@ -270,22 +264,18 @@
Modifier.animateItemPlacement()
}
+ // Animate the highlight alpha manually as alpha modifier (and AnimatedVisibility) clips the
+ // widget to bounds, which cuts off the highlight as we are drawing outside the widget bounds.
+ val alpha by
+ animateFloatAsState(
+ targetValue =
+ if ((dragging || selected) && !dragDropState.isDraggingToRemove) 1f else 0f,
+ animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
+ label = "Widget outline alpha"
+ )
+
Box(modifier) {
+ HighlightedItem(Modifier.matchParentSize(), alpha = alpha)
Box(draggingModifier) { content(dragging) }
- AnimatedVisibility(
- modifier =
- Modifier.matchParentSize()
- // Avoid taking focus away from the content when using explore-by-touch with
- // accessibility tools.
- .clearAndSetSemantics {}
- // Do not consume motion events in the highlighted item and pass them down to
- // the content.
- .pointerInteropFilter { false },
- visible = (dragging || selected) && !dragDropState.isDraggingToRemove,
- enter = fadeIn(),
- exit = fadeOut()
- ) {
- HighlightedItem(Modifier.matchParentSize())
- }
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt
new file mode 100644
index 0000000..1ea73e1
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt
@@ -0,0 +1,192 @@
+/*
+ * 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.communal.ui.compose.section
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.AnimatedVisibilityScope
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.TouchApp
+import androidx.compose.material.icons.outlined.Widgets
+import androidx.compose.material3.Button
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.TransformOrigin
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Popup
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.compose.theme.LocalAndroidColorScheme
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.communal.ui.viewmodel.PopupType
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+class CommunalPopupSection
+@Inject
+constructor(
+ private val viewModel: CommunalViewModel,
+) {
+
+ @Composable
+ fun Popup() {
+ val currentPopup by viewModel.currentPopup.collectAsStateWithLifecycle(initialValue = null)
+
+ if (currentPopup == PopupType.CtaTile) {
+ PopupOnDismissCtaTile(viewModel::onHidePopup)
+ }
+
+ AnimatedVisibility(
+ visible = currentPopup == PopupType.CustomizeWidgetButton,
+ modifier = Modifier.fillMaxSize()
+ ) {
+ ButtonToEditWidgets(
+ onClick = {
+ viewModel.onHidePopup()
+ viewModel.onOpenWidgetEditor()
+ },
+ onDismissRequest = {
+ viewModel.onHidePopup()
+ viewModel.setSelectedKey(null)
+ }
+ )
+ }
+ }
+
+ @Composable
+ private fun AnimatedVisibilityScope.ButtonToEditWidgets(
+ onClick: () -> Unit,
+ onDismissRequest: () -> Unit,
+ ) {
+ Popup(
+ alignment = Alignment.TopCenter,
+ offset = IntOffset(0, 40),
+ onDismissRequest = onDismissRequest,
+ ) {
+ val colors = LocalAndroidColorScheme.current
+ Button(
+ modifier =
+ Modifier.height(56.dp)
+ .graphicsLayer { transformOrigin = TransformOrigin(0f, 0f) }
+ .animateEnterExit(
+ enter =
+ fadeIn(
+ initialAlpha = 0f,
+ animationSpec =
+ tween(durationMillis = 83, easing = LinearEasing)
+ ),
+ exit =
+ fadeOut(
+ animationSpec =
+ tween(
+ durationMillis = 83,
+ delayMillis = 167,
+ easing = LinearEasing
+ )
+ )
+ )
+ .background(colors.secondary, RoundedCornerShape(50.dp)),
+ onClick = onClick,
+ ) {
+ Row(
+ modifier =
+ Modifier.animateEnterExit(
+ enter =
+ fadeIn(
+ animationSpec =
+ tween(
+ durationMillis = 167,
+ delayMillis = 83,
+ easing = LinearEasing
+ )
+ ),
+ exit =
+ fadeOut(
+ animationSpec =
+ tween(durationMillis = 167, easing = LinearEasing)
+ )
+ )
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.Widgets,
+ contentDescription =
+ stringResource(R.string.button_to_configure_widgets_text),
+ tint = colors.onSecondary,
+ modifier = Modifier.size(20.dp)
+ )
+ Spacer(modifier = Modifier.size(8.dp))
+ Text(
+ text = stringResource(R.string.button_to_configure_widgets_text),
+ style = MaterialTheme.typography.titleSmall,
+ color = colors.onSecondary
+ )
+ }
+ }
+ }
+ }
+
+ @Composable
+ private fun PopupOnDismissCtaTile(onDismissRequest: () -> Unit) {
+ Popup(
+ alignment = Alignment.TopCenter,
+ offset = IntOffset(0, 40),
+ onDismissRequest = onDismissRequest
+ ) {
+ val colors = LocalAndroidColorScheme.current
+ Row(
+ modifier =
+ Modifier.height(56.dp)
+ .background(colors.secondary, RoundedCornerShape(50.dp))
+ .padding(16.dp),
+ horizontalArrangement = Arrangement.Center,
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.TouchApp,
+ contentDescription = stringResource(R.string.popup_on_dismiss_cta_tile_text),
+ tint = colors.onSecondary,
+ modifier = Modifier.size(20.dp)
+ )
+ Spacer(modifier = Modifier.size(8.dp))
+ Text(
+ text = stringResource(R.string.popup_on_dismiss_cta_tile_text),
+ style = MaterialTheme.typography.titleSmall,
+ color = colors.onSecondary,
+ )
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 7a5f81c..51991de 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -75,6 +75,7 @@
import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
+import com.android.systemui.statusbar.KeyguardIndicationController
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
@@ -91,6 +92,7 @@
import org.mockito.Mockito
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
@@ -154,13 +156,14 @@
context.resources,
kosmos.keyguardTransitionInteractor,
kosmos.keyguardInteractor,
+ mock<KeyguardIndicationController>(),
kosmos.communalSceneInteractor,
kosmos.communalInteractor,
kosmos.communalSettingsInteractor,
kosmos.communalTutorialInteractor,
kosmos.shadeInteractor,
mediaHost,
- logcatLogBuffer("CommunalViewModelTest"),
+ logcatLogBuffer("CommunalViewModelTest")
)
}
@@ -358,7 +361,7 @@
val currentPopup by collectLastValue(underTest.currentPopup)
assertThat(currentPopup).isNull()
- underTest.onShowCustomizeWidgetButton()
+ underTest.onLongClick()
assertThat(currentPopup).isEqualTo(PopupType.CustomizeWidgetButton)
advanceTimeBy(POPUP_AUTO_HIDE_TIMEOUT_MS)
assertThat(currentPopup).isNull()
@@ -370,7 +373,7 @@
tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
val currentPopup by collectLastValue(underTest.currentPopup)
- underTest.onShowCustomizeWidgetButton()
+ underTest.onLongClick()
assertThat(currentPopup).isEqualTo(PopupType.CustomizeWidgetButton)
underTest.onHidePopup()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt
index cb4e2d3..16c7090 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt
@@ -28,12 +28,14 @@
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.shade.ui.viewmodel.notificationsShadeSceneViewModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -56,7 +58,7 @@
private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }
- private val underTest = kosmos.notificationsShadeSceneViewModel
+ private val underTest by lazy { kosmos.notificationsShadeSceneViewModel }
@Test
fun upTransitionSceneKey_deviceLocked_lockscreen() =
@@ -65,11 +67,23 @@
lockDevice()
assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(destinationScenes?.get(Swipe.Down)).isNull()
assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value)
.isEqualTo(Scenes.Lockscreen)
}
@Test
+ fun upTransitionSceneKey_deviceLocked_keyguardDisabled_gone() =
+ testScope.runTest {
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
+ lockDevice()
+ kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+
+ assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value).isEqualTo(Scenes.Gone)
+ }
+
+ @Test
fun upTransitionSceneKey_deviceUnlocked_gone() =
testScope.runTest {
val destinationScenes by collectLastValue(underTest.destinationScenes)
@@ -77,6 +91,33 @@
unlockDevice()
assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(destinationScenes?.get(Swipe.Down)).isNull()
+ assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Gone)
+ }
+
+ @Test
+ fun downTransitionSceneKey_deviceLocked_bottomAligned_lockscreen() =
+ testScope.runTest {
+ kosmos.fakeShadeRepository.setDualShadeAlignedToBottom(true)
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
+ lockDevice()
+
+ assertThat(destinationScenes?.get(Swipe.Down)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(destinationScenes?.get(Swipe.Up)).isNull()
+ assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value)
+ .isEqualTo(Scenes.Lockscreen)
+ }
+
+ @Test
+ fun downTransitionSceneKey_deviceUnlocked_bottomAligned_gone() =
+ testScope.runTest {
+ kosmos.fakeShadeRepository.setDualShadeAlignedToBottom(true)
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
+ lockDevice()
+ unlockDevice()
+
+ assertThat(destinationScenes?.get(Swipe.Down)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(destinationScenes?.get(Swipe.Up)).isNull()
assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Gone)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index d43d50a..e01ffa6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -32,6 +32,7 @@
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
@@ -160,6 +161,37 @@
}
@Test
+ fun destinations_whenNotCustomizing_withPreviousSceneLockscreen_butLockscreenDisabled() =
+ testScope.runTest {
+ overrideResource(R.bool.config_use_split_notification_shade, false)
+ qsFlexiglassAdapter.setCustomizing(false)
+ val destinations by collectLastValue(underTest.destinationScenes)
+
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val backScene by collectLastValue(sceneBackInteractor.backScene)
+ val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
+ sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
+ sceneInteractor.changeScene(Scenes.QuickSettings, "reason")
+
+ kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+ assertThat(backScene).isNull()
+ assertThat(destinations)
+ .isEqualTo(
+ mapOf(
+ Back to UserActionResult(Scenes.Shade),
+ Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Shade),
+ Swipe(
+ fromSource = Edge.Bottom,
+ direction = SwipeDirection.Up,
+ ) to UserActionResult(SceneFamilies.Home)
+ )
+ )
+ assertThat(homeScene).isEqualTo(Scenes.Gone)
+ }
+
+ @Test
fun destinations_whenNotCustomizing_authMethodSwipe_lockscreenNotDismissed() =
testScope.runTest {
overrideResource(R.bool.config_use_split_notification_shade, false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt
index ac67ac8..411a7a4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt
@@ -28,12 +28,14 @@
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.shade.ui.viewmodel.quickSettingsShadeSceneViewModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -56,7 +58,7 @@
private val sceneInteractor = kosmos.sceneInteractor
private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor
- private val underTest = kosmos.quickSettingsShadeSceneViewModel
+ private val underTest by lazy { kosmos.quickSettingsShadeSceneViewModel }
@Test
fun upTransitionSceneKey_deviceLocked_lockscreen() =
@@ -66,10 +68,23 @@
lockDevice()
assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(destinationScenes?.get(Swipe.Down)).isNull()
assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
}
@Test
+ fun upTransitionSceneKey_deviceLocked_keyguardDisabled_gone() =
+ testScope.runTest {
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
+ val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
+ lockDevice()
+ kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+
+ assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(homeScene).isEqualTo(Scenes.Gone)
+ }
+
+ @Test
fun upTransitionSceneKey_deviceUnlocked_gone() =
testScope.runTest {
val destinationScenes by collectLastValue(underTest.destinationScenes)
@@ -78,6 +93,34 @@
unlockDevice()
assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(destinationScenes?.get(Swipe.Down)).isNull()
+ assertThat(homeScene).isEqualTo(Scenes.Gone)
+ }
+
+ @Test
+ fun downTransitionSceneKey_deviceLocked_bottomAligned_lockscreen() =
+ testScope.runTest {
+ kosmos.fakeShadeRepository.setDualShadeAlignedToBottom(true)
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
+ val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
+ lockDevice()
+
+ assertThat(destinationScenes?.get(Swipe.Down)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(destinationScenes?.get(Swipe.Up)).isNull()
+ assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
+ }
+
+ @Test
+ fun downTransitionSceneKey_deviceUnlocked_bottomAligned_gone() =
+ testScope.runTest {
+ kosmos.fakeShadeRepository.setDualShadeAlignedToBottom(true)
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
+ val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
+ lockDevice()
+ unlockDevice()
+
+ assertThat(destinationScenes?.get(Swipe.Down)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(destinationScenes?.get(Swipe.Up)).isNull()
assertThat(homeScene).isEqualTo(Scenes.Gone)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 9edc3af..ae615dd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -36,12 +36,15 @@
import com.android.systemui.classifier.falsingManager
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeTrustRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.model.sysUiState
@@ -1339,6 +1342,111 @@
assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
}
+ @Test
+ fun switchToGone_whenKeyguardBecomesDisabled() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ prepareState()
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ underTest.start()
+
+ kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+ runCurrent()
+
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+ }
+
+ @Test
+ fun switchToGone_whenKeyguardBecomesDisabled_whenOnShadeScene() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ prepareState(
+ initialSceneKey = Scenes.Shade,
+ )
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+ underTest.start()
+
+ kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+ runCurrent()
+
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+ }
+
+ @Test
+ fun doesNotSwitchToGone_whenKeyguardBecomesDisabled_whenInLockdownMode() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ prepareState()
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ underTest.start()
+
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+ kosmos.fakeBiometricSettingsRepository.setIsUserInLockdown(true)
+ kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+ runCurrent()
+
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ }
+
+ @Test
+ fun doesNotSwitchToGone_whenKeyguardBecomesDisabled_whenDeviceEntered() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ prepareState(
+ isDeviceUnlocked = true,
+ initialSceneKey = Scenes.Gone,
+ )
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+ assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isTrue()
+ underTest.start()
+ sceneInteractor.changeScene(Scenes.Shade, "")
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+ assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isTrue()
+
+ kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+ runCurrent()
+
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+ }
+
+ @Test
+ fun switchToLockscreen_whenKeyguardBecomesEnabled_afterHidingWhenDisabled() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ prepareState()
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ underTest.start()
+ kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+ runCurrent()
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+
+ kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(true)
+ runCurrent()
+
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ }
+
+ @Test
+ fun doesNotSwitchToLockscreen_whenKeyguardBecomesEnabled_ifAuthMethodBecameInsecure() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ prepareState()
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ underTest.start()
+ kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+ runCurrent()
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
+ runCurrent()
+
+ kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(true)
+ runCurrent()
+
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+ }
+
private fun TestScope.emulateSceneTransition(
transitionStateFlow: MutableStateFlow<ObservableTransitionState>,
toScene: SceneKey,
@@ -1381,15 +1489,10 @@
isDeviceProvisioned: Boolean = true,
isInteractive: Boolean = true,
): MutableStateFlow<ObservableTransitionState> {
- if (authenticationMethod?.isSecure == true) {
- assert(isLockscreenEnabled) {
- "Lockscreen cannot be disabled while having a secure authentication method"
- }
- if (isDeviceUnlocked) {
- kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus(
- SuccessFingerprintAuthenticationStatus(0, true)
- )
- }
+ if (isDeviceUnlocked) {
+ kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
}
check(initialSceneKey != Scenes.Gone || isDeviceUnlocked) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index c53cdf8..a0295c9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -32,6 +32,7 @@
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
@@ -139,6 +140,21 @@
}
@Test
+ fun upTransitionSceneKey_keyguardDisabled_gone() =
+ testScope.runTest {
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
+ val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+
+ assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
+ .isEqualTo(SceneFamilies.Home)
+ assertThat(homeScene).isEqualTo(Scenes.Gone)
+ }
+
+ @Test
fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
testScope.runTest {
val destinationScenes by collectLastValue(underTest.destinationScenes)
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
index 004d5db..c2c5277 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
@@ -42,4 +42,7 @@
fun accessibilityQsShortcutsRepository(
impl: AccessibilityQsShortcutsRepositoryImpl
): AccessibilityQsShortcutsRepository
+
+ @Binds
+ fun magnification(impl: MagnificationImpl): Magnification
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
index 3c4c003..3c0ac9a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
@@ -56,7 +56,7 @@
import java.util.concurrent.Executor;
import java.util.function.Supplier;
-class FullscreenMagnificationController implements ComponentCallbacks {
+public class FullscreenMagnificationController implements ComponentCallbacks {
private static final String TAG = "FullscreenMagnificationController";
private final Context mContext;
@@ -87,7 +87,7 @@
};
private final long mLongAnimationTimeMs;
- FullscreenMagnificationController(
+ public FullscreenMagnificationController(
@UiContext Context context,
@Main Handler handler,
@Main Executor executor,
@@ -157,7 +157,7 @@
* there is an activation change.
*/
@UiThread
- void onFullscreenMagnificationActivationChanged(boolean activated) {
+ public void onFullscreenMagnificationActivationChanged(boolean activated) {
final boolean changed = (mFullscreenMagnificationActivated != activated);
if (changed) {
mFullscreenMagnificationActivated = activated;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
index e22a4e4..48e5f0b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
@@ -16,618 +16,54 @@
package com.android.systemui.accessibility;
-import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
-import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
-import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
-
-import static com.android.systemui.accessibility.AccessibilityLogger.MagnificationSettingsEvent;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
-
import android.annotation.MainThread;
import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.Rect;
-import android.hardware.display.DisplayManager;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.util.SparseArray;
-import android.view.Display;
-import android.view.IWindowManager;
-import android.view.SurfaceControl;
-import android.view.SurfaceControlViewHost;
-import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.IMagnificationConnection;
import android.view.accessibility.IRemoteMagnificationAnimationCallback;
-import android.window.InputTransferToken;
-import androidx.annotation.NonNull;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.CoreStartable;
-import com.android.systemui.Flags;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.settings.DisplayTracker;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.util.settings.SecureSettings;
-import java.io.PrintWriter;
-import java.util.concurrent.Executor;
-import java.util.function.Supplier;
-
-import javax.inject.Inject;
-
-/**
- * Class to handle the interaction with
- * {@link com.android.server.accessibility.AccessibilityManagerService}. It invokes
- * {@link AccessibilityManager#setMagnificationConnection(IMagnificationConnection)}
- * when {@code IStatusBar#requestWindowMagnificationConnection(boolean)} is called.
- */
-@SysUISingleton
-public class Magnification implements CoreStartable, CommandQueue.Callbacks {
- private static final String TAG = "Magnification";
-
- @VisibleForTesting static final int DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS = 300;
- private static final int MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL = 1;
-
- private final ModeSwitchesController mModeSwitchesController;
- private final Context mContext;
- private final Handler mHandler;
- private final Executor mExecutor;
- private final AccessibilityManager mAccessibilityManager;
- private final CommandQueue mCommandQueue;
- private final OverviewProxyService mOverviewProxyService;
- private final DisplayTracker mDisplayTracker;
- private final AccessibilityLogger mA11yLogger;
-
- private MagnificationConnectionImpl mMagnificationConnectionImpl;
- private SysUiState mSysUiState;
-
- @VisibleForTesting
- SparseArray<SparseArray<Float>> mUsersScales = new SparseArray();
-
- private static class WindowMagnificationControllerSupplier extends
- DisplayIdIndexSupplier<WindowMagnificationController> {
-
- private final Context mContext;
- private final Handler mHandler;
- private final WindowMagnifierCallback mWindowMagnifierCallback;
- private final SysUiState mSysUiState;
- private final SecureSettings mSecureSettings;
-
- WindowMagnificationControllerSupplier(Context context, Handler handler,
- WindowMagnifierCallback windowMagnifierCallback,
- DisplayManager displayManager, SysUiState sysUiState,
- SecureSettings secureSettings) {
- super(displayManager);
- mContext = context;
- mHandler = handler;
- mWindowMagnifierCallback = windowMagnifierCallback;
- mSysUiState = sysUiState;
- mSecureSettings = secureSettings;
- }
-
- @Override
- protected WindowMagnificationController createInstance(Display display) {
- final Context windowContext = mContext.createWindowContext(display,
- Flags.createWindowlessWindowMagnifier()
- ? TYPE_ACCESSIBILITY_OVERLAY
- : TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY,
- /* options */ null);
- windowContext.setTheme(com.android.systemui.res.R.style.Theme_SystemUI);
-
- Supplier<SurfaceControlViewHost> scvhSupplier = () ->
- Flags.createWindowlessWindowMagnifier() ? new SurfaceControlViewHost(mContext,
- mContext.getDisplay(), new InputTransferToken(), TAG) : null;
-
- return new WindowMagnificationController(
- windowContext,
- mHandler,
- new WindowMagnificationAnimationController(windowContext),
- /* mirrorWindowControl= */ null,
- new SurfaceControl.Transaction(),
- mWindowMagnifierCallback,
- mSysUiState,
- mSecureSettings,
- scvhSupplier,
- new SfVsyncFrameCallbackProvider(),
- WindowManagerGlobal::getWindowSession);
- }
- }
-
- @VisibleForTesting
- DisplayIdIndexSupplier<WindowMagnificationController> mWindowMagnificationControllerSupplier;
-
- private static class FullscreenMagnificationControllerSupplier extends
- DisplayIdIndexSupplier<FullscreenMagnificationController> {
-
- private final Context mContext;
- private final Handler mHandler;
- private final Executor mExecutor;
- private final IWindowManager mIWindowManager;
-
- FullscreenMagnificationControllerSupplier(Context context,
- DisplayManager displayManager,
- Handler handler,
- Executor executor, IWindowManager iWindowManager) {
- super(displayManager);
- mContext = context;
- mHandler = handler;
- mExecutor = executor;
- mIWindowManager = iWindowManager;
- }
-
- @Override
- protected FullscreenMagnificationController createInstance(Display display) {
- final Context windowContext = mContext.createWindowContext(display,
- TYPE_ACCESSIBILITY_OVERLAY, /* options */ null);
- Supplier<SurfaceControlViewHost> scvhSupplier = () -> new SurfaceControlViewHost(
- mContext, mContext.getDisplay(), new InputTransferToken(), TAG);
- windowContext.setTheme(com.android.systemui.res.R.style.Theme_SystemUI);
- return new FullscreenMagnificationController(
- windowContext,
- mHandler,
- mExecutor,
- windowContext.getSystemService(AccessibilityManager.class),
- windowContext.getSystemService(WindowManager.class),
- mIWindowManager,
- scvhSupplier);
- }
- }
-
- @VisibleForTesting
- DisplayIdIndexSupplier<FullscreenMagnificationController>
- mFullscreenMagnificationControllerSupplier;
-
- private static class SettingsSupplier extends
- DisplayIdIndexSupplier<MagnificationSettingsController> {
-
- private final Context mContext;
- private final MagnificationSettingsController.Callback mSettingsControllerCallback;
- private final SecureSettings mSecureSettings;
-
- SettingsSupplier(Context context,
- MagnificationSettingsController.Callback settingsControllerCallback,
- DisplayManager displayManager,
- SecureSettings secureSettings) {
- super(displayManager);
- mContext = context;
- mSettingsControllerCallback = settingsControllerCallback;
- mSecureSettings = secureSettings;
- }
-
- @Override
- protected MagnificationSettingsController createInstance(Display display) {
- final Context windowContext = mContext.createWindowContext(display,
- TYPE_ACCESSIBILITY_OVERLAY, /* options */ null);
- windowContext.setTheme(com.android.systemui.res.R.style.Theme_SystemUI);
- return new MagnificationSettingsController(
- windowContext,
- new SfVsyncFrameCallbackProvider(),
- mSettingsControllerCallback,
- mSecureSettings);
- }
- }
-
- @VisibleForTesting
- DisplayIdIndexSupplier<MagnificationSettingsController> mMagnificationSettingsSupplier;
-
- @Inject
- public Magnification(Context context,
- @Main Handler mainHandler, @Main Executor executor,
- CommandQueue commandQueue, ModeSwitchesController modeSwitchesController,
- SysUiState sysUiState, OverviewProxyService overviewProxyService,
- SecureSettings secureSettings, DisplayTracker displayTracker,
- DisplayManager displayManager, AccessibilityLogger a11yLogger,
- IWindowManager iWindowManager) {
- this(context, mainHandler.getLooper(), executor, commandQueue,
- modeSwitchesController, sysUiState, overviewProxyService, secureSettings,
- displayTracker, displayManager, a11yLogger, iWindowManager);
- }
-
- @VisibleForTesting
- public Magnification(Context context, Looper looper, @Main Executor executor,
- CommandQueue commandQueue, ModeSwitchesController modeSwitchesController,
- SysUiState sysUiState, OverviewProxyService overviewProxyService,
- SecureSettings secureSettings, DisplayTracker displayTracker,
- DisplayManager displayManager, AccessibilityLogger a11yLogger,
- IWindowManager iWindowManager) {
- mContext = context;
- mHandler = new Handler(looper) {
- @Override
- public void handleMessage(@NonNull Message msg) {
- if (msg.what == MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL) {
- showMagnificationButtonInternal(msg.arg1, msg.arg2);
- }
- }
- };
- mExecutor = executor;
- mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
- mCommandQueue = commandQueue;
- mModeSwitchesController = modeSwitchesController;
- mSysUiState = sysUiState;
- mOverviewProxyService = overviewProxyService;
- mDisplayTracker = displayTracker;
- mA11yLogger = a11yLogger;
- mWindowMagnificationControllerSupplier = new WindowMagnificationControllerSupplier(context,
- mHandler, mWindowMagnifierCallback,
- displayManager, sysUiState, secureSettings);
- mFullscreenMagnificationControllerSupplier = new FullscreenMagnificationControllerSupplier(
- context, displayManager, mHandler, mExecutor, iWindowManager);
- mMagnificationSettingsSupplier = new SettingsSupplier(context,
- mMagnificationSettingsControllerCallback, displayManager, secureSettings);
-
- mModeSwitchesController.setClickListenerDelegate(
- displayId -> mHandler.post(() -> {
- toggleSettingsPanelVisibility(displayId);
- }));
- }
-
- @Override
- public void start() {
- mCommandQueue.addCallback(this);
- mOverviewProxyService.addCallback(new OverviewProxyService.OverviewProxyListener() {
- @Override
- public void onConnectionChanged(boolean isConnected) {
- if (isConnected) {
- updateSysUiStateFlag();
- }
- }
- });
- }
-
- private void updateSysUiStateFlag() {
- //TODO(b/187510533): support multi-display once SysuiState supports it.
- final WindowMagnificationController controller =
- mWindowMagnificationControllerSupplier.valueAt(
- mDisplayTracker.getDefaultDisplayId());
- if (controller != null) {
- controller.updateSysUIStateFlag();
- } else {
- // The instance is initialized when there is an IPC request. Considering
- // self-crash cases, we need to reset the flag in such situation.
- mSysUiState.setFlag(SYSUI_STATE_MAGNIFICATION_OVERLAP, false)
- .commitUpdate(mDisplayTracker.getDefaultDisplayId());
- }
- }
+/** Interface for managing magnification connection calls from system process. */
+public interface Magnification extends CoreStartable {
@MainThread
- void enableWindowMagnification(int displayId, float scale, float centerX, float centerY,
- float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY,
- @Nullable IRemoteMagnificationAnimationCallback callback) {
- final WindowMagnificationController windowMagnificationController =
- mWindowMagnificationControllerSupplier.get(displayId);
- if (windowMagnificationController != null) {
- windowMagnificationController.enableWindowMagnification(scale, centerX, centerY,
- magnificationFrameOffsetRatioX, magnificationFrameOffsetRatioY, callback);
- }
- }
+ void enableWindowMagnification(
+ int displayId,
+ float scale,
+ float centerX,
+ float centerY,
+ float magnificationFrameOffsetRatioX,
+ float magnificationFrameOffsetRatioY,
+ @Nullable IRemoteMagnificationAnimationCallback callback);
@MainThread
- void setScaleForWindowMagnification(int displayId, float scale) {
- final WindowMagnificationController windowMagnificationController =
- mWindowMagnificationControllerSupplier.get(displayId);
- if (windowMagnificationController != null) {
- windowMagnificationController.setScale(scale);
- }
- }
+ void setScaleForWindowMagnification(int displayId, float scale);
@MainThread
- void moveWindowMagnifier(int displayId, float offsetX, float offsetY) {
- final WindowMagnificationController windowMagnificationcontroller =
- mWindowMagnificationControllerSupplier.get(displayId);
- if (windowMagnificationcontroller != null) {
- windowMagnificationcontroller.moveWindowMagnifier(offsetX, offsetY);
- }
- }
+ void moveWindowMagnifier(int displayId, float offsetX, float offsetY);
@MainThread
- void moveWindowMagnifierToPositionInternal(int displayId, float positionX, float positionY,
- IRemoteMagnificationAnimationCallback callback) {
- final WindowMagnificationController windowMagnificationController =
- mWindowMagnificationControllerSupplier.get(displayId);
- if (windowMagnificationController != null) {
- windowMagnificationController.moveWindowMagnifierToPosition(positionX, positionY,
- callback);
- }
- }
+ void disableWindowMagnification(
+ int displayId, @Nullable IRemoteMagnificationAnimationCallback callback);
@MainThread
- void disableWindowMagnification(int displayId,
- @Nullable IRemoteMagnificationAnimationCallback callback) {
- final WindowMagnificationController windowMagnificationController =
- mWindowMagnificationControllerSupplier.get(displayId);
- if (windowMagnificationController != null) {
- windowMagnificationController.deleteWindowMagnification(callback);
- }
- }
+ void onFullscreenMagnificationActivationChanged(int displayId, boolean activated);
@MainThread
- void onFullscreenMagnificationActivationChanged(int displayId, boolean activated) {
- final FullscreenMagnificationController fullscreenMagnificationController =
- mFullscreenMagnificationControllerSupplier.get(displayId);
- if (fullscreenMagnificationController != null) {
- fullscreenMagnificationController.onFullscreenMagnificationActivationChanged(activated);
- }
- }
+ void showMagnificationButton(int displayId, int magnificationMode);
@MainThread
- void toggleSettingsPanelVisibility(int displayId) {
- final MagnificationSettingsController magnificationSettingsController =
- mMagnificationSettingsSupplier.get(displayId);
- if (magnificationSettingsController != null) {
- magnificationSettingsController.toggleSettingsPanelVisibility();
- }
- }
+ void removeMagnificationButton(int displayId);
@MainThread
- void hideMagnificationSettingsPanel(int displayId) {
- final MagnificationSettingsController magnificationSettingsController =
- mMagnificationSettingsSupplier.get(displayId);
- if (magnificationSettingsController != null) {
- magnificationSettingsController.closeMagnificationSettings();
- }
- }
-
- boolean isMagnificationSettingsPanelShowing(int displayId) {
- final MagnificationSettingsController magnificationSettingsController =
- mMagnificationSettingsSupplier.get(displayId);
- if (magnificationSettingsController != null) {
- return magnificationSettingsController.isMagnificationSettingsShowing();
- }
- return false;
- }
+ void setUserMagnificationScale(int userId, int displayId, float scale);
@MainThread
- void showMagnificationButton(int displayId, int magnificationMode) {
- if (Flags.delayShowMagnificationButton()) {
- if (mHandler.hasMessages(MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL)) {
- return;
- }
- mHandler.sendMessageDelayed(
- mHandler.obtainMessage(
- MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL, displayId, magnificationMode),
- DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS);
- } else {
- showMagnificationButtonInternal(displayId, magnificationMode);
- }
- }
+ void hideMagnificationSettingsPanel(int displayId);
@MainThread
- private void showMagnificationButtonInternal(int displayId, int magnificationMode) {
- // not to show mode switch button if settings panel is already showing to
- // prevent settings panel be covered by the button.
- if (isMagnificationSettingsPanelShowing(displayId)) {
- return;
- }
- mModeSwitchesController.showButton(displayId, magnificationMode);
- }
-
- @MainThread
- void removeMagnificationButton(int displayId) {
- if (Flags.delayShowMagnificationButton()) {
- mHandler.removeMessages(MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL);
- }
- mModeSwitchesController.removeButton(displayId);
- }
-
- @MainThread
- void setUserMagnificationScale(int userId, int displayId, float scale) {
- SparseArray<Float> scales = mUsersScales.get(userId);
- if (scales == null) {
- scales = new SparseArray<>();
- mUsersScales.put(userId, scales);
- }
- if (scales.contains(displayId) && scales.get(displayId) == scale) {
- return;
- }
- scales.put(displayId, scale);
-
- final MagnificationSettingsController magnificationSettingsController =
- mMagnificationSettingsSupplier.get(displayId);
- magnificationSettingsController.setMagnificationScale(scale);
- }
-
- @VisibleForTesting
- final WindowMagnifierCallback mWindowMagnifierCallback = new WindowMagnifierCallback() {
- @Override
- public void onWindowMagnifierBoundsChanged(int displayId, Rect frame) {
- if (mMagnificationConnectionImpl != null) {
- mMagnificationConnectionImpl.onWindowMagnifierBoundsChanged(displayId, frame);
- }
- }
-
- @Override
- public void onSourceBoundsChanged(int displayId, Rect sourceBounds) {
- if (mMagnificationConnectionImpl != null) {
- mMagnificationConnectionImpl.onSourceBoundsChanged(displayId, sourceBounds);
- }
- }
-
- @Override
- public void onPerformScaleAction(int displayId, float scale, boolean updatePersistence) {
- if (mMagnificationConnectionImpl != null) {
- mMagnificationConnectionImpl.onPerformScaleAction(
- displayId, scale, updatePersistence);
- }
- }
-
- @Override
- public void onAccessibilityActionPerformed(int displayId) {
- if (mMagnificationConnectionImpl != null) {
- mMagnificationConnectionImpl.onAccessibilityActionPerformed(displayId);
- }
- }
-
- @Override
- public void onMove(int displayId) {
- if (mMagnificationConnectionImpl != null) {
- mMagnificationConnectionImpl.onMove(displayId);
- }
- }
-
- @Override
- public void onClickSettingsButton(int displayId) {
- mHandler.post(() -> {
- toggleSettingsPanelVisibility(displayId);
- });
- }
- };
-
- @VisibleForTesting
- final MagnificationSettingsController.Callback mMagnificationSettingsControllerCallback =
- new MagnificationSettingsController.Callback() {
- @Override
- public void onSetMagnifierSize(int displayId, int index) {
- mHandler.post(() -> onSetMagnifierSizeInternal(displayId, index));
- mA11yLogger.logWithPosition(
- MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_WINDOW_SIZE_SELECTED,
- index
- );
- }
-
- @Override
- public void onSetDiagonalScrolling(int displayId, boolean enable) {
- mHandler.post(() -> onSetDiagonalScrollingInternal(displayId, enable));
- }
-
- @Override
- public void onEditMagnifierSizeMode(int displayId, boolean enable) {
- mHandler.post(() -> onEditMagnifierSizeModeInternal(displayId, enable));
- mA11yLogger.log(enable
- ?
- MagnificationSettingsEvent
- .MAGNIFICATION_SETTINGS_SIZE_EDITING_ACTIVATED
- : MagnificationSettingsEvent
- .MAGNIFICATION_SETTINGS_SIZE_EDITING_DEACTIVATED);
- }
-
- @Override
- public void onMagnifierScale(int displayId, float scale,
- boolean updatePersistence) {
- if (mMagnificationConnectionImpl != null) {
- mMagnificationConnectionImpl.onPerformScaleAction(
- displayId, scale, updatePersistence);
- }
- mA11yLogger.logThrottled(
- MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_ZOOM_SLIDER_CHANGED
- );
- }
-
- @Override
- public void onModeSwitch(int displayId, int newMode) {
- mHandler.post(() -> onModeSwitchInternal(displayId, newMode));
- }
-
- @Override
- public void onSettingsPanelVisibilityChanged(int displayId, boolean shown) {
- mHandler.post(() -> onSettingsPanelVisibilityChangedInternal(displayId, shown));
- }
- };
-
- @MainThread
- private void onSetMagnifierSizeInternal(int displayId, int index) {
- final WindowMagnificationController windowMagnificationController =
- mWindowMagnificationControllerSupplier.get(displayId);
- if (windowMagnificationController != null) {
- windowMagnificationController.changeMagnificationSize(index);
- }
- }
-
- @MainThread
- private void onSetDiagonalScrollingInternal(int displayId, boolean enable) {
- final WindowMagnificationController windowMagnificationController =
- mWindowMagnificationControllerSupplier.get(displayId);
- if (windowMagnificationController != null) {
- windowMagnificationController.setDiagonalScrolling(enable);
- }
- }
-
- @MainThread
- private void onEditMagnifierSizeModeInternal(int displayId, boolean enable) {
- final WindowMagnificationController windowMagnificationController =
- mWindowMagnificationControllerSupplier.get(displayId);
- if (windowMagnificationController != null && windowMagnificationController.isActivated()) {
- windowMagnificationController.setEditMagnifierSizeMode(enable);
- }
- }
-
- @MainThread
- private void onModeSwitchInternal(int displayId, int newMode) {
- final WindowMagnificationController windowMagnificationController =
- mWindowMagnificationControllerSupplier.get(displayId);
- final boolean isWindowMagnifierActivated = windowMagnificationController.isActivated();
- final boolean isSwitchToWindowMode = (newMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
- final boolean changed = isSwitchToWindowMode ^ isWindowMagnifierActivated;
- if (changed) {
- final MagnificationSettingsController magnificationSettingsController =
- mMagnificationSettingsSupplier.get(displayId);
- if (magnificationSettingsController != null) {
- magnificationSettingsController.closeMagnificationSettings();
- }
- if (mMagnificationConnectionImpl != null) {
- mMagnificationConnectionImpl.onChangeMagnificationMode(displayId, newMode);
- }
- }
- }
-
- @MainThread
- private void onSettingsPanelVisibilityChangedInternal(int displayId, boolean shown) {
- final WindowMagnificationController windowMagnificationController =
- mWindowMagnificationControllerSupplier.get(displayId);
- if (windowMagnificationController != null) {
- boolean isWindowMagnifierActivated = windowMagnificationController.isActivated();
- if (isWindowMagnifierActivated) {
- windowMagnificationController.updateDragHandleResourcesIfNeeded(shown);
- }
-
- if (shown) {
- mA11yLogger.logWithPosition(
- MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_OPENED,
- isWindowMagnifierActivated
- ? ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW
- : ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN
- );
- } else {
- mA11yLogger.log(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_CLOSED);
- }
- }
- }
-
- @Override
- public void requestMagnificationConnection(boolean connect) {
- if (connect) {
- setMagnificationConnection();
- } else {
- clearMagnificationConnection();
- }
- }
-
- @Override
- public void dump(PrintWriter pw, String[] args) {
- pw.println(TAG);
- mWindowMagnificationControllerSupplier.forEach(
- magnificationController -> magnificationController.dump(pw));
- }
-
- private void setMagnificationConnection() {
- if (mMagnificationConnectionImpl == null) {
- mMagnificationConnectionImpl = new MagnificationConnectionImpl(this,
- mHandler);
- }
- mAccessibilityManager.setMagnificationConnection(
- mMagnificationConnectionImpl);
- }
-
- private void clearMagnificationConnection() {
- mAccessibilityManager.setMagnificationConnection(null);
- //TODO: destroy controllers.
- }
+ void moveWindowMagnifierToPosition(
+ int displayId,
+ float positionX,
+ float positionY,
+ IRemoteMagnificationAnimationCallback callback);
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationConnectionImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationConnectionImpl.java
index b5f3aef..c8b53ee 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationConnectionImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationConnectionImpl.java
@@ -32,7 +32,7 @@
*
* @see IMagnificationConnection
*/
-class MagnificationConnectionImpl extends IMagnificationConnection.Stub {
+public class MagnificationConnectionImpl extends IMagnificationConnection.Stub {
private static final String TAG = "WindowMagnificationConnectionImpl";
@@ -40,7 +40,7 @@
private final Magnification mMagnification;
private final Handler mHandler;
- MagnificationConnectionImpl(@NonNull Magnification magnification,
+ public MagnificationConnectionImpl(@NonNull Magnification magnification,
@Main Handler mainHandler) {
mMagnification = magnification;
mHandler = mainHandler;
@@ -83,7 +83,7 @@
@Override
public void moveWindowMagnifierToPosition(int displayId, float positionX, float positionY,
IRemoteMagnificationAnimationCallback callback) {
- mHandler.post(() -> mMagnification.moveWindowMagnifierToPositionInternal(
+ mHandler.post(() -> mMagnification.moveWindowMagnifierToPosition(
displayId, positionX, positionY, callback));
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
new file mode 100644
index 0000000..6e5e44e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
@@ -0,0 +1,641 @@
+/*
+ * 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.systemui.accessibility;
+
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
+import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
+
+import static com.android.systemui.accessibility.AccessibilityLogger.MagnificationSettingsEvent;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
+
+import android.annotation.MainThread;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.IWindowManager;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.IMagnificationConnection;
+import android.view.accessibility.IRemoteMagnificationAnimationCallback;
+import android.window.InputTransferToken;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.systemui.Flags;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.model.SysUiState;
+import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.DisplayTracker;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.util.settings.SecureSettings;
+
+import java.io.PrintWriter;
+import java.util.concurrent.Executor;
+import java.util.function.Supplier;
+
+import javax.inject.Inject;
+
+/**
+ * Class to handle the interaction with
+ * {@link com.android.server.accessibility.AccessibilityManagerService}. It invokes
+ * {@link AccessibilityManager#setMagnificationConnection(IMagnificationConnection)}
+ * when {@code IStatusBar#requestWindowMagnificationConnection(boolean)} is called.
+ */
+@SysUISingleton
+public class MagnificationImpl implements Magnification, CommandQueue.Callbacks {
+ private static final String TAG = "Magnification";
+
+ @VisibleForTesting static final int DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS = 300;
+ private static final int MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL = 1;
+
+ private final ModeSwitchesController mModeSwitchesController;
+ private final Handler mHandler;
+ private final Executor mExecutor;
+ private final AccessibilityManager mAccessibilityManager;
+ private final CommandQueue mCommandQueue;
+ private final OverviewProxyService mOverviewProxyService;
+ private final DisplayTracker mDisplayTracker;
+ private final AccessibilityLogger mA11yLogger;
+
+ private MagnificationConnectionImpl mMagnificationConnectionImpl;
+ private SysUiState mSysUiState;
+
+ @VisibleForTesting
+ SparseArray<SparseArray<Float>> mUsersScales = new SparseArray();
+
+ private static class WindowMagnificationControllerSupplier extends
+ DisplayIdIndexSupplier<WindowMagnificationController> {
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final WindowMagnifierCallback mWindowMagnifierCallback;
+ private final SysUiState mSysUiState;
+ private final SecureSettings mSecureSettings;
+
+ WindowMagnificationControllerSupplier(Context context, Handler handler,
+ WindowMagnifierCallback windowMagnifierCallback,
+ DisplayManager displayManager, SysUiState sysUiState,
+ SecureSettings secureSettings) {
+ super(displayManager);
+ mContext = context;
+ mHandler = handler;
+ mWindowMagnifierCallback = windowMagnifierCallback;
+ mSysUiState = sysUiState;
+ mSecureSettings = secureSettings;
+ }
+
+ @Override
+ protected WindowMagnificationController createInstance(Display display) {
+ final Context windowContext = mContext.createWindowContext(display,
+ Flags.createWindowlessWindowMagnifier()
+ ? TYPE_ACCESSIBILITY_OVERLAY
+ : TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY,
+ /* options */ null);
+ windowContext.setTheme(com.android.systemui.res.R.style.Theme_SystemUI);
+
+ Supplier<SurfaceControlViewHost> scvhSupplier = () ->
+ Flags.createWindowlessWindowMagnifier() ? new SurfaceControlViewHost(mContext,
+ mContext.getDisplay(), new InputTransferToken(), TAG) : null;
+
+ return new WindowMagnificationController(
+ windowContext,
+ mHandler,
+ new WindowMagnificationAnimationController(windowContext),
+ /* mirrorWindowControl= */ null,
+ new SurfaceControl.Transaction(),
+ mWindowMagnifierCallback,
+ mSysUiState,
+ mSecureSettings,
+ scvhSupplier,
+ new SfVsyncFrameCallbackProvider(),
+ WindowManagerGlobal::getWindowSession);
+ }
+ }
+
+ @VisibleForTesting
+ DisplayIdIndexSupplier<WindowMagnificationController> mWindowMagnificationControllerSupplier;
+
+ private static class FullscreenMagnificationControllerSupplier extends
+ DisplayIdIndexSupplier<FullscreenMagnificationController> {
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final Executor mExecutor;
+ private final IWindowManager mIWindowManager;
+
+ FullscreenMagnificationControllerSupplier(Context context,
+ DisplayManager displayManager,
+ Handler handler,
+ Executor executor, IWindowManager iWindowManager) {
+ super(displayManager);
+ mContext = context;
+ mHandler = handler;
+ mExecutor = executor;
+ mIWindowManager = iWindowManager;
+ }
+
+ @Override
+ protected FullscreenMagnificationController createInstance(Display display) {
+ final Context windowContext = mContext.createWindowContext(display,
+ TYPE_ACCESSIBILITY_OVERLAY, /* options */ null);
+ Supplier<SurfaceControlViewHost> scvhSupplier = () -> new SurfaceControlViewHost(
+ mContext, mContext.getDisplay(), new InputTransferToken(), TAG);
+ windowContext.setTheme(com.android.systemui.res.R.style.Theme_SystemUI);
+ return new FullscreenMagnificationController(
+ windowContext,
+ mHandler,
+ mExecutor,
+ windowContext.getSystemService(AccessibilityManager.class),
+ windowContext.getSystemService(WindowManager.class),
+ mIWindowManager,
+ scvhSupplier);
+ }
+ }
+
+ @VisibleForTesting
+ DisplayIdIndexSupplier<FullscreenMagnificationController>
+ mFullscreenMagnificationControllerSupplier;
+
+ private static class SettingsSupplier extends
+ DisplayIdIndexSupplier<MagnificationSettingsController> {
+
+ private final Context mContext;
+ private final MagnificationSettingsController.Callback mSettingsControllerCallback;
+ private final SecureSettings mSecureSettings;
+
+ SettingsSupplier(Context context,
+ MagnificationSettingsController.Callback settingsControllerCallback,
+ DisplayManager displayManager,
+ SecureSettings secureSettings) {
+ super(displayManager);
+ mContext = context;
+ mSettingsControllerCallback = settingsControllerCallback;
+ mSecureSettings = secureSettings;
+ }
+
+ @Override
+ protected MagnificationSettingsController createInstance(Display display) {
+ final Context windowContext = mContext.createWindowContext(display,
+ TYPE_ACCESSIBILITY_OVERLAY, /* options */ null);
+ windowContext.setTheme(com.android.systemui.res.R.style.Theme_SystemUI);
+ return new MagnificationSettingsController(
+ windowContext,
+ new SfVsyncFrameCallbackProvider(),
+ mSettingsControllerCallback,
+ mSecureSettings);
+ }
+ }
+
+ @VisibleForTesting
+ DisplayIdIndexSupplier<MagnificationSettingsController> mMagnificationSettingsSupplier;
+
+ @Inject
+ public MagnificationImpl(Context context,
+ @Main Handler mainHandler, @Main Executor executor,
+ CommandQueue commandQueue, ModeSwitchesController modeSwitchesController,
+ SysUiState sysUiState, OverviewProxyService overviewProxyService,
+ SecureSettings secureSettings, DisplayTracker displayTracker,
+ DisplayManager displayManager, AccessibilityLogger a11yLogger,
+ IWindowManager iWindowManager, AccessibilityManager accessibilityManager) {
+ this(context, mainHandler.getLooper(), executor, commandQueue,
+ modeSwitchesController, sysUiState, overviewProxyService, secureSettings,
+ displayTracker, displayManager, a11yLogger, iWindowManager, accessibilityManager);
+ }
+
+ @VisibleForTesting
+ public MagnificationImpl(Context context, Looper looper, @Main Executor executor,
+ CommandQueue commandQueue, ModeSwitchesController modeSwitchesController,
+ SysUiState sysUiState, OverviewProxyService overviewProxyService,
+ SecureSettings secureSettings, DisplayTracker displayTracker,
+ DisplayManager displayManager, AccessibilityLogger a11yLogger,
+ IWindowManager iWindowManager,
+ AccessibilityManager accessibilityManager) {
+ mHandler = new Handler(looper) {
+ @Override
+ public void handleMessage(@NonNull Message msg) {
+ if (msg.what == MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL) {
+ showMagnificationButtonInternal(msg.arg1, msg.arg2);
+ }
+ }
+ };
+ mExecutor = executor;
+ mAccessibilityManager = accessibilityManager;
+ mCommandQueue = commandQueue;
+ mModeSwitchesController = modeSwitchesController;
+ mSysUiState = sysUiState;
+ mOverviewProxyService = overviewProxyService;
+ mDisplayTracker = displayTracker;
+ mA11yLogger = a11yLogger;
+ mWindowMagnificationControllerSupplier = new WindowMagnificationControllerSupplier(context,
+ mHandler, mWindowMagnifierCallback,
+ displayManager, sysUiState, secureSettings);
+ mFullscreenMagnificationControllerSupplier = new FullscreenMagnificationControllerSupplier(
+ context, displayManager, mHandler, mExecutor, iWindowManager);
+ mMagnificationSettingsSupplier = new SettingsSupplier(context,
+ mMagnificationSettingsControllerCallback, displayManager, secureSettings);
+
+ mModeSwitchesController.setClickListenerDelegate(
+ displayId -> mHandler.post(() -> {
+ toggleSettingsPanelVisibility(displayId);
+ }));
+ }
+
+ @Override
+ public void start() {
+ mCommandQueue.addCallback(this);
+ mOverviewProxyService.addCallback(new OverviewProxyService.OverviewProxyListener() {
+ @Override
+ public void onConnectionChanged(boolean isConnected) {
+ if (isConnected) {
+ updateSysUiStateFlag();
+ }
+ }
+ });
+ }
+
+ private void updateSysUiStateFlag() {
+ //TODO(b/187510533): support multi-display once SysuiState supports it.
+ final WindowMagnificationController controller =
+ mWindowMagnificationControllerSupplier.valueAt(
+ mDisplayTracker.getDefaultDisplayId());
+ if (controller != null) {
+ controller.updateSysUIStateFlag();
+ } else {
+ // The instance is initialized when there is an IPC request. Considering
+ // self-crash cases, we need to reset the flag in such situation.
+ mSysUiState.setFlag(SYSUI_STATE_MAGNIFICATION_OVERLAP, false)
+ .commitUpdate(mDisplayTracker.getDefaultDisplayId());
+ }
+ }
+
+ @Override
+ @MainThread
+ public void enableWindowMagnification(int displayId, float scale, float centerX, float centerY,
+ float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY,
+ @Nullable IRemoteMagnificationAnimationCallback callback) {
+ final WindowMagnificationController windowMagnificationController =
+ mWindowMagnificationControllerSupplier.get(displayId);
+ if (windowMagnificationController != null) {
+ windowMagnificationController.enableWindowMagnification(scale, centerX, centerY,
+ magnificationFrameOffsetRatioX, magnificationFrameOffsetRatioY, callback);
+ }
+ }
+
+ @Override
+ @MainThread
+ public void setScaleForWindowMagnification(int displayId, float scale) {
+ final WindowMagnificationController windowMagnificationController =
+ mWindowMagnificationControllerSupplier.get(displayId);
+ if (windowMagnificationController != null) {
+ windowMagnificationController.setScale(scale);
+ }
+ }
+
+ @Override
+ @MainThread
+ public void moveWindowMagnifier(int displayId, float offsetX, float offsetY) {
+ final WindowMagnificationController windowMagnificationcontroller =
+ mWindowMagnificationControllerSupplier.get(displayId);
+ if (windowMagnificationcontroller != null) {
+ windowMagnificationcontroller.moveWindowMagnifier(offsetX, offsetY);
+ }
+ }
+
+ @Override
+ @MainThread
+ public void moveWindowMagnifierToPosition(int displayId, float positionX, float positionY,
+ IRemoteMagnificationAnimationCallback callback) {
+ final WindowMagnificationController windowMagnificationController =
+ mWindowMagnificationControllerSupplier.get(displayId);
+ if (windowMagnificationController != null) {
+ windowMagnificationController.moveWindowMagnifierToPosition(positionX, positionY,
+ callback);
+ }
+ }
+
+ @Override
+ @MainThread
+ public void disableWindowMagnification(int displayId,
+ @Nullable IRemoteMagnificationAnimationCallback callback) {
+ final WindowMagnificationController windowMagnificationController =
+ mWindowMagnificationControllerSupplier.get(displayId);
+ if (windowMagnificationController != null) {
+ windowMagnificationController.deleteWindowMagnification(callback);
+ }
+ }
+
+ @Override
+ @MainThread
+ public void onFullscreenMagnificationActivationChanged(int displayId, boolean activated) {
+ final FullscreenMagnificationController fullscreenMagnificationController =
+ mFullscreenMagnificationControllerSupplier.get(displayId);
+ if (fullscreenMagnificationController != null) {
+ fullscreenMagnificationController.onFullscreenMagnificationActivationChanged(activated);
+ }
+ }
+
+ @MainThread
+ void toggleSettingsPanelVisibility(int displayId) {
+ final MagnificationSettingsController magnificationSettingsController =
+ mMagnificationSettingsSupplier.get(displayId);
+ if (magnificationSettingsController != null) {
+ magnificationSettingsController.toggleSettingsPanelVisibility();
+ }
+ }
+
+ @Override
+ @MainThread
+ public void hideMagnificationSettingsPanel(int displayId) {
+ final MagnificationSettingsController magnificationSettingsController =
+ mMagnificationSettingsSupplier.get(displayId);
+ if (magnificationSettingsController != null) {
+ magnificationSettingsController.closeMagnificationSettings();
+ }
+ }
+
+ boolean isMagnificationSettingsPanelShowing(int displayId) {
+ final MagnificationSettingsController magnificationSettingsController =
+ mMagnificationSettingsSupplier.get(displayId);
+ if (magnificationSettingsController != null) {
+ return magnificationSettingsController.isMagnificationSettingsShowing();
+ }
+ return false;
+ }
+
+ @Override
+ @MainThread
+ public void showMagnificationButton(int displayId, int magnificationMode) {
+ if (Flags.delayShowMagnificationButton()) {
+ if (mHandler.hasMessages(MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL)) {
+ return;
+ }
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(
+ MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL, displayId, magnificationMode),
+ DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS);
+ } else {
+ showMagnificationButtonInternal(displayId, magnificationMode);
+ }
+ }
+
+ @MainThread
+ private void showMagnificationButtonInternal(int displayId, int magnificationMode) {
+ // not to show mode switch button if settings panel is already showing to
+ // prevent settings panel be covered by the button.
+ if (isMagnificationSettingsPanelShowing(displayId)) {
+ return;
+ }
+ mModeSwitchesController.showButton(displayId, magnificationMode);
+ }
+
+ @Override
+ @MainThread
+ public void removeMagnificationButton(int displayId) {
+ if (Flags.delayShowMagnificationButton()) {
+ mHandler.removeMessages(MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL);
+ }
+ mModeSwitchesController.removeButton(displayId);
+ }
+
+ @Override
+ @MainThread
+ public void setUserMagnificationScale(int userId, int displayId, float scale) {
+ SparseArray<Float> scales = mUsersScales.get(userId);
+ if (scales == null) {
+ scales = new SparseArray<>();
+ mUsersScales.put(userId, scales);
+ }
+ if (scales.contains(displayId) && scales.get(displayId) == scale) {
+ return;
+ }
+ scales.put(displayId, scale);
+
+ final MagnificationSettingsController magnificationSettingsController =
+ mMagnificationSettingsSupplier.get(displayId);
+ magnificationSettingsController.setMagnificationScale(scale);
+ }
+
+ @VisibleForTesting
+ final WindowMagnifierCallback mWindowMagnifierCallback = new WindowMagnifierCallback() {
+ @Override
+ public void onWindowMagnifierBoundsChanged(int displayId, Rect frame) {
+ if (mMagnificationConnectionImpl != null) {
+ mMagnificationConnectionImpl.onWindowMagnifierBoundsChanged(displayId, frame);
+ }
+ }
+
+ @Override
+ public void onSourceBoundsChanged(int displayId, Rect sourceBounds) {
+ if (mMagnificationConnectionImpl != null) {
+ mMagnificationConnectionImpl.onSourceBoundsChanged(displayId, sourceBounds);
+ }
+ }
+
+ @Override
+ public void onPerformScaleAction(int displayId, float scale, boolean updatePersistence) {
+ if (mMagnificationConnectionImpl != null) {
+ mMagnificationConnectionImpl.onPerformScaleAction(
+ displayId, scale, updatePersistence);
+ }
+ }
+
+ @Override
+ public void onAccessibilityActionPerformed(int displayId) {
+ if (mMagnificationConnectionImpl != null) {
+ mMagnificationConnectionImpl.onAccessibilityActionPerformed(displayId);
+ }
+ }
+
+ @Override
+ public void onMove(int displayId) {
+ if (mMagnificationConnectionImpl != null) {
+ mMagnificationConnectionImpl.onMove(displayId);
+ }
+ }
+
+ @Override
+ public void onClickSettingsButton(int displayId) {
+ mHandler.post(() -> {
+ toggleSettingsPanelVisibility(displayId);
+ });
+ }
+ };
+
+ @VisibleForTesting
+ final MagnificationSettingsController.Callback mMagnificationSettingsControllerCallback =
+ new MagnificationSettingsController.Callback() {
+ @Override
+ public void onSetMagnifierSize(int displayId, int index) {
+ mHandler.post(() -> onSetMagnifierSizeInternal(displayId, index));
+ mA11yLogger.logWithPosition(
+ MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_WINDOW_SIZE_SELECTED,
+ index
+ );
+ }
+
+ @Override
+ public void onSetDiagonalScrolling(int displayId, boolean enable) {
+ mHandler.post(() -> onSetDiagonalScrollingInternal(displayId, enable));
+ }
+
+ @Override
+ public void onEditMagnifierSizeMode(int displayId, boolean enable) {
+ mHandler.post(() -> onEditMagnifierSizeModeInternal(displayId, enable));
+ mA11yLogger.log(enable
+ ?
+ MagnificationSettingsEvent
+ .MAGNIFICATION_SETTINGS_SIZE_EDITING_ACTIVATED
+ : MagnificationSettingsEvent
+ .MAGNIFICATION_SETTINGS_SIZE_EDITING_DEACTIVATED);
+ }
+
+ @Override
+ public void onMagnifierScale(int displayId, float scale,
+ boolean updatePersistence) {
+ if (mMagnificationConnectionImpl != null) {
+ mMagnificationConnectionImpl.onPerformScaleAction(
+ displayId, scale, updatePersistence);
+ }
+ mA11yLogger.logThrottled(
+ MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_ZOOM_SLIDER_CHANGED
+ );
+ }
+
+ @Override
+ public void onModeSwitch(int displayId, int newMode) {
+ mHandler.post(() -> onModeSwitchInternal(displayId, newMode));
+ }
+
+ @Override
+ public void onSettingsPanelVisibilityChanged(int displayId, boolean shown) {
+ mHandler.post(() -> onSettingsPanelVisibilityChangedInternal(displayId, shown));
+ }
+ };
+
+ @MainThread
+ private void onSetMagnifierSizeInternal(int displayId, int index) {
+ final WindowMagnificationController windowMagnificationController =
+ mWindowMagnificationControllerSupplier.get(displayId);
+ if (windowMagnificationController != null) {
+ windowMagnificationController.changeMagnificationSize(index);
+ }
+ }
+
+ @MainThread
+ private void onSetDiagonalScrollingInternal(int displayId, boolean enable) {
+ final WindowMagnificationController windowMagnificationController =
+ mWindowMagnificationControllerSupplier.get(displayId);
+ if (windowMagnificationController != null) {
+ windowMagnificationController.setDiagonalScrolling(enable);
+ }
+ }
+
+ @MainThread
+ private void onEditMagnifierSizeModeInternal(int displayId, boolean enable) {
+ final WindowMagnificationController windowMagnificationController =
+ mWindowMagnificationControllerSupplier.get(displayId);
+ if (windowMagnificationController != null && windowMagnificationController.isActivated()) {
+ windowMagnificationController.setEditMagnifierSizeMode(enable);
+ }
+ }
+
+ @MainThread
+ private void onModeSwitchInternal(int displayId, int newMode) {
+ final WindowMagnificationController windowMagnificationController =
+ mWindowMagnificationControllerSupplier.get(displayId);
+ final boolean isWindowMagnifierActivated = windowMagnificationController.isActivated();
+ final boolean isSwitchToWindowMode = (newMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ final boolean changed = isSwitchToWindowMode ^ isWindowMagnifierActivated;
+ if (changed) {
+ final MagnificationSettingsController magnificationSettingsController =
+ mMagnificationSettingsSupplier.get(displayId);
+ if (magnificationSettingsController != null) {
+ magnificationSettingsController.closeMagnificationSettings();
+ }
+ if (mMagnificationConnectionImpl != null) {
+ mMagnificationConnectionImpl.onChangeMagnificationMode(displayId, newMode);
+ }
+ }
+ }
+
+ @MainThread
+ private void onSettingsPanelVisibilityChangedInternal(int displayId, boolean shown) {
+ final WindowMagnificationController windowMagnificationController =
+ mWindowMagnificationControllerSupplier.get(displayId);
+ if (windowMagnificationController != null) {
+ boolean isWindowMagnifierActivated = windowMagnificationController.isActivated();
+ if (isWindowMagnifierActivated) {
+ windowMagnificationController.updateDragHandleResourcesIfNeeded(shown);
+ }
+
+ if (shown) {
+ mA11yLogger.logWithPosition(
+ MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_OPENED,
+ isWindowMagnifierActivated
+ ? ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW
+ : ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN
+ );
+ } else {
+ mA11yLogger.log(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_CLOSED);
+ }
+ }
+ }
+
+ @Override
+ public void requestMagnificationConnection(boolean connect) {
+ if (connect) {
+ setMagnificationConnection();
+ } else {
+ clearMagnificationConnection();
+ }
+ }
+
+ @Override
+ public void dump(PrintWriter pw, String[] args) {
+ pw.println(TAG);
+ mWindowMagnificationControllerSupplier.forEach(
+ magnificationController -> magnificationController.dump(pw));
+ }
+
+ private void setMagnificationConnection() {
+ if (mMagnificationConnectionImpl == null) {
+ mMagnificationConnectionImpl = new MagnificationConnectionImpl(this,
+ mHandler);
+ }
+ mAccessibilityManager.setMagnificationConnection(
+ mMagnificationConnectionImpl);
+ }
+
+ private void clearMagnificationConnection() {
+ mAccessibilityManager.setMagnificationConnection(null);
+ //TODO: destroy controllers.
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index ac8807d..3904ee1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -50,6 +50,7 @@
import com.android.systemui.biometrics.shared.model.BiometricModality
import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.biometrics.shared.model.PromptKind
+import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
import com.android.systemui.res.R
@@ -132,11 +133,11 @@
R.dimen.biometric_prompt_landscape_medium_horizontal_padding
)
+ val udfpsOverlayParams: StateFlow<UdfpsOverlayParams> =
+ udfpsOverlayInteractor.udfpsOverlayParams
+
private val udfpsSensorBounds: Flow<Rect> =
- combine(
- udfpsOverlayInteractor.udfpsOverlayParams,
- displayStateInteractor.currentRotation
- ) { params, rotation ->
+ combine(udfpsOverlayParams, displayStateInteractor.currentRotation) { params, rotation ->
val rotatedBounds = Rect(params.sensorBounds)
RotationUtils.rotateBounds(
rotatedBounds,
@@ -153,32 +154,27 @@
}
.distinctUntilChanged()
+ private val udfpsSensorWidth: Flow<Int> = udfpsOverlayParams.map { it.sensorBounds.width() }
+ private val udfpsSensorHeight: Flow<Int> = udfpsOverlayParams.map { it.sensorBounds.height() }
+
val legacyFingerprintSensorWidth: Flow<Int> =
- combine(modalities, udfpsOverlayInteractor.udfpsOverlayParams) { modalities, overlayParams
- ->
+ combine(modalities, udfpsSensorWidth) { modalities, udfpsSensorWidth ->
if (modalities.hasUdfps) {
- overlayParams.sensorBounds.width()
+ udfpsSensorWidth
} else {
fingerprintIconWidth
}
}
val legacyFingerprintSensorHeight: Flow<Int> =
- combine(modalities, udfpsOverlayInteractor.udfpsOverlayParams) { modalities, overlayParams
- ->
+ combine(modalities, udfpsSensorHeight) { modalities, udfpsSensorHeight ->
if (modalities.hasUdfps) {
- overlayParams.sensorBounds.height()
+ udfpsSensorHeight
} else {
fingerprintIconHeight
}
}
- val fingerprintSensorWidth: Int =
- udfpsOverlayInteractor.udfpsOverlayParams.value.sensorBounds.width()
-
- val fingerprintSensorHeight: Int =
- udfpsOverlayInteractor.udfpsOverlayParams.value.sensorBounds.height()
-
private val _accessibilityHint = MutableSharedFlow<String>()
/** Hint for talkback directional guidance */
@@ -442,12 +438,16 @@
/** The size of the biometric icon */
val iconSize: Flow<Pair<Int, Int>> =
- combine(iconViewModel.activeAuthType, modalities) { activeAuthType, modalities ->
+ combine(iconViewModel.activeAuthType, modalities, udfpsSensorWidth, udfpsSensorHeight) {
+ activeAuthType,
+ modalities,
+ udfpsSensorWidth,
+ udfpsSensorHeight ->
if (activeAuthType == PromptIconViewModel.AuthType.Face) {
Pair(faceIconWidth, faceIconHeight)
} else {
if (modalities.hasUdfps) {
- Pair(fingerprintSensorWidth, fingerprintSensorHeight)
+ Pair(udfpsSensorWidth, udfpsSensorHeight)
} else {
Pair(fingerprintIconWidth, fingerprintIconHeight)
}
@@ -921,13 +921,13 @@
udfpsUtils.getTouchInNativeCoordinates(
event.getPointerId(0),
event,
- udfpsOverlayInteractor.udfpsOverlayParams.value
+ udfpsOverlayParams.value
)
if (
!udfpsUtils.isWithinSensorArea(
event.getPointerId(0),
event,
- udfpsOverlayInteractor.udfpsOverlayParams.value
+ udfpsOverlayParams.value
)
) {
_accessibilityHint.emit(
@@ -936,7 +936,7 @@
context,
scaledTouch.x,
scaledTouch.y,
- udfpsOverlayInteractor.udfpsOverlayParams.value
+ udfpsOverlayParams.value
)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index cc90730..6ec6ec1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -129,7 +129,6 @@
/** Called as the UI requests opening the widget editor with an optional preselected widget. */
open fun onOpenWidgetEditor(
- preselectedKey: String? = null,
shouldOpenWidgetPickerOnStart: Boolean = false,
) {}
@@ -146,7 +145,7 @@
open fun onReorderWidgetCancel() {}
/** Called as the user request to show the customize widget button. */
- open fun onShowCustomizeWidgetButton() {}
+ open fun onLongClick() {}
/** Set the key of the currently selected item */
fun setSelectedKey(key: String?) {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 11247df..2043dd1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -40,6 +40,7 @@
import com.android.systemui.media.dagger.MediaModule
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.KeyguardIndicationController
import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
@@ -76,9 +77,10 @@
@Main private val resources: Resources,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
keyguardInteractor: KeyguardInteractor,
+ private val keyguardIndicationController: KeyguardIndicationController,
communalSceneInteractor: CommunalSceneInteractor,
private val communalInteractor: CommunalInteractor,
- private val communalSettingsInteractor: CommunalSettingsInteractor,
+ communalSettingsInteractor: CommunalSettingsInteractor,
tutorialInteractor: CommunalTutorialInteractor,
private val shadeInteractor: ShadeInteractor,
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
@@ -218,9 +220,8 @@
}
override fun onOpenWidgetEditor(
- preselectedKey: String?,
shouldOpenWidgetPickerOnStart: Boolean,
- ) = communalInteractor.showWidgetEditor(preselectedKey, shouldOpenWidgetPickerOnStart)
+ ) = communalInteractor.showWidgetEditor(selectedKey.value, shouldOpenWidgetPickerOnStart)
override fun onDismissCtaTile() {
scope.launch {
@@ -229,7 +230,11 @@
}
}
- override fun onShowCustomizeWidgetButton() {
+ fun onClick() {
+ keyguardIndicationController.showActionToUnlock()
+ }
+
+ override fun onLongClick() {
setCurrentPopupType(PopupType.CustomizeWidgetButton)
}
@@ -319,7 +324,7 @@
}
sealed class PopupType {
- object CtaTile : PopupType()
+ data object CtaTile : PopupType()
- object CustomizeWidgetButton : PopupType()
+ data object CustomizeWidgetButton : PopupType()
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 9f0fc51..9ae63a1 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -25,7 +25,9 @@
import com.android.keyguard.KeyguardViewController;
import com.android.systemui.CoreStartable;
import com.android.systemui.ScreenDecorationsModule;
+import com.android.systemui.accessibility.AccessibilityModule;
import com.android.systemui.accessibility.SystemActionsModule;
+import com.android.systemui.accessibility.data.repository.AccessibilityRepositoryModule;
import com.android.systemui.battery.BatterySaverModule;
import com.android.systemui.display.ui.viewmodel.ConnectingDisplayViewModel;
import com.android.systemui.dock.DockManager;
@@ -107,6 +109,8 @@
* SystemUI code that variants of SystemUI _must_ include to function correctly.
*/
@Module(includes = {
+ AccessibilityModule.class,
+ AccessibilityRepositoryModule.class,
AospPolicyModule.class,
BatterySaverModule.class,
CollapsedStatusBarFragmentStartableModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index a7ff3c3..4b2fb44 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -32,8 +32,6 @@
import com.android.systemui.CameraProtectionModule;
import com.android.systemui.CoreStartable;
import com.android.systemui.SystemUISecondaryUserService;
-import com.android.systemui.accessibility.AccessibilityModule;
-import com.android.systemui.accessibility.data.repository.AccessibilityRepositoryModule;
import com.android.systemui.ambient.dagger.AmbientModule;
import com.android.systemui.appops.dagger.AppOpsModule;
import com.android.systemui.assist.AssistModule;
@@ -187,8 +185,6 @@
* may not appreciate that.
*/
@Module(includes = {
- AccessibilityModule.class,
- AccessibilityRepositoryModule.class,
AmbientModule.class,
AppOpsModule.class,
AssistModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 10d6881..4c7142b 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -211,6 +211,9 @@
}
}
+ /** Whether the device is in lockdown mode, where bouncer input is required to unlock. */
+ val isInLockdown: Flow<Boolean> = deviceEntryRestrictionReason.map { it.isInLockdown() }
+
/**
* Attempt to enter the device and dismiss the lockscreen. If authentication is required to
* unlock the device it will transition to bouncer.
@@ -259,6 +262,27 @@
return repository.isLockscreenEnabled()
}
+ fun DeviceEntryRestrictionReason?.isInLockdown(): Boolean {
+ return when (this) {
+ DeviceEntryRestrictionReason.UserLockdown -> true
+ DeviceEntryRestrictionReason.PolicyLockdown -> true
+
+ // Add individual enum value instead of using "else" so new reasons are guaranteed
+ // to be added here at compile-time.
+ null -> false
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot -> false
+ DeviceEntryRestrictionReason.BouncerLockedOut -> false
+ DeviceEntryRestrictionReason.AdaptiveAuthRequest -> false
+ DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout -> false
+ DeviceEntryRestrictionReason.TrustAgentDisabled -> false
+ DeviceEntryRestrictionReason.StrongBiometricsLockedOut -> false
+ DeviceEntryRestrictionReason.SecurityTimeout -> false
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate -> false
+ DeviceEntryRestrictionReason.UnattendedUpdate -> false
+ DeviceEntryRestrictionReason.NonStrongFaceLockedOut -> false
+ }
+ }
+
/**
* Whether lockscreen bypass is enabled. When enabled, the lockscreen will be automatically
* dismissed once the authentication challenge is completed. For example, completing a biometric
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index cdf3b06..888d047 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -39,6 +39,7 @@
import com.android.systemui.keyguard.shared.model.KeyguardDone
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.time.SystemClock
@@ -293,6 +294,15 @@
/** Sets whether the keyguard is enabled (see [isKeyguardEnabled]). */
fun setKeyguardEnabled(enabled: Boolean)
+
+ /** @see isShowKeyguardWhenReenabled */
+ fun setShowKeyguardWhenReenabled(isShowKeyguardWhenReenabled: Boolean)
+
+ /**
+ * Returns `true` if the keyguard should be re-shown once it becomes re-enabled again; `false`
+ * otherwise.
+ */
+ fun isShowKeyguardWhenReenabled(): Boolean
}
/** Encapsulates application state for the keyguard. */
@@ -474,6 +484,8 @@
private val _isDozing = MutableStateFlow(statusBarStateController.isDozing)
override val isDozing: StateFlow<Boolean> = _isDozing.asStateFlow()
+ private var isShowKeyguardWhenReenabled: Boolean = false
+
override fun setIsDozing(isDozing: Boolean) {
_isDozing.value = isDozing
}
@@ -692,6 +704,16 @@
_isKeyguardEnabled.value = enabled
}
+ override fun setShowKeyguardWhenReenabled(isShowKeyguardWhenReenabled: Boolean) {
+ SceneContainerFlag.assertInNewMode()
+ this.isShowKeyguardWhenReenabled = isShowKeyguardWhenReenabled
+ }
+
+ override fun isShowKeyguardWhenReenabled(): Boolean {
+ SceneContainerFlag.assertInNewMode()
+ return isShowKeyguardWhenReenabled
+ }
+
private fun statusBarStateIntToObject(value: Int): StatusBarState {
return when (value) {
0 -> StatusBarState.SHADE
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 2f40c99..506a18d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -232,5 +232,6 @@
val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION
val TO_GONE_DURATION = DEFAULT_DURATION
val TO_PRIMARY_BOUNCER_DURATION = DEFAULT_DURATION
+ val TO_GLANCEABLE_HUB_DURATION = DEFAULT_DURATION
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index f5b12a2..07a2b04 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -99,19 +99,21 @@
}
}
- scope.launch {
- keyguardRepository.isKeyguardEnabled
- .filterRelevantKeyguardStateAnd { enabled -> enabled }
- .sample(keyguardEnabledInteractor.showKeyguardWhenReenabled)
- .filter { reshow -> reshow }
- .collect {
- startTransitionTo(
- KeyguardState.LOCKSCREEN,
- ownerReason =
- "Keyguard was re-enabled, and we weren't GONE when it " +
- "was originally disabled"
- )
- }
+ if (!SceneContainerFlag.isEnabled) {
+ scope.launch {
+ keyguardRepository.isKeyguardEnabled
+ .filterRelevantKeyguardStateAnd { enabled -> enabled }
+ .sample(keyguardEnabledInteractor.showKeyguardWhenReenabled)
+ .filter { reshow -> reshow }
+ .collect {
+ startTransitionTo(
+ KeyguardState.LOCKSCREEN,
+ ownerReason =
+ "Keyguard was re-enabled, and we weren't GONE when it " +
+ "was originally disabled"
+ )
+ }
+ }
}
} else {
scope.launch("$TAG#listenForGoneToLockscreenOrHub") {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index f8208b3..f23b12f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -233,6 +233,7 @@
KeyguardState.DOZING -> TO_DOZING_DURATION
KeyguardState.GONE -> TO_GONE_DURATION
KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
+ KeyguardState.GLANCEABLE_HUB -> TO_GLANCEABLE_HUB_DURATION
else -> DEFAULT_DURATION
}.inWholeMilliseconds
}
@@ -245,6 +246,7 @@
val TO_GONE_DURATION = 500.milliseconds
val TO_GONE_SHORT_DURATION = 200.milliseconds
val TO_LOCKSCREEN_DURATION = 450.milliseconds
+ val TO_GLANCEABLE_HUB_DURATION = DEFAULT_DURATION
val TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD = 0.5f
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
index 9cc0b3c..8d683f0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
@@ -21,6 +21,7 @@
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -28,6 +29,7 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
/**
@@ -68,6 +70,7 @@
*/
val showKeyguardWhenReenabled: Flow<Boolean> =
repository.isKeyguardEnabled
+ .onEach { SceneContainerFlag.assertInLegacyMode() }
// Whenever the keyguard is disabled...
.filter { enabled -> !enabled }
.sampleCombine(
@@ -103,4 +106,12 @@
fun notifyKeyguardEnabled(enabled: Boolean) {
repository.setKeyguardEnabled(enabled)
}
+
+ fun setShowKeyguardWhenReenabled(isShowKeyguardWhenReenabled: Boolean) {
+ repository.setShowKeyguardWhenReenabled(isShowKeyguardWhenReenabled)
+ }
+
+ fun isShowKeyguardWhenReenabled(): Boolean {
+ return repository.isShowKeyguardWhenReenabled()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
index ecdc21c..dc7a649 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
@@ -24,6 +24,7 @@
import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToPrimaryBouncerTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DozingToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DozingToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DozingToOccludedTransitionViewModel
@@ -49,6 +50,7 @@
import com.android.systemui.keyguard.ui.viewmodel.OffToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToAodTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToDozingTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToLockscreenTransitionViewModel
import dagger.Binds
import dagger.Module
@@ -253,4 +255,16 @@
abstract fun goneToGlanceableHub(
impl: GoneToGlanceableHubTransitionViewModel
): DeviceEntryIconTransition
+
+ @Binds
+ @IntoSet
+ abstract fun primaryBouncerToGlanceableHub(
+ impl: PrimaryBouncerToGlanceableHubTransitionViewModel
+ ): DeviceEntryIconTransition
+
+ @Binds
+ @IntoSet
+ abstract fun dozingToGlanceableHub(
+ impl: DozingToGlanceableHubTransitionViewModel
+ ): DeviceEntryIconTransition
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
index 215ac46..1c63235 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
@@ -73,14 +73,14 @@
AccessibilityNodeInfoCompat.ACTION_CLICK,
resources.getString(R.string.accessibility_enter_hint)
)
+
override fun onInitializeAccessibilityNodeInfo(
v: View,
info: AccessibilityNodeInfo
) {
super.onInitializeAccessibilityNodeInfo(v, info)
when (accessibilityHintType) {
- AccessibilityHintType.BOUNCER ->
- info.addAction(accessibilityBouncerHint)
+ AccessibilityHintType.BOUNCER -> info.addAction(accessibilityBouncerHint)
AccessibilityHintType.ENTER -> info.addAction(accessibilityEnterHint)
AccessibilityHintType.NONE -> return
}
@@ -204,12 +204,12 @@
/* reversible */ false,
)
- // LockscreenFingerprint <=> LockscreenLocked
+ // LockscreenFingerprint => LockscreenLocked
animatedIconDrawable.addTransition(
R.id.locked_fp,
R.id.locked,
context.getDrawable(R.drawable.fp_to_locked) as AnimatedVectorDrawable,
- /* reversible */ true,
+ /* reversible */ false,
)
// LockscreenUnlocked <=> AodLocked
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModel.kt
new file mode 100644
index 0000000..aee34e1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModel.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor.Companion.TO_GLANCEABLE_HUB_DURATION
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+@SysUISingleton
+class DozingToGlanceableHubTransitionViewModel
+@Inject
+constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTransition {
+ private val transitionAnimation =
+ animationFlow
+ .setup(
+ duration = TO_GLANCEABLE_HUB_DURATION,
+ edge = Edge.create(DOZING, Scenes.Communal)
+ )
+ .setupWithoutSceneContainer(edge = Edge.create(DOZING, GLANCEABLE_HUB))
+
+ override val deviceEntryParentViewAlpha: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(1f)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt
new file mode 100644
index 0000000..754fb94
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GLANCEABLE_HUB_DURATION
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.model.Scenes
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+@SysUISingleton
+class PrimaryBouncerToGlanceableHubTransitionViewModel
+@Inject
+constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTransition {
+ private val transitionAnimation =
+ animationFlow
+ .setup(
+ duration = TO_GLANCEABLE_HUB_DURATION,
+ edge = Edge.create(PRIMARY_BOUNCER, Scenes.Communal)
+ )
+ .setupWithoutSceneContainer(edge = Edge.create(PRIMARY_BOUNCER, GLANCEABLE_HUB))
+
+ override val deviceEntryParentViewAlpha: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(1f)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt
index d0c7fbc..1f74716 100644
--- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt
@@ -22,6 +22,8 @@
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.shared.model.SceneFamilies
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeAlignment
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -29,11 +31,19 @@
/** Models UI state and handles user input for the Notifications Shade scene. */
@SysUISingleton
-class NotificationsShadeSceneViewModel @Inject constructor() {
+class NotificationsShadeSceneViewModel
+@Inject
+constructor(
+ shadeInteractor: ShadeInteractor,
+) {
val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
MutableStateFlow(
mapOf(
- Swipe.Up to SceneFamilies.Home,
+ if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
+ Swipe.Up
+ } else {
+ Swipe.Down
+ } to SceneFamilies.Home,
Back to SceneFamilies.Home,
)
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index e77bd03..4018320 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -103,8 +103,7 @@
}
private int mLastMaxHeight = -1;
- @Override
- public void setPageMargin(int marginPixels) {
+ public void setPageMargin(int marginPixelsStart, int marginPixelsEnd) {
// Using page margins creates some rounding issues that interfere with the correct position
// in the onPageChangedListener and therefore present bad positions to the PageIndicator.
// Instead, we use negative margins in the container and positive padding in the pages,
@@ -113,14 +112,19 @@
// QSContainerImpl resources are set onAttachedView, so this view will always have the right
// values when attached.
MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
- lp.setMarginStart(-marginPixels);
- lp.setMarginEnd(-marginPixels);
+ lp.setMarginStart(-marginPixelsStart);
+ lp.setMarginEnd(-marginPixelsEnd);
setLayoutParams(lp);
int nPages = mPages.size();
for (int i = 0; i < nPages; i++) {
View v = mPages.get(i);
- v.setPadding(marginPixels, v.getPaddingTop(), marginPixels, v.getPaddingBottom());
+ v.setPadding(
+ marginPixelsStart,
+ v.getPaddingTop(),
+ marginPixelsEnd,
+ v.getPaddingBottom()
+ );
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index cc0901f..db0676e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -35,6 +35,7 @@
import com.android.systemui.Dumpable;
import com.android.systemui.qs.customize.QSCustomizer;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.shade.LargeScreenHeaderHelper;
import com.android.systemui.shade.TouchLogger;
import com.android.systemui.util.LargeScreenUtils;
@@ -300,7 +301,7 @@
// QS panel lays out some of its content full width
qsPanelController.setContentMargins(mContentHorizontalPadding,
mContentHorizontalPadding);
- qsPanelController.setPageMargin(mTilesPageMargin);
+ setPageMargins(qsPanelController);
} else if (view == mHeader) {
quickStatusBarHeaderController.setContentMargins(mContentHorizontalPadding,
mContentHorizontalPadding);
@@ -318,6 +319,18 @@
}
}
+ private void setPageMargins(QSPanelController qsPanelController) {
+ if (SceneContainerFlag.isEnabled()) {
+ if (mHorizontalMargins == mTilesPageMargin * 2 + 1) {
+ qsPanelController.setPageMargin(mTilesPageMargin, mTilesPageMargin + 1);
+ } else {
+ qsPanelController.setPageMargin(mTilesPageMargin, mTilesPageMargin);
+ }
+ } else {
+ qsPanelController.setPageMargin(mTilesPageMargin, mTilesPageMargin);
+ }
+ }
+
/**
* Clip QS bottom using a concave shape.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index abc2b7f..032891f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -638,9 +638,9 @@
return mListening;
}
- protected void setPageMargin(int pageMargin) {
+ protected void setPageMargin(int pageMarginStart, int pageMarginEnd) {
if (mTileLayout instanceof PagedTileLayout) {
- ((PagedTileLayout) mTileLayout).setPageMargin(pageMargin);
+ ((PagedTileLayout) mTileLayout).setPageMargin(pageMarginStart, pageMarginEnd);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index f76183e..d5cd8dc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -284,8 +284,8 @@
return mView.isExpanded();
}
- void setPageMargin(int pageMargin) {
- mView.setPageMargin(pageMargin);
+ void setPageMargin(int pageMarginStart, int pageMarginEnd) {
+ mView.setPageMargin(pageMarginStart, pageMarginEnd);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
index bd748d5..e395d30 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
@@ -26,6 +26,8 @@
import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.scene.shared.model.SceneFamilies
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeAlignment
import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
@@ -37,6 +39,7 @@
class QuickSettingsShadeSceneViewModel
@Inject
constructor(
+ shadeInteractor: ShadeInteractor,
val overlayShadeViewModel: OverlayShadeViewModel,
val brightnessSliderViewModel: BrightnessSliderViewModel,
val tileGridViewModel: TileGridViewModel,
@@ -46,7 +49,11 @@
val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
MutableStateFlow(
mapOf(
- Swipe.Up to SceneFamilies.Home,
+ if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
+ Swipe.Up
+ } else {
+ Swipe.Down
+ } to SceneFamilies.Home,
Back to SceneFamilies.Home,
)
)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt
index 9e91b66..41a3c8a 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt
@@ -22,6 +22,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
import dagger.Binds
@@ -45,11 +46,13 @@
constructor(
@Application private val applicationScope: CoroutineScope,
deviceEntryInteractor: DeviceEntryInteractor,
+ keyguardEnabledInteractor: KeyguardEnabledInteractor,
) : SceneResolver {
override val targetFamily: SceneKey = SceneFamilies.Home
override val resolvedScene: StateFlow<SceneKey> =
combine(
+ keyguardEnabledInteractor.isKeyguardEnabled,
deviceEntryInteractor.canSwipeToEnter,
deviceEntryInteractor.isDeviceEntered,
deviceEntryInteractor.isUnlocked,
@@ -60,20 +63,23 @@
started = SharingStarted.Eagerly,
initialValue =
homeScene(
- deviceEntryInteractor.canSwipeToEnter.value,
- deviceEntryInteractor.isDeviceEntered.value,
- deviceEntryInteractor.isUnlocked.value,
+ isKeyguardEnabled = keyguardEnabledInteractor.isKeyguardEnabled.value,
+ canSwipeToEnter = deviceEntryInteractor.canSwipeToEnter.value,
+ isDeviceEntered = deviceEntryInteractor.isDeviceEntered.value,
+ isUnlocked = deviceEntryInteractor.isUnlocked.value,
)
)
override fun includesScene(scene: SceneKey): Boolean = scene in homeScenes
private fun homeScene(
+ isKeyguardEnabled: Boolean,
canSwipeToEnter: Boolean?,
isDeviceEntered: Boolean,
isUnlocked: Boolean,
): SceneKey =
when {
+ !isKeyguardEnabled -> Scenes.Gone
canSwipeToEnter == true -> Scenes.Lockscreen
!isDeviceEntered -> Scenes.Lockscreen
!isUnlocked -> Scenes.Lockscreen
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 218853d7..fbc0748 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -22,7 +22,6 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.internal.logging.UiEventLogger
-import com.android.internal.policy.IKeyguardStateCallback
import com.android.systemui.CoreStartable
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
@@ -39,6 +38,7 @@
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource
+import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor
import com.android.systemui.model.SceneContainerPlugin
@@ -83,6 +83,7 @@
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.filterNot
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
@@ -124,12 +125,11 @@
private val sceneBackInteractor: SceneBackInteractor,
private val shadeSessionStorage: SessionStorage,
private val windowMgrLockscreenVisInteractor: WindowManagerLockscreenVisibilityInteractor,
+ private val keyguardEnabledInteractor: KeyguardEnabledInteractor,
) : CoreStartable {
private val centralSurfaces: CentralSurfaces?
get() = centralSurfacesOptLazy.get().getOrNull()
- private val keyguardStateCallbacks = mutableListOf<IKeyguardStateCallback>()
-
override fun start() {
if (SceneContainerFlag.isEnabled) {
sceneLogger.logFrameworkEnabled(isEnabled = true)
@@ -143,6 +143,7 @@
hydrateWindowController()
hydrateBackStack()
resetShadeSessions()
+ handleKeyguardEnabledness()
} else {
sceneLogger.logFrameworkEnabled(
isEnabled = false,
@@ -653,6 +654,51 @@
}
}
+ private fun handleKeyguardEnabledness() {
+ // Automatically switches scenes when keyguard is enabled or disabled, as needed.
+ applicationScope.launch {
+ keyguardEnabledInteractor.isKeyguardEnabled
+ .sample(
+ combine(
+ deviceEntryInteractor.isInLockdown,
+ deviceEntryInteractor.isDeviceEntered,
+ ::Pair,
+ )
+ ) { isKeyguardEnabled, (isInLockdown, isDeviceEntered) ->
+ when {
+ !isKeyguardEnabled && !isInLockdown && !isDeviceEntered -> {
+ keyguardEnabledInteractor.setShowKeyguardWhenReenabled(true)
+ Scenes.Gone to "Keyguard became disabled"
+ }
+ isKeyguardEnabled &&
+ keyguardEnabledInteractor.isShowKeyguardWhenReenabled() -> {
+ keyguardEnabledInteractor.setShowKeyguardWhenReenabled(false)
+ Scenes.Lockscreen to "Keyguard became enabled"
+ }
+ else -> null
+ }
+ }
+ .filterNotNull()
+ .collect { (targetScene, loggingReason) ->
+ switchToScene(targetScene, loggingReason)
+ }
+ }
+
+ // Clears the showKeyguardWhenReenabled if the auth method changes to an insecure one.
+ applicationScope.launch {
+ authenticationInteractor
+ .get()
+ .authenticationMethod
+ .map { it.isSecure }
+ .distinctUntilChanged()
+ .collect { isAuthenticationMethodSecure ->
+ if (!isAuthenticationMethodSecure) {
+ keyguardEnabledInteractor.setShowKeyguardWhenReenabled(false)
+ }
+ }
+ }
+ }
+
private fun switchToScene(targetSceneKey: SceneKey, loggingReason: String) {
sceneInteractor.changeScene(
toScene = targetSceneKey,
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index d382b7a..a62edcb 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -25,6 +25,7 @@
import android.app.Activity;
import android.content.res.Configuration;
+import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.Gravity;
@@ -32,7 +33,9 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
+import android.view.WindowInsets;
import android.view.WindowManager;
+import android.view.WindowMetrics;
import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
@@ -152,18 +155,27 @@
Configuration configuration = getResources().getConfiguration();
int orientation = configuration.orientation;
- int screenWidth = getWindowManager().getDefaultDisplay().getWidth();
+ int windowWidth = getWindowAvailableWidth();
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
boolean shouldBeFullWidth = getIntent()
.getBooleanExtra(EXTRA_BRIGHTNESS_DIALOG_IS_FULL_WIDTH, false);
- lp.width = (shouldBeFullWidth ? screenWidth : screenWidth / 2) - horizontalMargin * 2;
+ lp.width = (shouldBeFullWidth ? windowWidth : windowWidth / 2) - horizontalMargin * 2;
} else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
- lp.width = screenWidth - horizontalMargin * 2;
+ lp.width = windowWidth - horizontalMargin * 2;
}
frame.setLayoutParams(lp);
+ }
+ private int getWindowAvailableWidth() {
+ final WindowMetrics metrics = getWindowManager().getCurrentWindowMetrics();
+ // Gets all excluding insets
+ final WindowInsets windowInsets = metrics.getWindowInsets();
+ Insets insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars()
+ | WindowInsets.Type.displayCutout());
+ int insetsWidth = insets.right + insets.left;
+ return metrics.getBounds().width() - insetsWidth;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index d54e66e..af40cab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -4474,6 +4474,7 @@
}
void goToFullShade(long delay) {
+ SceneContainerFlag.assertInLegacyMode();
mGoToFullShadeNeedsAnimation = true;
mGoToFullShadeDelay = delay;
mNeedsAnimation = true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index d984685..bf53ee2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1177,6 +1177,7 @@
}
public void goToFullShade(long delay) {
+ SceneContainerFlag.assertInLegacyMode();
mView.goToFullShade(delay);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
index 361a945..16b9ab5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
@@ -16,7 +16,7 @@
package com.android.systemui.accessibility;
-import static com.android.systemui.accessibility.Magnification.DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS;
+import static com.android.systemui.accessibility.MagnificationImpl.DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -65,7 +65,7 @@
/**
* Tests for {@link android.view.accessibility.IMagnificationConnection} retrieved from
- * {@link Magnification}
+ * {@link MagnificationImpl}
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -104,7 +104,7 @@
private IWindowManager mIWindowManager;
private IMagnificationConnection mIMagnificationConnection;
- private Magnification mMagnification;
+ private MagnificationImpl mMagnification;
private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
private TestableLooper mTestableLooper;
@@ -119,11 +119,11 @@
any(IMagnificationConnection.class));
mTestableLooper = TestableLooper.get(this);
assertNotNull(mTestableLooper);
- mMagnification = new Magnification(getContext(),
+ mMagnification = new MagnificationImpl(getContext(),
mTestableLooper.getLooper(), mContext.getMainExecutor(), mCommandQueue,
mModeSwitchesController, mSysUiState, mOverviewProxyService, mSecureSettings,
mDisplayTracker, getContext().getSystemService(DisplayManager.class),
- mA11yLogger, mIWindowManager);
+ mA11yLogger, mIWindowManager, mAccessibilityManager);
mMagnification.mWindowMagnificationControllerSupplier =
new FakeWindowMagnificationControllerSupplier(
mContext.getSystemService(DisplayManager.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
index 17b7e21..41a4116 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
@@ -84,7 +84,7 @@
private SecureSettings mSecureSettings;
private CommandQueue mCommandQueue;
- private Magnification mMagnification;
+ private MagnificationImpl mMagnification;
private OverviewProxyListener mOverviewProxyListener;
private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
@@ -124,11 +124,12 @@
when(mWindowMagnificationController.isActivated()).thenReturn(true);
mCommandQueue = new CommandQueue(getContext(), mDisplayTracker);
- mMagnification = new Magnification(getContext(),
+ mMagnification = new MagnificationImpl(getContext(),
getContext().getMainThreadHandler(), mContext.getMainExecutor(),
mCommandQueue, mModeSwitchesController,
mSysUiState, mOverviewProxyService, mSecureSettings, mDisplayTracker,
- getContext().getSystemService(DisplayManager.class), mA11yLogger, mIWindowManager);
+ getContext().getSystemService(DisplayManager.class), mA11yLogger, mIWindowManager,
+ getContext().getSystemService(AccessibilityManager.class));
mMagnification.mWindowMagnificationControllerSupplier = new FakeControllerSupplier(
mContext.getSystemService(DisplayManager.class), mWindowMagnificationController);
mMagnification.mMagnificationSettingsSupplier = new FakeSettingsSupplier(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index dca531a..5bae6ec 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -133,6 +133,8 @@
override val topClippingBounds = MutableStateFlow<Int?>(null)
+ private var isShowKeyguardWhenReenabled: Boolean = false
+
override fun setQuickSettingsVisible(isVisible: Boolean) {
_isQuickSettingsVisible.value = isVisible
}
@@ -268,6 +270,14 @@
fun setIsEncryptedOrLockdown(value: Boolean) {
_isEncryptedOrLockdown.value = value
}
+
+ override fun setShowKeyguardWhenReenabled(isShowKeyguardWhenReenabled: Boolean) {
+ this.isShowKeyguardWhenReenabled = isShowKeyguardWhenReenabled
+ }
+
+ override fun isShowKeyguardWhenReenabled(): Boolean {
+ return isShowKeyguardWhenReenabled
+ }
}
@Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/resolver/SceneResolverKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/resolver/SceneResolverKosmos.kt
index 6be1939..ae33aea 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/resolver/SceneResolverKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/resolver/SceneResolverKosmos.kt
@@ -20,6 +20,7 @@
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.scene.shared.model.SceneFamilies
@@ -39,6 +40,7 @@
HomeSceneFamilyResolver(
applicationScope = applicationCoroutineScope,
deviceEntryInteractor = deviceEntryInteractor,
+ keyguardEnabledInteractor = keyguardEnabledInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
index 8b887d3..a661ab6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
@@ -25,6 +25,7 @@
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.windowManagerLockscreenVisibilityInteractor
import com.android.systemui.kosmos.Kosmos
@@ -71,5 +72,6 @@
sceneBackInteractor = sceneBackInteractor,
shadeSessionStorage = shadeSessionStorage,
windowMgrLockscreenVisInteractor = windowManagerLockscreenVisibilityInteractor,
+ keyguardEnabledInteractor = keyguardEnabledInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
index 4813794..b9918f1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
@@ -30,7 +30,7 @@
@SysUISingleton
class FakeShadeRepository @Inject constructor() : ShadeRepository {
private val _qsExpansion = MutableStateFlow(0f)
- override val qsExpansion = _qsExpansion
+ @Deprecated("Use ShadeInteractor.qsExpansion instead") override val qsExpansion = _qsExpansion
private val _udfpsTransitionToFullShadeProgress = MutableStateFlow(0f)
override val udfpsTransitionToFullShadeProgress = _udfpsTransitionToFullShadeProgress
@@ -59,12 +59,15 @@
private val _legacyIsQsExpanded = MutableStateFlow(false)
@Deprecated("Use ShadeInteractor instead") override val legacyIsQsExpanded = _legacyIsQsExpanded
+ @Deprecated("Use ShadeInteractor.isUserInteractingWithShade instead")
override val legacyLockscreenShadeTracking = MutableStateFlow(false)
private val _shadeMode = MutableStateFlow<ShadeMode>(ShadeMode.Single)
override val shadeMode: StateFlow<ShadeMode> = _shadeMode.asStateFlow()
- override val isDualShadeAlignedToBottom = false
+ private var _isDualShadeAlignedToBottom = false
+ override val isDualShadeAlignedToBottom
+ get() = _isDualShadeAlignedToBottom
@Deprecated("Use ShadeInteractor instead")
override fun setLegacyIsQsExpanded(legacyIsQsExpanded: Boolean) {
@@ -139,8 +142,12 @@
_legacyShadeExpansion.value = expandedFraction
}
- override fun setShadeMode(shadeMode: ShadeMode) {
- _shadeMode.value = shadeMode
+ override fun setShadeMode(mode: ShadeMode) {
+ _shadeMode.value = mode
+ }
+
+ fun setDualShadeAlignedToBottom(isAlignedToBottom: Boolean) {
+ _isDualShadeAlignedToBottom = isAlignedToBottom
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelKosmos.kt
index 1ca3509..72a80d4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelKosmos.kt
@@ -18,6 +18,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneViewModel
+import com.android.systemui.shade.domain.interactor.shadeInteractor
val Kosmos.notificationsShadeSceneViewModel: NotificationsShadeSceneViewModel by
- Kosmos.Fixture { NotificationsShadeSceneViewModel() }
+ Kosmos.Fixture { NotificationsShadeSceneViewModel(shadeInteractor) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
index 4d81ea1..8adb26f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
@@ -22,6 +22,7 @@
import com.android.systemui.qs.panels.ui.viewmodel.tileGridViewModel
import com.android.systemui.qs.ui.adapter.qsSceneAdapter
import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeSceneViewModel
+import com.android.systemui.shade.domain.interactor.shadeInteractor
val Kosmos.quickSettingsShadeSceneViewModel: QuickSettingsShadeSceneViewModel by
Kosmos.Fixture {
@@ -31,5 +32,6 @@
tileGridViewModel = tileGridViewModel,
editModeViewModel = editModeViewModel,
qsSceneAdapter = qsSceneAdapter,
+ shadeInteractor = shadeInteractor,
)
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 6bd7445..0fa5260 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -127,6 +127,7 @@
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SERVICE_EXECUTING;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.ProcessList.UNKNOWN_ADJ;
import android.Manifest;
import android.annotation.IntDef;
@@ -4304,7 +4305,7 @@
// queued up in the app side as they're one way calls. And we'll also hold off
// the service timeout timer until the process is unfrozen.
mAm.mOomAdjuster.updateAppFreezeStateLSP(callerApp, OOM_ADJ_REASON_BIND_SERVICE,
- true);
+ true, UNKNOWN_ADJ);
}
final boolean wasStopped = hostApp == null ? wasStopped(s) : false;
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 211f952..6433f2c 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -83,6 +83,8 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.ServiceThread;
+import dalvik.annotation.optimization.NeverCompile;
+
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
@@ -98,8 +100,6 @@
import java.util.Random;
import java.util.Set;
-import dalvik.annotation.optimization.NeverCompile;
-
public final class CachedAppOptimizer {
// Flags stored in the DeviceConfig API.
@@ -248,7 +248,7 @@
private static final int COMPACT_ACTION_ANON_FLAG = 2;
private static final String ATRACE_COMPACTION_TRACK = "Compaction";
- private static final String ATRACE_FREEZER_TRACK = "Freezer";
+ public static final String ATRACE_FREEZER_TRACK = "Freezer";
private static final int FREEZE_BINDER_TIMEOUT_MS = 0;
private static final int FREEZE_DEADLOCK_TIMEOUT_MS = 1000;
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index c9e0666..27c194b 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1832,7 +1832,8 @@
state.setNoKillOnBgRestrictedAndIdle(false);
// If this UID is currently allowlisted, it should not be frozen.
final UidRecord uidRec = app.getUidRecord();
- app.mOptRecord.setShouldNotFreeze(uidRec != null && uidRec.isCurAllowListed());
+ app.mOptRecord.setShouldNotFreeze(uidRec != null && uidRec.isCurAllowListed(),
+ ProcessCachedOptimizerRecord.SHOULD_NOT_FREEZE_REASON_UID_ALLOWLISTED, mAdjSeq);
}
final int appUid = app.info.uid;
@@ -2672,7 +2673,9 @@
if (client.mOptRecord.shouldNotFreeze()) {
// Propagate the shouldNotFreeze flag down the bindings.
- if (app.mOptRecord.setShouldNotFreeze(true, dryRun)) {
+ if (app.mOptRecord.setShouldNotFreeze(true, dryRun,
+ app.mOptRecord.shouldNotFreezeReason()
+ | client.mOptRecord.shouldNotFreezeReason(), mAdjSeq)) {
// Bail out early, as we only care about the return value for a dryrun.
return true;
}
@@ -2739,7 +2742,10 @@
if (cr.hasFlag(Context.BIND_ALLOW_OOM_MANAGEMENT)) {
// Similar to BIND_WAIVE_PRIORITY, keep it unfrozen.
if (clientAdj < CACHED_APP_MIN_ADJ) {
- if (app.mOptRecord.setShouldNotFreeze(true, dryRun)) {
+ if (app.mOptRecord.setShouldNotFreeze(true, dryRun,
+ app.mOptRecord.shouldNotFreezeReason()
+ | ProcessCachedOptimizerRecord
+ .SHOULD_NOT_FREEZE_REASON_BINDER_ALLOW_OOM_MANAGEMENT, mAdjSeq)) {
// Bail out early, as we only care about the return value for a dryrun.
return true;
}
@@ -2976,7 +2982,10 @@
// bound by an unfrozen app via a WPRI binding has to remain
// unfrozen.
if (clientAdj < CACHED_APP_MIN_ADJ) {
- if (app.mOptRecord.setShouldNotFreeze(true, dryRun)) {
+ if (app.mOptRecord.setShouldNotFreeze(true, dryRun,
+ app.mOptRecord.shouldNotFreezeReason()
+ | ProcessCachedOptimizerRecord
+ .SHOULD_NOT_FREEZE_REASON_BIND_WAIVE_PRIORITY, mAdjSeq)) {
// Bail out early, as we only care about the return value for a dryrun.
return true;
}
@@ -3117,7 +3126,9 @@
}
if (client.mOptRecord.shouldNotFreeze()) {
// Propagate the shouldNotFreeze flag down the bindings.
- if (app.mOptRecord.setShouldNotFreeze(true, dryRun)) {
+ if (app.mOptRecord.setShouldNotFreeze(true, dryRun,
+ app.mOptRecord.shouldNotFreezeReason()
+ | client.mOptRecord.shouldNotFreezeReason(), mAdjSeq)) {
// Bail out early, as we only care about the return value for a dryrun.
return true;
}
@@ -3476,7 +3487,7 @@
changes |= ActivityManagerService.ProcessChangeItem.CHANGE_ACTIVITIES;
}
- updateAppFreezeStateLSP(app, oomAdjReson, false);
+ updateAppFreezeStateLSP(app, oomAdjReson, false, oldOomAdj);
if (state.getReportedProcState() != state.getCurProcState()) {
state.setReportedProcState(state.getCurProcState());
@@ -3873,16 +3884,37 @@
@GuardedBy({"mService", "mProcLock"})
void updateAppFreezeStateLSP(ProcessRecord app, @OomAdjReason int oomAdjReason,
- boolean immediate) {
+ boolean immediate, int oldOomAdj) {
if (!mCachedAppOptimizer.useFreezer()) {
return;
}
+ final ProcessCachedOptimizerRecord opt = app.mOptRecord;
+ final ProcessStateRecord state = app.mState;
+ if (Flags.traceUpdateAppFreezeStateLsp()) {
+ final boolean oomAdjChanged =
+ (state.getCurAdj() >= FREEZER_CUTOFF_ADJ ^ oldOomAdj >= FREEZER_CUTOFF_ADJ)
+ || oldOomAdj == UNKNOWN_ADJ;
+ final boolean shouldNotFreezeChanged = opt.shouldNotFreezeAdjSeq() == mAdjSeq;
+ if ((oomAdjChanged || shouldNotFreezeChanged)
+ && Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ CachedAppOptimizer.ATRACE_FREEZER_TRACK,
+ "updateAppFreezeStateLSP " + app.processName
+ + " isFreezeExempt: " + opt.isFreezeExempt()
+ + " isFrozen: " + opt.isFrozen()
+ + " shouldNotFreeze: " + opt.shouldNotFreeze()
+ + " shouldNotFreezeReason: " + opt.shouldNotFreezeReason()
+ + " curAdj: " + state.getCurAdj()
+ + " oldOomAdj: " + oldOomAdj
+ + " immediate: " + immediate);
+ }
+ }
+
if (app.mOptRecord.isFreezeExempt()) {
return;
}
- final ProcessCachedOptimizerRecord opt = app.mOptRecord;
// if an app is already frozen and shouldNotFreeze becomes true, immediately unfreeze
if (opt.isFrozen() && opt.shouldNotFreeze()) {
mCachedAppOptimizer.unfreezeAppLSP(app,
@@ -3890,7 +3922,6 @@
return;
}
- final ProcessStateRecord state = app.mState;
// Use current adjustment when freezing, set adjustment when unfreezing.
if (state.getCurAdj() >= FREEZER_CUTOFF_ADJ && !opt.isFrozen()
&& !opt.shouldNotFreeze()) {
diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
index ea92571..53643b8 100644
--- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
+++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import android.annotation.IntDef;
import android.annotation.UptimeMillisLong;
import android.app.ActivityManagerInternal.OomAdjReason;
import android.util.TimeUtils;
@@ -23,14 +24,31 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import java.io.PrintWriter;
-
import dalvik.annotation.optimization.NeverCompile;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* The state info of app when it's cached, used by the optimizer.
*/
final class ProcessCachedOptimizerRecord {
+
+ static final int SHOULD_NOT_FREEZE_REASON_NONE = 1;
+ static final int SHOULD_NOT_FREEZE_REASON_UID_ALLOWLISTED = 1 << 1;
+ static final int SHOULD_NOT_FREEZE_REASON_BINDER_ALLOW_OOM_MANAGEMENT = 1 << 2;
+ static final int SHOULD_NOT_FREEZE_REASON_BIND_WAIVE_PRIORITY = 1 << 3;
+
+ @IntDef(flag = true, prefix = {"SHOULD_NOT_FREEZE_REASON_"}, value = {
+ SHOULD_NOT_FREEZE_REASON_NONE,
+ SHOULD_NOT_FREEZE_REASON_UID_ALLOWLISTED,
+ SHOULD_NOT_FREEZE_REASON_BINDER_ALLOW_OOM_MANAGEMENT,
+ SHOULD_NOT_FREEZE_REASON_BIND_WAIVE_PRIORITY,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ShouldNotFreezeReason {}
+
private final ProcessRecord mApp;
private final ActivityManagerGlobalLock mProcLock;
@@ -112,6 +130,18 @@
private boolean mShouldNotFreeze;
/**
+ * Reason for mShouldNotFreeze being set to a particular value.
+ */
+ @GuardedBy("mProcLock")
+ private @ShouldNotFreezeReason int mShouldNotFreezeReason;
+
+ /**
+ * The value of adjSeq when last time mShouldNotFreeze was set.
+ */
+ @GuardedBy("mProcLock")
+ private int mShouldNotFreezeAdjSeq;
+
+ /**
* Exempt from freezer (now for system apps with INSTALL_PACKAGES permission)
*/
@GuardedBy("mProcLock")
@@ -275,8 +305,19 @@
}
@GuardedBy("mProcLock")
- void setShouldNotFreeze(boolean shouldNotFreeze) {
- setShouldNotFreeze(shouldNotFreeze, false);
+ @ShouldNotFreezeReason int shouldNotFreezeReason() {
+ return mShouldNotFreezeReason;
+ }
+
+ @GuardedBy("mProcLock")
+ int shouldNotFreezeAdjSeq() {
+ return mShouldNotFreezeAdjSeq;
+ }
+
+ @GuardedBy("mProcLock")
+ void setShouldNotFreeze(boolean shouldNotFreeze, @ShouldNotFreezeReason int reason,
+ int adjSeq) {
+ setShouldNotFreeze(shouldNotFreeze, false, reason, adjSeq);
}
/**
@@ -284,10 +325,23 @@
* if it was a real run.
*/
@GuardedBy("mProcLock")
- boolean setShouldNotFreeze(boolean shouldNotFreeze, boolean dryRun) {
+ boolean setShouldNotFreeze(boolean shouldNotFreeze, boolean dryRun,
+ @ShouldNotFreezeReason int reason, int adjSeq) {
if (dryRun) {
return mFrozen && !shouldNotFreeze;
}
+ if (Flags.traceUpdateAppFreezeStateLsp()) {
+ if (shouldNotFreeze) {
+ reason &= ~SHOULD_NOT_FREEZE_REASON_NONE;
+ } else {
+ reason = SHOULD_NOT_FREEZE_REASON_NONE;
+ }
+
+ if (reason != mShouldNotFreezeReason || shouldNotFreeze != mShouldNotFreeze) {
+ mShouldNotFreezeAdjSeq = adjSeq;
+ }
+ mShouldNotFreezeReason = reason;
+ }
mShouldNotFreeze = shouldNotFreeze;
return false;
}
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index a30590f..0dfa330 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -125,6 +125,14 @@
}
flag {
+ name: "trace_update_app_freeze_state_lsp"
+ namespace: "backstage_power"
+ description: "Output process oom adj and other metadata to trace in updateAppFreezeStateLSP"
+ bug: "345547733"
+ is_fixed_read_only: true
+}
+
+flag {
name: "simplify_process_traversal"
namespace: "backstage_power"
description: "Simplify the OomAdjuster's process traversal mechanism."
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 6d1983e..c89992d 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -11698,7 +11698,7 @@
static final int LOG_NB_EVENTS_VOLUME = 100;
static final int LOG_NB_EVENTS_DYN_POLICY = 10;
static final int LOG_NB_EVENTS_SPATIAL = 30;
- static final int LOG_NB_EVENTS_SOUND_DOSE = 30;
+ static final int LOG_NB_EVENTS_SOUND_DOSE = 50;
static final int LOG_NB_EVENTS_LOUDNESS_CODEC = 30;
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index 749044e..8ea28be 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -577,6 +577,7 @@
static final int DOSE_REPEAT_5X = 2;
static final int DOSE_ACCUMULATION_START = 3;
static final int LOWER_VOLUME_TO_RS1 = 4;
+ static final int UPDATE_ABS_VOLUME_ATTENUATION = 5;
final int mEventType;
final float mFloatValue;
@@ -608,6 +609,10 @@
return new SoundDoseEvent(LOWER_VOLUME_TO_RS1, 0 /*ignored*/, 0 /*ignored*/);
}
+ static SoundDoseEvent getAbsVolumeAttenuationEvent(float attenuation, int device) {
+ return new SoundDoseEvent(UPDATE_ABS_VOLUME_ATTENUATION, attenuation, device);
+ }
+
@Override
public String eventToString() {
switch (mEventType) {
@@ -623,6 +628,10 @@
return "CSD accumulating: RS2 entered";
case LOWER_VOLUME_TO_RS1:
return "CSD lowering volume to RS1";
+ case UPDATE_ABS_VOLUME_ATTENUATION:
+ return String.format(java.util.Locale.US,
+ "Updating CSD absolute volume attenuation on device %d with %.2f dB ",
+ mLongValue, mFloatValue);
}
return new StringBuilder("FIXME invalid event type:").append(mEventType).toString();
}
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index e28ae95..5c74304 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -39,6 +39,7 @@
import android.media.ISoundDose;
import android.media.ISoundDoseCallback;
import android.media.SoundDoseRecord;
+import android.media.VolumeInfo;
import android.os.Binder;
import android.os.Message;
import android.os.RemoteException;
@@ -895,6 +896,8 @@
try {
if (!isAbsoluteVolume) {
+ mLogger.enqueue(
+ SoundDoseEvent.getAbsVolumeAttenuationEvent(/*attenuation=*/0.f, device));
// remove any possible previous attenuation
soundDose.updateAttenuation(/* attenuationDB= */0.f, device);
@@ -903,10 +906,11 @@
if (AudioService.mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC
&& safeDevicesContains(device)) {
- soundDose.updateAttenuation(
- -AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_MUSIC,
- (newIndex + 5) / 10,
- device), device);
+ float attenuationDb = -AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_MUSIC,
+ (newIndex + 5) / 10, device);
+ mLogger.enqueue(
+ SoundDoseEvent.getAbsVolumeAttenuationEvent(attenuationDb, device));
+ soundDose.updateAttenuation(attenuationDb, device);
}
} catch (RemoteException e) {
Log.e(TAG, "Could not apply the attenuation for MEL calculation with volume index "
@@ -1313,22 +1317,30 @@
/** Called when handling MSG_LOWER_VOLUME_TO_RS1 */
private void onLowerVolumeToRs1() {
- mLogger.enqueue(SoundDoseEvent.getLowerVolumeToRs1Event());
final ArrayList<AudioDeviceAttributes> devices = mAudioService.getDevicesForAttributesInt(
- new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build(), true);
- final int nativeDeviceType;
- final AudioDeviceAttributes ada;
- if (!devices.isEmpty()) {
- ada = devices.get(0);
- nativeDeviceType = ada.getInternalType();
- } else {
- nativeDeviceType = AudioSystem.DEVICE_OUT_USB_HEADSET;
- ada = new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_USB_HEADSET, "");
+ new AudioAttributes.Builder().setUsage(
+ AudioAttributes.USAGE_MEDIA).build(), /*forVolume=*/true);
+ if (devices.isEmpty()) {
+ Log.e(TAG, "Cannot lower the volume to RS1, no devices registered for USAGE_MEDIA");
+ return;
}
- final int index = safeMediaVolumeIndex(nativeDeviceType);
- mAudioService.setStreamVolumeWithAttributionInt(STREAM_MUSIC, index / 10, /*flags*/ 0, ada,
- mContext.getOpPackageName(), /*attributionTag=*/null,
- true /*canChangeMuteAndUpdateController*/);
+ final AudioDeviceAttributes ada = devices.get(0);
+ final int nativeDeviceType = ada.getInternalType();
+ final int index = safeMediaVolumeIndex(nativeDeviceType) / 10;
+ final VolumeInfo curVolume = mAudioService.getDeviceVolume(
+ new VolumeInfo.Builder(STREAM_MUSIC).build(), ada,
+ /*callingPackage=*/"sounddosehelper");
+
+ if (index < curVolume.getVolumeIndex()) {
+ mLogger.enqueue(SoundDoseEvent.getLowerVolumeToRs1Event());
+ mAudioService.setStreamVolumeWithAttributionInt(STREAM_MUSIC, index, /*flags*/ 0, ada,
+ mContext.getOpPackageName(), /*attributionTag=*/null,
+ /*canChangeMuteAndUpdateController=*/true);
+ } else {
+ Log.i(TAG, "The current volume " + curVolume.getVolumeIndex()
+ + " for device type " + nativeDeviceType
+ + " is already smaller or equal to the safe index volume " + index);
+ }
}
// StreamVolumeCommand contains the information needed to defer the process of
diff --git a/services/core/java/com/android/server/display/BrightnessSetting.java b/services/core/java/com/android/server/display/BrightnessSetting.java
index 651828b..7d26004 100644
--- a/services/core/java/com/android/server/display/BrightnessSetting.java
+++ b/services/core/java/com/android/server/display/BrightnessSetting.java
@@ -131,6 +131,25 @@
}
/**
+ * Sets the brightness. Does not send update event to listeners.
+ * @param brightness The value to which the brightness is to be set.
+ */
+ public void setBrightnessNoNotify(float brightness) {
+ if (Float.isNaN(brightness)) {
+ Slog.w(TAG, "Attempting to init invalid brightness");
+ return;
+ }
+ synchronized (mSyncRoot) {
+ if (brightness != mBrightness) {
+ mPersistentDataStore.setBrightness(mLogicalDisplay.getPrimaryDisplayDeviceLocked(),
+ brightness, mUserSerial
+ );
+ }
+ mBrightness = brightness;
+ }
+ }
+
+ /**
* @return The brightness for the default display in nits. Used when the underlying display
* device has changed but we want to persist the nit value.
*/
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index c298bbf..de9715a 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1567,7 +1567,9 @@
// even if they range changes what it means in absolute terms.
mDisplayBrightnessController.updateScreenBrightnessSetting(
MathUtils.constrain(unthrottledBrightnessState,
- clampedState.getMinBrightness(), clampedState.getMaxBrightness()));
+ clampedState.getMinBrightness(), clampedState.getMaxBrightness()),
+ Math.min(mBrightnessRangeController.getCurrentBrightnessMax(),
+ clampedState.getMaxBrightness()));
}
// The current brightness to use has been calculated at this point, and HbmController should
@@ -2471,12 +2473,20 @@
@Override
public void setBrightness(float brightness) {
- mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightness));
+ // After HBMController and NBMController migration to Clampers framework
+ // currentBrightnessMax should be taken from clampers controller
+ // TODO(b/263362199)
+ mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightness),
+ mBrightnessRangeController.getCurrentBrightnessMax());
}
@Override
public void setBrightness(float brightness, int userSerial) {
- mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightness), userSerial);
+ // After HBMController and NBMController migration to Clampers framework
+ // currentBrightnessMax should be taken from clampers controller
+ // TODO(b/263362199)
+ mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightness), userSerial,
+ mBrightnessRangeController.getCurrentBrightnessMax());
}
@Override
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index 4982a0b0..c632e77 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -313,13 +313,18 @@
/**
* Notifies the brightnessSetting to persist the supplied brightness value.
*/
- public void setBrightness(float brightnessValue) {
+ public void setBrightness(float brightnessValue, float maxBrightness) {
// Update the setting, which will eventually call back into DPC to have us actually
// update the display with the new value.
mBrightnessSetting.setBrightness(brightnessValue);
if (mDisplayId == Display.DEFAULT_DISPLAY && mPersistBrightnessNitsForDefaultDisplay) {
float nits = convertToNits(brightnessValue);
- if (nits >= 0) {
+ float currentlyStoredNits = mBrightnessSetting.getBrightnessNitsForDefaultDisplay();
+ // Don't override settings if the brightness is set to max, but the currently
+ // stored value is greater. On multi-screen device, when switching between a
+ // screen with a wider brightness range and one with a narrower brightness range,
+ // the stored value shouldn't change.
+ if (nits >= 0 && !(brightnessValue == maxBrightness && currentlyStoredNits > nits)) {
mBrightnessSetting.setBrightnessNitsForDefaultDisplay(nits);
}
}
@@ -328,15 +333,15 @@
/**
* Notifies the brightnessSetting to persist the supplied brightness value for a user.
*/
- public void setBrightness(float brightnessValue, int userSerial) {
+ public void setBrightness(float brightnessValue, int userSerial, float maxBrightness) {
mBrightnessSetting.setUserSerial(userSerial);
- setBrightness(brightnessValue);
+ setBrightness(brightnessValue, maxBrightness);
}
/**
* Sets the current screen brightness, and notifies the BrightnessSetting about the change.
*/
- public void updateScreenBrightnessSetting(float brightnessValue) {
+ public void updateScreenBrightnessSetting(float brightnessValue, float maxBrightness) {
synchronized (mLock) {
if (!BrightnessUtils.isValidBrightnessValue(brightnessValue)
|| brightnessValue == mCurrentScreenBrightness) {
@@ -345,7 +350,7 @@
setCurrentScreenBrightnessLocked(brightnessValue);
}
notifyCurrentScreenBrightness();
- setBrightness(brightnessValue);
+ setBrightness(brightnessValue, maxBrightness);
}
/**
@@ -582,7 +587,7 @@
float brightnessForDefaultDisplay = getBrightnessFromNits(
brightnessNitsForDefaultDisplay);
if (BrightnessUtils.isValidBrightnessValue(brightnessForDefaultDisplay)) {
- mBrightnessSetting.setBrightness(brightnessForDefaultDisplay);
+ mBrightnessSetting.setBrightnessNoNotify(brightnessForDefaultDisplay);
currentBrightnessSetting = brightnessForDefaultDisplay;
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 440343d..0e628b7 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -377,11 +377,6 @@
@SharedByAllUsersField
private final SparseArray<String> mVirtualDeviceMethodMap = new SparseArray<>();
- // TODO: Instantiate mSwitchingController for each user.
- @NonNull
- @MultiUserUnawareField
- private InputMethodSubtypeSwitchingController mSwitchingController;
-
@Nullable
private StatusBarManagerInternal mStatusBarManagerInternal;
@SharedByAllUsersField
@@ -1287,7 +1282,7 @@
@SuppressWarnings("GuardedBy") final IntFunction<InputMethodBindingController>
bindingControllerFactory = userId -> new InputMethodBindingController(userId,
InputMethodManagerService.this);
- mUserDataRepository = new UserDataRepository(mHandler, mUserManagerInternal,
+ mUserDataRepository = new UserDataRepository(mContext, mHandler, mUserManagerInternal,
bindingControllerForTesting != null ? bindingControllerForTesting
: bindingControllerFactory);
for (int id : mUserManagerInternal.getUserIds()) {
@@ -1295,9 +1290,9 @@
}
final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
-
- mSwitchingController = new InputMethodSubtypeSwitchingController(context, settings);
- getUserData(mCurrentUserId).mHardwareKeyboardShortcutController.update(settings);
+ final var userData = getUserData(mCurrentUserId);
+ userData.mSwitchingController.resetCircularListLocked(settings);
+ userData.mHardwareKeyboardShortcutController.update(settings);
mMenuController = new InputMethodMenuController(this);
mVisibilityStateComputer = new ImeVisibilityStateComputer(this);
mVisibilityApplier = new DefaultImeVisibilityApplier(this);
@@ -2928,7 +2923,6 @@
* {@link PackageManager#COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED} is not updated.
* </li>
* <li>{@link InputMethodBindingController#getDeviceIdToShowIme()} is ignored.</li>
- * <li>{@link #mSwitchingController} is ignored.</li>
* <li>{@link #mPreventImeStartupUnlessTextEditor} is ignored.</li>
* <li>and so on.</li>
* </ul>
@@ -2963,6 +2957,7 @@
}
final var userData = getUserData(userId);
+ userData.mSwitchingController.resetCircularListLocked(settings);
userData.mHardwareKeyboardShortcutController.update(settings);
}
@@ -3039,13 +3034,9 @@
resetCurrentMethodAndClientLocked(UnbindReason.NO_IME);
}
- // TODO: Instantiate mSwitchingController for each user.
- if (userId == mSwitchingController.getUserId()) {
- mSwitchingController.resetCircularListLocked(settings);
- } else {
- mSwitchingController = new InputMethodSubtypeSwitchingController(mContext, settings);
- }
- getUserData(userId).mHardwareKeyboardShortcutController.update(settings);
+ final var userData = getUserData(userId);
+ userData.mSwitchingController.resetCircularListLocked(settings);
+ userData.mHardwareKeyboardShortcutController.update(settings);
sendOnNavButtonFlagsChangedLocked();
}
@@ -4188,8 +4179,9 @@
final int userId = mCurrentUserId;
final var bindingController = getInputMethodBindingController(userId);
final var currentImi = bindingController.getSelectedMethod();
- final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
- onlyCurrentIme, currentImi, bindingController.getCurrentSubtype());
+ final ImeSubtypeListItem nextSubtype = getUserData(userId).mSwitchingController
+ .getNextInputMethodLocked(onlyCurrentIme, currentImi,
+ bindingController.getCurrentSubtype());
if (nextSubtype == null) {
return false;
}
@@ -4207,8 +4199,9 @@
final int userId = mCurrentUserId;
final var bindingController = getInputMethodBindingController(userId);
final var currentImi = bindingController.getSelectedMethod();
- final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
- false /* onlyCurrentIme */, currentImi, bindingController.getCurrentSubtype());
+ final ImeSubtypeListItem nextSubtype = getUserData(userId).mSwitchingController
+ .getNextInputMethodLocked(false /* onlyCurrentIme */, currentImi,
+ bindingController.getCurrentSubtype());
return nextSubtype != null;
}
}
@@ -4641,13 +4634,11 @@
return;
}
final int userId = mCurrentUserId;
- if (userId != mSwitchingController.getUserId()) {
- return;
- }
- final var imi = getInputMethodBindingController(userId).getSelectedMethod();
+ final var bindingController = getInputMethodBindingController(userId);
+ final InputMethodInfo imi = bindingController.getSelectedMethod();
if (imi != null) {
- mSwitchingController.onUserActionLocked(imi,
- getInputMethodBindingController(userId).getCurrentSubtype());
+ getUserData(userId).mSwitchingController.onUserActionLocked(imi,
+ bindingController.getCurrentSubtype());
}
}
}
@@ -5308,13 +5299,9 @@
updateDefaultVoiceImeIfNeededLocked();
- // TODO: Instantiate mSwitchingController for each user.
- if (userId == mSwitchingController.getUserId()) {
- mSwitchingController.resetCircularListLocked(settings);
- } else {
- mSwitchingController = new InputMethodSubtypeSwitchingController(mContext, settings);
- }
- getUserData(userId).mHardwareKeyboardShortcutController.update(settings);
+ final var userData = getUserData(userId);
+ userData.mSwitchingController.resetCircularListLocked(settings);
+ userData.mHardwareKeyboardShortcutController.update(settings);
sendOnNavButtonFlagsChangedLocked();
@@ -6115,6 +6102,8 @@
p.println(" hasMainConnection="
+ u.mBindingController.hasMainConnection());
p.println(" isVisibleBound=" + u.mBindingController.isVisibleBound());
+ p.println(" mSwitchingController:");
+ u.mSwitchingController.dump(p, " ");
};
mUserDataRepository.forAllUserData(userDataDump);
@@ -6135,8 +6124,6 @@
p.println(" mSettingsObserver=" + mSettingsObserver);
p.println(" mStylusIds=" + (mStylusIds != null
? Arrays.toString(mStylusIds.toArray()) : ""));
- p.println(" mSwitchingController:");
- mSwitchingController.dump(p, " ");
p.println(" mStartInputHistory:");
mStartInputHistory.dump(pw, " ");
diff --git a/services/core/java/com/android/server/inputmethod/UserDataRepository.java b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
index 5da4e89..1f089bf 100644
--- a/services/core/java/com/android/server/inputmethod/UserDataRepository.java
+++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.content.Context;
import android.content.pm.UserInfo;
import android.os.Handler;
import android.util.SparseArray;
@@ -35,12 +36,15 @@
private final IntFunction<InputMethodBindingController> mBindingControllerFactory;
+ @NonNull
+ private final Context mContext;
+
@GuardedBy("ImfLock.class")
@NonNull
UserData getOrCreate(@UserIdInt int userId) {
UserData userData = mUserData.get(userId);
if (userData == null) {
- userData = new UserData(userId, mBindingControllerFactory.apply(userId));
+ userData = new UserData(userId, mBindingControllerFactory.apply(userId), mContext);
mUserData.put(userId, userData);
}
return userData;
@@ -53,8 +57,11 @@
}
}
- UserDataRepository(@NonNull Handler handler, @NonNull UserManagerInternal userManagerInternal,
+ UserDataRepository(
+ @NonNull Context context,
+ @NonNull Handler handler, @NonNull UserManagerInternal userManagerInternal,
@NonNull IntFunction<InputMethodBindingController> bindingControllerFactory) {
+ mContext = context;
mBindingControllerFactory = bindingControllerFactory;
userManagerInternal.addUserLifecycleListener(
new UserManagerInternal.UserLifecycleListener() {
@@ -89,17 +96,23 @@
final InputMethodBindingController mBindingController;
@NonNull
+ final InputMethodSubtypeSwitchingController mSwitchingController;
+
+ @NonNull
final HardwareKeyboardShortcutController mHardwareKeyboardShortcutController;
/**
* Intended to be instantiated only from this file.
*/
private UserData(@UserIdInt int userId,
- @NonNull InputMethodBindingController bindingController) {
+ @NonNull InputMethodBindingController bindingController, @NonNull Context context) {
mUserId = userId;
mBindingController = bindingController;
+ final var emptySettings = InputMethodSettings.createEmptyMap(userId);
+ mSwitchingController = new InputMethodSubtypeSwitchingController(context,
+ emptySettings);
mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController(
- InputMethodSettings.createEmptyMap(userId));
+ emptySettings);
}
@Override
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index 286e789..1938150 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -68,7 +68,6 @@
import android.location.LocationRequest;
import android.location.LocationResult;
import android.location.LocationResult.BadLocationException;
-import android.location.flags.Flags;
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
import android.location.util.identity.CallerIdentity;
@@ -1050,22 +1049,10 @@
stopBatching();
if (mStarted && mGnssNative.getCapabilities().hasScheduling()) {
- if (Flags.gnssCallStopBeforeSetPositionMode()) {
- GnssPositionMode positionMode = new GnssPositionMode(mPositionMode,
- GNSS_POSITION_RECURRENCE_PERIODIC, mFixInterval,
- /* preferredAccuracy= */ 0,
- /* preferredTime= */ 0,
- mProviderRequest.isLowPower());
- if (!positionMode.equals(mLastPositionMode)) {
- stopNavigating();
- startNavigating();
- }
- } else {
- // change period and/or lowPowerMode
- if (!setPositionMode(mPositionMode, GNSS_POSITION_RECURRENCE_PERIODIC,
- mFixInterval, mProviderRequest.isLowPower())) {
- Log.e(TAG, "set_position_mode failed in updateRequirements");
- }
+ // change period and/or lowPowerMode
+ if (!setPositionMode(mPositionMode, GNSS_POSITION_RECURRENCE_PERIODIC,
+ mFixInterval, mProviderRequest.isLowPower())) {
+ Log.e(TAG, "set_position_mode failed in updateRequirements");
}
} else if (!mStarted) {
// start GPS
@@ -1248,32 +1235,11 @@
}
int interval = mGnssNative.getCapabilities().hasScheduling() ? mFixInterval : 1000;
-
- if (Flags.gnssCallStopBeforeSetPositionMode()) {
- boolean success = mGnssNative.setPositionMode(mPositionMode,
- GNSS_POSITION_RECURRENCE_PERIODIC, interval,
- /* preferredAccuracy= */ 0,
- /* preferredTime= */ 0,
- mProviderRequest.isLowPower());
- if (success) {
- mLastPositionMode = new GnssPositionMode(mPositionMode,
- GNSS_POSITION_RECURRENCE_PERIODIC, interval,
- /* preferredAccuracy= */ 0,
- /* preferredTime= */ 0,
- mProviderRequest.isLowPower());
- } else {
- mLastPositionMode = null;
- setStarted(false);
- Log.e(TAG, "set_position_mode failed in startNavigating()");
- return;
- }
- } else {
- if (!setPositionMode(mPositionMode, GNSS_POSITION_RECURRENCE_PERIODIC,
- interval, mProviderRequest.isLowPower())) {
- setStarted(false);
- Log.e(TAG, "set_position_mode failed in startNavigating()");
- return;
- }
+ if (!setPositionMode(mPositionMode, GNSS_POSITION_RECURRENCE_PERIODIC,
+ interval, mProviderRequest.isLowPower())) {
+ setStarted(false);
+ Log.e(TAG, "set_position_mode failed in startNavigating()");
+ return;
}
if (!mGnssNative.start()) {
setStarted(false);
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 11b9e77..f85b8cc 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -17,6 +17,7 @@
package com.android.server.power;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
@@ -197,9 +198,10 @@
FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector,
Executor backgroundExecutor, PowerManagerFlags powerManagerFlags, Injector injector) {
mContext = context;
+ mInjector = (injector == null) ? new RealInjector() : injector;
mFlags = powerManagerFlags;
mBatteryStats = batteryStats;
- mAppOps = mContext.getSystemService(AppOpsManager.class);
+ mAppOps = mInjector.getAppOpsManager(context);
mSuspendBlocker = suspendBlocker;
mPolicy = policy;
mFaceDownDetector = faceDownDetector;
@@ -230,7 +232,6 @@
mShowWirelessChargingAnimationConfig = context.getResources().getBoolean(
com.android.internal.R.bool.config_showBuiltinWirelessChargingAnim);
- mInjector = (injector == null) ? new RealInjector() : injector;
mWakeLockLog = mInjector.getWakeLockLog(context);
// Initialize interactive state for battery stats.
try {
@@ -264,6 +265,7 @@
/**
* Called when a wake lock is acquired.
*/
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public void onWakeLockAcquired(int flags, String tag, String packageName,
int ownerUid, int ownerPid, WorkSource workSource, String historyTag,
IWakeLockCallback callback) {
@@ -273,27 +275,28 @@
+ ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid
+ ", workSource=" + workSource);
}
- notifyWakeLockListener(callback, tag, true, ownerUid, flags);
- final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
- if (monitorType >= 0) {
- try {
- final boolean unimportantForLogging = ownerUid == Process.SYSTEM_UID
- && (flags & PowerManager.UNIMPORTANT_FOR_LOGGING) != 0;
- if (workSource != null) {
- mBatteryStats.noteStartWakelockFromSource(workSource, ownerPid, tag,
- historyTag, monitorType, unimportantForLogging);
- } else {
- mBatteryStats.noteStartWakelock(ownerUid, ownerPid, tag, historyTag,
- monitorType, unimportantForLogging);
- // XXX need to deal with disabled operations.
- mAppOps.startOpNoThrow(AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName);
- }
- } catch (RemoteException ex) {
- // Ignore
- }
- }
-
+ notifyWakeLockListener(callback, tag, true, ownerUid, ownerPid, flags, workSource,
+ packageName, historyTag);
if (!mFlags.improveWakelockLatency()) {
+ final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
+ if (monitorType >= 0) {
+ try {
+ final boolean unimportantForLogging = ownerUid == Process.SYSTEM_UID
+ && (flags & PowerManager.UNIMPORTANT_FOR_LOGGING) != 0;
+ if (workSource != null) {
+ mBatteryStats.noteStartWakelockFromSource(workSource, ownerPid, tag,
+ historyTag, monitorType, unimportantForLogging);
+ } else {
+ mBatteryStats.noteStartWakelock(ownerUid, ownerPid, tag, historyTag,
+ monitorType, unimportantForLogging);
+ // XXX need to deal with disabled operations.
+ mAppOps.startOpNoThrow(AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName,
+ false, null, null);
+ }
+ } catch (RemoteException ex) {
+ // Ignore
+ }
+ }
mWakeLockLog.onWakeLockAcquired(tag, ownerUid, flags, /*eventTime=*/ -1);
}
mWakefulnessSessionObserver.onWakeLockAcquired(flags);
@@ -404,6 +407,7 @@
/**
* Called when a wake lock is released.
*/
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public void onWakeLockReleased(int flags, String tag, String packageName,
int ownerUid, int ownerPid, WorkSource workSource, String historyTag,
IWakeLockCallback callback, int releaseReason) {
@@ -413,23 +417,24 @@
+ ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid
+ ", workSource=" + workSource);
}
- notifyWakeLockListener(callback, tag, false, ownerUid, flags);
- final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
- if (monitorType >= 0) {
- try {
- if (workSource != null) {
- mBatteryStats.noteStopWakelockFromSource(workSource, ownerPid, tag,
- historyTag, monitorType);
- } else {
- mBatteryStats.noteStopWakelock(ownerUid, ownerPid, tag,
- historyTag, monitorType);
- mAppOps.finishOp(AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName);
- }
- } catch (RemoteException ex) {
- // Ignore
- }
- }
+ notifyWakeLockListener(callback, tag, false, ownerUid, ownerPid, flags, workSource,
+ packageName, historyTag);
if (!mFlags.improveWakelockLatency()) {
+ final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
+ if (monitorType >= 0) {
+ try {
+ if (workSource != null) {
+ mBatteryStats.noteStopWakelockFromSource(workSource, ownerPid, tag,
+ historyTag, monitorType);
+ } else {
+ mBatteryStats.noteStopWakelock(ownerUid, ownerPid, tag,
+ historyTag, monitorType);
+ mAppOps.finishOp(AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName, null);
+ }
+ } catch (RemoteException ex) {
+ // Ignore
+ }
+ }
mWakeLockLog.onWakeLockReleased(tag, ownerUid, /*eventTime=*/ -1);
}
mWakefulnessSessionObserver.onWakeLockReleased(flags, releaseReason);
@@ -1049,24 +1054,75 @@
}
private void notifyWakeLockListener(IWakeLockCallback callback, String tag, boolean isEnabled,
- int ownerUid, int flags) {
- if (callback != null) {
- long currentTime = mInjector.currentTimeMillis();
- mHandler.post(() -> {
+ int ownerUid, int ownerPid, int flags, WorkSource workSource, String packageName,
+ String historyTag) {
+ mHandler.post(() -> {
+ if (mFlags.improveWakelockLatency()) {
+ long currentTime = mInjector.currentTimeMillis();
+ if (isEnabled) {
+ notifyWakelockAcquisition(tag, ownerUid, ownerPid, flags,
+ workSource, packageName, historyTag, currentTime);
+ } else {
+ notifyWakelockRelease(tag, ownerUid, ownerPid, flags,
+ workSource, packageName, historyTag, currentTime);
+ }
+ }
+
+ if (callback != null) {
try {
- if (mFlags.improveWakelockLatency()) {
- if (isEnabled) {
- mWakeLockLog.onWakeLockAcquired(tag, ownerUid, flags, currentTime);
- } else {
- mWakeLockLog.onWakeLockReleased(tag, ownerUid, currentTime);
- }
- }
callback.onStateChanged(isEnabled);
} catch (RemoteException e) {
Slog.e(TAG, "Wakelock.mCallback [" + tag + "] is already dead.", e);
}
- });
+ }
+ });
+ }
+
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ private void notifyWakelockAcquisition(String tag, int ownerUid, int ownerPid, int flags,
+ WorkSource workSource, String packageName, String historyTag, long currentTime) {
+ final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
+ if (monitorType >= 0) {
+ try {
+ final boolean unimportantForLogging = ownerUid == Process.SYSTEM_UID
+ && (flags & PowerManager.UNIMPORTANT_FOR_LOGGING) != 0;
+ if (workSource != null) {
+ mBatteryStats.noteStartWakelockFromSource(workSource, ownerPid, tag,
+ historyTag, monitorType, unimportantForLogging);
+ } else {
+ mBatteryStats.noteStartWakelock(ownerUid, ownerPid, tag, historyTag,
+ monitorType, unimportantForLogging);
+ // XXX need to deal with disabled operations.
+ mAppOps.startOpNoThrow(
+ AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName,
+ false, null, null);
+ }
+ } catch (RemoteException ex) {
+ // Do Nothing
+ }
}
+ mWakeLockLog.onWakeLockAcquired(tag, ownerUid, flags, currentTime);
+ }
+
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ private void notifyWakelockRelease(String tag, int ownerUid, int ownerPid, int flags,
+ WorkSource workSource, String packageName, String historyTag, long currentTime) {
+ final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
+ if (monitorType >= 0) {
+ try {
+ if (workSource != null) {
+ mBatteryStats.noteStopWakelockFromSource(workSource, ownerPid, tag,
+ historyTag, monitorType);
+ } else {
+ mBatteryStats.noteStopWakelock(ownerUid, ownerPid, tag,
+ historyTag, monitorType);
+ mAppOps.finishOp(AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName, null);
+ }
+ } catch (RemoteException ex) {
+ // Ignore
+ }
+ }
+ mWakeLockLog.onWakeLockReleased(tag, ownerUid, currentTime);
}
private final class NotifierHandler extends Handler {
@@ -1114,6 +1170,11 @@
* Gets the WakeLockLog object
*/
WakeLockLog getWakeLockLog(Context context);
+
+ /**
+ * Gets the AppOpsManager system service
+ */
+ AppOpsManager getAppOpsManager(Context context);
}
static class RealInjector implements Injector {
@@ -1126,5 +1187,10 @@
public WakeLockLog getWakeLockLog(Context context) {
return new WakeLockLog(context);
}
+
+ @Override
+ public AppOpsManager getAppOpsManager(Context context) {
+ return context.getSystemService(AppOpsManager.class);
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index e9c08e0..717c399 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1649,7 +1649,8 @@
// activity, so this isn't just deliver-to-top
&& mMovedToTopActivity == null
&& !transitionController.hasOrderChanges()
- && !transitionController.isTransientHide(startedActivityRootTask)) {
+ && !transitionController.isTransientHide(startedActivityRootTask)
+ && !newTransition.hasChanged(mLastStartActivityRecord)) {
// We just delivered to top, so there isn't an actual transition here.
if (!forceTransientTransition) {
newTransition.abort();
@@ -1792,6 +1793,7 @@
activity.destroyIfPossible("Removes redundant singleInstance");
}
}
+ targetTaskTop.mTransitionController.collect(targetTaskTop);
recordTransientLaunchIfNeeded(targetTaskTop);
// Recycle the target task for this launch.
startResult =
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index b6e6991..cd5576f 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -952,7 +952,7 @@
// Schedule transaction.
if (shouldDispatchLaunchActivityItemIndependently(r.info.packageName, r.getUid())) {
// LaunchActivityItem has @UnsupportedAppUsage usages.
- // Guard the bundleClientTransactionFlag feature with targetSDK on Android 15+.
+ // Guard with targetSDK on Android 15+.
// To not bundle the transaction, dispatch the pending before schedule new
// transaction.
mService.getLifecycleManager().dispatchPendingTransaction(proc.getThread());
diff --git a/services/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
index e396f2b..bf34759 100644
--- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java
+++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
@@ -35,7 +35,6 @@
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.window.flags.Flags;
/**
* Class that is able to combine multiple client lifecycle transition requests and/or callbacks,
@@ -72,9 +71,8 @@
* @throws RemoteException
*
* @see ClientTransaction
- * @deprecated use {@link #scheduleTransactionItem(IApplicationThread, ClientTransactionItem)}.
*/
- @Deprecated
+ @VisibleForTesting
void scheduleTransaction(@NonNull ClientTransaction transaction) throws RemoteException {
final IApplicationThread client = transaction.getClient();
try {
@@ -114,22 +112,13 @@
*/
void scheduleTransactionItem(@NonNull IApplicationThread client,
@NonNull ClientTransactionItem transactionItem) throws RemoteException {
- // The behavior is different depending on the flag.
- // When flag is on, we wait until RootWindowContainer#performSurfacePlacementNoTrace to
- // dispatch all pending transactions at once.
- if (Flags.bundleClientTransactionFlag()) {
- final ClientTransaction clientTransaction = getOrCreatePendingTransaction(client);
- clientTransaction.addTransactionItem(transactionItem);
+ // Wait until RootWindowContainer#performSurfacePlacementNoTrace to dispatch all pending
+ // transactions at once.
+ final ClientTransaction clientTransaction = getOrCreatePendingTransaction(client);
+ clientTransaction.addTransactionItem(transactionItem);
- onClientTransactionItemScheduled(clientTransaction,
- false /* shouldDispatchImmediately */);
- } else {
- // TODO(b/260873529): cleanup after launch.
- final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
- clientTransaction.addTransactionItem(transactionItem);
-
- scheduleTransaction(clientTransaction);
- }
+ onClientTransactionItemScheduled(clientTransaction,
+ false /* shouldDispatchImmediately */);
}
void scheduleTransactionAndLifecycleItems(@NonNull IApplicationThread client,
@@ -156,27 +145,18 @@
@NonNull ClientTransactionItem transactionItem,
@NonNull ActivityLifecycleItem lifecycleItem,
boolean shouldDispatchImmediately) throws RemoteException {
- // The behavior is different depending on the flag.
- // When flag is on, we wait until RootWindowContainer#performSurfacePlacementNoTrace to
- // dispatch all pending transactions at once.
- if (Flags.bundleClientTransactionFlag()) {
- final ClientTransaction clientTransaction = getOrCreatePendingTransaction(client);
- clientTransaction.addTransactionItem(transactionItem);
- clientTransaction.addTransactionItem(lifecycleItem);
+ // Wait until RootWindowContainer#performSurfacePlacementNoTrace to dispatch all pending
+ // transactions at once.
+ final ClientTransaction clientTransaction = getOrCreatePendingTransaction(client);
+ clientTransaction.addTransactionItem(transactionItem);
+ clientTransaction.addTransactionItem(lifecycleItem);
- onClientTransactionItemScheduled(clientTransaction, shouldDispatchImmediately);
- } else {
- // TODO(b/260873529): cleanup after launch.
- final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
- clientTransaction.addTransactionItem(transactionItem);
- clientTransaction.addTransactionItem(lifecycleItem);
- scheduleTransaction(clientTransaction);
- }
+ onClientTransactionItemScheduled(clientTransaction, shouldDispatchImmediately);
}
/** Executes all the pending transactions. */
void dispatchPendingTransactions() {
- if (!Flags.bundleClientTransactionFlag() || mPendingTransactions.isEmpty()) {
+ if (mPendingTransactions.isEmpty()) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "clientTransactionsDispatched");
@@ -196,9 +176,6 @@
/** Executes the pending transaction for the given client process. */
void dispatchPendingTransaction(@NonNull IApplicationThread client) {
- if (!Flags.bundleClientTransactionFlag()) {
- return;
- }
final ClientTransaction pendingTransaction = mPendingTransactions.remove(client.asBinder());
if (pendingTransaction != null) {
try {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 54ba47e..6abd488 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -152,7 +152,6 @@
import com.android.server.policy.PermissionPolicyInternal;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.utils.Slogf;
-import com.android.window.flags.Flags;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -789,13 +788,12 @@
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
- if (Flags.bundleClientTransactionFlag()) {
- // mWmService.mResizingWindows is populated in #applySurfaceChangesTransaction()
- handleResizingWindows();
+ // mWmService.mResizingWindows is populated in #applySurfaceChangesTransaction()
+ handleResizingWindows();
+ clearFrameChangingWindows();
- // Called after #handleResizingWindows to include WindowStateResizeItem if any.
- mWmService.mAtmService.getLifecycleManager().dispatchPendingTransactions();
- }
+ // Called after #handleResizingWindows to include WindowStateResizeItem if any.
+ mWmService.mAtmService.getLifecycleManager().dispatchPendingTransactions();
// Send any pending task-info changes that were queued-up during a layout deferment
mWmService.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
@@ -839,11 +837,6 @@
}
}
- if (!Flags.bundleClientTransactionFlag()) {
- handleResizingWindows();
- }
- clearFrameChangingWindows();
-
if (mWmService.mDisplayFrozen) {
ProtoLog.v(WM_DEBUG_ORIENTATION,
"With display frozen, orientationChangeComplete=%b",
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index acdb66a..3cd071b 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -82,7 +82,6 @@
import android.app.ResultInfo;
import android.app.WindowConfiguration;
import android.app.servertransaction.ActivityResultItem;
-import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.NewIntentItem;
import android.app.servertransaction.PauseActivityItem;
import android.app.servertransaction.ResumeActivityItem;
@@ -1604,9 +1603,6 @@
try {
final IApplicationThread appThread = next.app.getThread();
- final ClientTransaction transaction = Flags.bundleClientTransactionFlag()
- ? null
- : ClientTransaction.obtain(appThread);
// Deliver all pending results.
final ArrayList<ResultInfo> a = next.results;
if (a != null) {
@@ -1617,24 +1613,16 @@
}
final ActivityResultItem activityResultItem = ActivityResultItem.obtain(
next.token, a);
- if (transaction == null) {
- mAtmService.getLifecycleManager().scheduleTransactionItem(
- appThread, activityResultItem);
- } else {
- transaction.addTransactionItem(activityResultItem);
- }
+ mAtmService.getLifecycleManager().scheduleTransactionItem(
+ appThread, activityResultItem);
}
}
if (next.newIntents != null) {
final NewIntentItem newIntentItem = NewIntentItem.obtain(
next.token, next.newIntents, true /* resume */);
- if (transaction == null) {
- mAtmService.getLifecycleManager().scheduleTransactionItem(
- appThread, newIntentItem);
- } else {
- transaction.addTransactionItem(newIntentItem);
- }
+ mAtmService.getLifecycleManager().scheduleTransactionItem(
+ appThread, newIntentItem);
}
// Well the app will no longer be stopped.
@@ -1651,13 +1639,8 @@
final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(
next.token, topProcessState, dc.isNextTransitionForward(),
next.shouldSendCompatFakeFocus());
- if (transaction == null) {
- mAtmService.getLifecycleManager().scheduleTransactionItem(
- appThread, resumeActivityItem);
- } else {
- transaction.addTransactionItem(resumeActivityItem);
- mAtmService.getLifecycleManager().scheduleTransaction(transaction);
- }
+ mAtmService.getLifecycleManager().scheduleTransactionItem(
+ appThread, resumeActivityItem);
ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Resumed %s", next);
} catch (Exception e) {
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index c4e932a..26315f9 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -171,11 +171,7 @@
TaskFragmentOrganizerState(@NonNull ITaskFragmentOrganizer organizer, int pid, int uid,
boolean isSystemOrganizer) {
- if (Flags.bundleClientTransactionFlag()) {
- mAppThread = getAppThread(pid, uid);
- } else {
- mAppThread = null;
- }
+ mAppThread = getAppThread(pid, uid);
mOrganizer = organizer;
mOrganizerPid = pid;
mOrganizerUid = uid;
@@ -431,13 +427,9 @@
return;
}
try {
- if (Flags.bundleClientTransactionFlag()) {
- // Dispatch through IApplicationThread to ensure the binder call is in order
- // with ClientTransaction.
- mAppThread.scheduleTaskFragmentTransaction(mOrganizer, transaction);
- } else {
- mOrganizer.onTransactionReady(transaction);
- }
+ // Dispatch through IApplicationThread to ensure the binder call is in order
+ // with ClientTransaction.
+ mAppThread.scheduleTaskFragmentTransaction(mOrganizer, transaction);
} catch (RemoteException e) {
Slog.d(TAG, "Exception sending TaskFragmentTransaction", e);
return;
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 2572128..33b604c 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -3231,6 +3231,12 @@
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_WINDOW_MANAGER, TAG, cookie);
}
+ boolean hasChanged(WindowContainer wc) {
+ final ChangeInfo chg = mChanges.get(wc);
+ if (chg == null) return false;
+ return chg.hasChanged();
+ }
+
@VisibleForTesting
static class ChangeInfo {
private static final int FLAG_NONE = 0;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 9d4a3b8..b08135f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3760,31 +3760,13 @@
final boolean isDragResizing = isDragResizing();
markRedrawForSyncReported();
-
- if (Flags.bundleClientTransactionFlag()) {
- getProcess().scheduleClientTransactionItem(
- WindowStateResizeItem.obtain(mClient, mLastReportedFrames, reportDraw,
- mLastReportedConfiguration, mLastReportedInsetsState, forceRelayout,
- alwaysConsumeSystemBars, displayId,
- syncWithBuffers ? mSyncSeqId : -1, isDragResizing,
- mLastReportedActivityWindowInfo));
- onResizePostDispatched(drawPending, prevRotation, displayId);
- } else {
- // TODO(b/301870955): cleanup after launch
- try {
- mClient.resized(mLastReportedFrames, reportDraw, mLastReportedConfiguration,
- mLastReportedInsetsState, forceRelayout, alwaysConsumeSystemBars, displayId,
+ getProcess().scheduleClientTransactionItem(
+ WindowStateResizeItem.obtain(mClient, mLastReportedFrames, reportDraw,
+ mLastReportedConfiguration, mLastReportedInsetsState, forceRelayout,
+ alwaysConsumeSystemBars, displayId,
syncWithBuffers ? mSyncSeqId : -1, isDragResizing,
- mLastReportedActivityWindowInfo);
- onResizePostDispatched(drawPending, prevRotation, displayId);
- } catch (RemoteException e) {
- // Cancel orientation change of this window to avoid blocking unfreeze display.
- setOrientationChanging(false);
- mLastFreezeDuration = (int) (SystemClock.elapsedRealtime()
- - mWmService.mDisplayFreezeTime);
- Slog.w(TAG, "Failed to report 'resized' to " + this + " due to " + e);
- }
- }
+ mLastReportedActivityWindowInfo));
+ onResizePostDispatched(drawPending, prevRotation, displayId);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index 42bd75a..2dfb303 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -181,6 +181,7 @@
// InputMethodManagerService.
doNothing().when(mContext).enforceCallingPermission(anyString(), anyString());
doNothing().when(mContext).sendBroadcastAsUser(any(), any());
+ doReturn(mContext).when(mContext).createContextAsUser(any(), anyInt());
doReturn(null).when(mContext).registerReceiver(any(), any());
doReturn(null)
.when(mContext)
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
index 79943f6..7592d34 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
@@ -18,16 +18,22 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.content.Context;
import android.content.pm.UserInfo;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.Looper;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.platform.app.InstrumentationRegistry;
+
import com.android.server.pm.UserManagerInternal;
import org.junit.After;
@@ -61,10 +67,15 @@
private IntFunction<InputMethodBindingController> mBindingControllerFactory;
+ private Context mMockedContext;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
SecureSettingsWrapper.startTestMode();
+ mMockedContext = spy(InstrumentationRegistry.getInstrumentation().getTargetContext());
+ doReturn(mMockedContext).when(mMockedContext).createContextAsUser(any(), anyInt());
+
mHandler = new Handler(Looper.getMainLooper());
mBindingControllerFactory = new IntFunction<InputMethodBindingController>() {
@@ -85,8 +96,8 @@
// Create UserDataRepository and capture the user lifecycle listener
final var captor = ArgumentCaptor.forClass(UserManagerInternal.UserLifecycleListener.class);
final var bindingControllerFactorySpy = spy(mBindingControllerFactory);
- final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal,
- bindingControllerFactorySpy);
+ final var repository = new UserDataRepository(mMockedContext, mHandler,
+ mMockUserManagerInternal, bindingControllerFactorySpy);
verify(mMockUserManagerInternal, times(1)).addUserLifecycleListener(captor.capture());
final var listener = captor.getValue();
@@ -112,7 +123,8 @@
public void testUserDataRepository_removesUserInfoOnUserRemovedEvent() {
// Create UserDataRepository and capture the user lifecycle listener
final var captor = ArgumentCaptor.forClass(UserManagerInternal.UserLifecycleListener.class);
- final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal,
+ final var repository = new UserDataRepository(mMockedContext, mHandler,
+ mMockUserManagerInternal,
userId -> new InputMethodBindingController(userId, mMockInputMethodManagerService));
verify(mMockUserManagerInternal, times(1)).addUserLifecycleListener(captor.capture());
@@ -134,8 +146,8 @@
@Test
public void testGetOrCreate() {
- final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal,
- mBindingControllerFactory);
+ final var repository = new UserDataRepository(mMockedContext, mHandler,
+ mMockUserManagerInternal, mBindingControllerFactory);
synchronized (ImfLock.class) {
final var userData = repository.getOrCreate(ANY_USER_ID);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
index 88d3238..d7936fe 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
@@ -255,7 +256,8 @@
@Test
public void setBrightnessSetsInBrightnessSetting() {
float brightnessValue = 0.3f;
- mDisplayBrightnessController.setBrightness(brightnessValue);
+ float maxBrightnessValue = 0.65f;
+ mDisplayBrightnessController.setBrightness(brightnessValue, maxBrightnessValue);
verify(mBrightnessSetting).setBrightness(brightnessValue);
}
@@ -266,20 +268,24 @@
// Sets the appropriate value when valid, and not equal to the current brightness
float brightnessValue = 0.3f;
- mDisplayBrightnessController.updateScreenBrightnessSetting(brightnessValue);
+ float maxBrightnessValue = 0.65f;
+ mDisplayBrightnessController.updateScreenBrightnessSetting(brightnessValue,
+ maxBrightnessValue);
assertEquals(mDisplayBrightnessController.getCurrentBrightness(), brightnessValue, 0.0f);
verify(mBrightnessChangeExecutor).execute(mOnBrightnessChangeRunnable);
verify(mBrightnessSetting).setBrightness(brightnessValue);
// Does nothing if the value is invalid
- mDisplayBrightnessController.updateScreenBrightnessSetting(Float.NaN);
+ clearInvocations(mBrightnessSetting);
+ mDisplayBrightnessController.updateScreenBrightnessSetting(Float.NaN, maxBrightnessValue);
verifyNoMoreInteractions(mBrightnessChangeExecutor, mBrightnessSetting);
// Does nothing if the value is same as the current brightness
brightnessValue = 0.2f;
mDisplayBrightnessController.setAndNotifyCurrentScreenBrightness(brightnessValue);
verify(mBrightnessChangeExecutor, times(2)).execute(mOnBrightnessChangeRunnable);
- mDisplayBrightnessController.updateScreenBrightnessSetting(brightnessValue);
+ mDisplayBrightnessController.updateScreenBrightnessSetting(brightnessValue,
+ maxBrightnessValue);
verifyNoMoreInteractions(mBrightnessChangeExecutor, mBrightnessSetting);
}
@@ -366,7 +372,7 @@
when(mBrightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
mDisplayBrightnessController.setAutomaticBrightnessController(
automaticBrightnessController);
- verify(mBrightnessSetting).setBrightness(brightness);
+ verify(mBrightnessSetting).setBrightnessNoNotify(brightness);
assertEquals(brightness, mDisplayBrightnessController.getCurrentBrightness(), 0.01f);
clearInvocations(automaticBrightnessController, mBrightnessSetting);
@@ -378,7 +384,7 @@
when(mBrightnessSetting.getBrightness()).thenReturn(brightness);
mDisplayBrightnessController.setAutomaticBrightnessController(
automaticBrightnessController);
- verify(mBrightnessSetting, never()).setBrightness(brightness);
+ verify(mBrightnessSetting, never()).setBrightnessNoNotify(brightness);
assertEquals(brightness, mDisplayBrightnessController.getCurrentBrightness(), 0.01f);
clearInvocations(automaticBrightnessController, mBrightnessSetting);
@@ -395,15 +401,73 @@
assertEquals(brightness, mDisplayBrightnessController.getCurrentBrightness(), 0.01f);
verifyZeroInteractions(automaticBrightnessController);
verify(mBrightnessSetting, never()).getBrightnessNitsForDefaultDisplay();
- verify(mBrightnessSetting, never()).setBrightness(brightness);
+ verify(mBrightnessSetting, never()).setBrightnessNoNotify(brightness);
}
@Test
+ public void testDoesNotSetBrightnessNits_settingMaxBrightnessAndStoredValueGreater() {
+ float brightnessValue = 0.65f;
+ float maxBrightness = 0.65f;
+ float nits = 200f;
+ float storedNits = 300f;
+
+ when(mBrightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(storedNits);
+ AutomaticBrightnessController automaticBrightnessController = mock(
+ AutomaticBrightnessController.class);
+ when(automaticBrightnessController.convertToNits(brightnessValue)).thenReturn(nits);
+ mDisplayBrightnessController.mAutomaticBrightnessController = automaticBrightnessController;
+
+ mDisplayBrightnessController.setBrightness(brightnessValue, maxBrightness);
+
+ verify(mBrightnessSetting, never()).setBrightnessNitsForDefaultDisplay(anyFloat());
+ }
+
+ @Test
+ public void testSetsBrightnessNits_storedValueLower() {
+ float brightnessValue = 0.65f;
+ float maxBrightness = 0.65f;
+ float nits = 200f;
+ float storedNits = 100f;
+
+ when(mBrightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(storedNits);
+ AutomaticBrightnessController automaticBrightnessController = mock(
+ AutomaticBrightnessController.class);
+ when(automaticBrightnessController.convertToNits(brightnessValue)).thenReturn(nits);
+ mDisplayBrightnessController.mAutomaticBrightnessController = automaticBrightnessController;
+
+ mDisplayBrightnessController.setBrightness(brightnessValue, maxBrightness);
+
+ verify(mBrightnessSetting).setBrightnessNitsForDefaultDisplay(nits);
+ }
+
+ @Test
+ public void testSetsBrightnessNits_lowerThanMax() {
+ float brightnessValue = 0.60f;
+ float maxBrightness = 0.65f;
+ float nits = 200f;
+ float storedNits = 300f;
+
+ when(mBrightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(storedNits);
+ AutomaticBrightnessController automaticBrightnessController = mock(
+ AutomaticBrightnessController.class);
+ when(automaticBrightnessController.convertToNits(brightnessValue)).thenReturn(nits);
+ mDisplayBrightnessController.mAutomaticBrightnessController = automaticBrightnessController;
+
+ mDisplayBrightnessController.setBrightness(brightnessValue, maxBrightness);
+
+ verify(mBrightnessSetting).setBrightnessNitsForDefaultDisplay(nits);
+ }
+
+
+ @Test
public void testChangeBrightnessNitsWhenUserChanges() {
float brightnessValue1 = 0.3f;
float nits1 = 200f;
+ int userSerial1 = 1;
float brightnessValue2 = 0.5f;
float nits2 = 300f;
+ int userSerial2 = 2;
+ float maxBrightness = 0.65f;
AutomaticBrightnessController automaticBrightnessController = mock(
AutomaticBrightnessController.class);
when(automaticBrightnessController.convertToNits(brightnessValue1)).thenReturn(nits1);
@@ -417,13 +481,13 @@
verify(automaticBrightnessStrategy)
.setAutomaticBrightnessController(automaticBrightnessController);
- mDisplayBrightnessController.setBrightness(brightnessValue1, 1 /* user-serial */);
- verify(mBrightnessSetting).setUserSerial(1);
+ mDisplayBrightnessController.setBrightness(brightnessValue1, userSerial1, maxBrightness);
+ verify(mBrightnessSetting).setUserSerial(userSerial1);
verify(mBrightnessSetting).setBrightness(brightnessValue1);
verify(mBrightnessSetting).setBrightnessNitsForDefaultDisplay(nits1);
- mDisplayBrightnessController.setBrightness(brightnessValue2, 2 /* user-serial */);
- verify(mBrightnessSetting).setUserSerial(2);
+ mDisplayBrightnessController.setBrightness(brightnessValue2, userSerial2, maxBrightness);
+ verify(mBrightnessSetting).setUserSerial(userSerial2);
verify(mBrightnessSetting).setBrightness(brightnessValue2);
verify(mBrightnessSetting).setBrightnessNitsForDefaultDisplay(nits2);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssLocationProviderTest.java
deleted file mode 100644
index c5e6824..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssLocationProviderTest.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.location.gnss;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.app.AlarmManager;
-import android.app.AppOpsManager;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.location.GnssCapabilities;
-import android.location.LocationManager;
-import android.location.LocationManagerInternal;
-import android.location.flags.Flags;
-import android.location.provider.ProviderRequest;
-import android.os.PowerManager;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.flag.junit.SetFlagsRule;
-import android.provider.Settings;
-import android.telephony.TelephonyManager;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.modules.utils.testing.ExtendedMockitoRule;
-import com.android.server.LocalServices;
-import com.android.server.location.gnss.hal.FakeGnssHal;
-import com.android.server.location.gnss.hal.GnssNative;
-import com.android.server.location.injector.Injector;
-import com.android.server.location.injector.TestInjector;
-import com.android.server.timedetector.TimeDetectorInternal;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.quality.Strictness;
-
-import java.util.HashSet;
-import java.util.Objects;
-import java.util.Set;
-
-@Presubmit
-@androidx.test.filters.SmallTest
-@RunWith(AndroidJUnit4.class)
-public class GnssLocationProviderTest {
-
- @Rule
- public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
- .setStrictness(Strictness.WARN)
- .mockStatic(Settings.Global.class)
- .build();
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
- private @Mock Context mContext;
- private @Mock LocationManagerInternal mLocationManagerInternal;
- private @Mock LocationManager mLocationManager;
- private @Mock TimeDetectorInternal mTimeDetectorInternal;
- private @Mock GnssConfiguration mMockConfiguration;
- private @Mock GnssMetrics mGnssMetrics;
- private @Mock PowerManager mPowerManager;
- private @Mock TelephonyManager mTelephonyManager;
- private @Mock AppOpsManager mAppOpsManager;
- private @Mock AlarmManager mAlarmManager;
- private @Mock PowerManager.WakeLock mWakeLock;
- private @Mock ContentResolver mContentResolver;
- private @Mock UserManager mUserManager;
- private @Mock UserHandle mUserHandle;
- private Set<UserHandle> mUserHandleSet = new HashSet<>();
-
- private GnssNative mGnssNative;
-
- private GnssLocationProvider mTestProvider;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- doReturn("mypackage").when(mContext).getPackageName();
- doReturn("attribution").when(mContext).getAttributionTag();
- doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class);
- doReturn(mPowerManager).when(mContext).getSystemService("power");
- doReturn(mAppOpsManager).when(mContext).getSystemService(AppOpsManager.class);
- doReturn(mTelephonyManager).when(mContext).getSystemService(Context.TELEPHONY_SERVICE);
- doReturn(mAlarmManager).when(mContext).getSystemService(Context.ALARM_SERVICE);
- doReturn(mLocationManager).when(mContext).getSystemService(LocationManager.class);
- doReturn(mUserManager).when(mContext).getSystemService(UserManager.class);
- mUserHandleSet.add(mUserHandle);
- doReturn(true).when(mLocationManager).isLocationEnabledForUser(eq(mUserHandle));
- doReturn(mUserHandleSet).when(mUserManager).getVisibleUsers();
- doReturn(mContentResolver).when(mContext).getContentResolver();
- doReturn(mWakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString());
- LocalServices.addService(LocationManagerInternal.class, mLocationManagerInternal);
- LocalServices.addService(TimeDetectorInternal.class, mTimeDetectorInternal);
- FakeGnssHal fakeGnssHal = new FakeGnssHal();
- GnssNative.setGnssHalForTest(fakeGnssHal);
- Injector injector = new TestInjector(mContext);
- mGnssNative = spy(Objects.requireNonNull(
- GnssNative.create(injector, mMockConfiguration)));
- doReturn(true).when(mGnssNative).init();
- GnssCapabilities gnssCapabilities = new GnssCapabilities.Builder().setHasScheduling(
- true).build();
- doReturn(gnssCapabilities).when(mGnssNative).getCapabilities();
-
- mTestProvider = new GnssLocationProvider(mContext, mGnssNative, mGnssMetrics);
- mGnssNative.register();
- }
-
- @After
- public void tearDown() {
- LocalServices.removeServiceForTest(LocationManagerInternal.class);
- LocalServices.removeServiceForTest(TimeDetectorInternal.class);
- }
-
- @Test
- public void testStartNavigating() {
- ProviderRequest providerRequest = new ProviderRequest.Builder().setIntervalMillis(
- 0).build();
-
- mTestProvider.onSetRequest(providerRequest);
- verify(mGnssNative).start();
- }
-
- @Test
- public void testUpdateRequirements_sameRequest() {
- mSetFlagsRule.enableFlags(Flags.FLAG_GNSS_CALL_STOP_BEFORE_SET_POSITION_MODE);
- ProviderRequest providerRequest = new ProviderRequest.Builder().setIntervalMillis(
- 0).build();
-
- mTestProvider.onSetRequest(providerRequest);
- verify(mGnssNative).start();
-
- // set the same request
- mTestProvider.onSetRequest(providerRequest);
- verify(mGnssNative, never()).stop();
- verify(mGnssNative, times(1)).start();
- }
-
- @Test
- public void testUpdateRequirements_differentRequest() {
- mSetFlagsRule.enableFlags(Flags.FLAG_GNSS_CALL_STOP_BEFORE_SET_POSITION_MODE);
- ProviderRequest providerRequest = new ProviderRequest.Builder().setIntervalMillis(
- 0).build();
-
- mTestProvider.onSetRequest(providerRequest);
- verify(mGnssNative).start();
-
- // set a different request
- providerRequest = new ProviderRequest.Builder().setIntervalMillis(2000).build();
- mTestProvider.onSetRequest(providerRequest);
- verify(mGnssNative).stop();
- verify(mGnssNative, times(2)).start();
- }
-}
diff --git a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
index 4460c6a..ce2bb95 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
@@ -31,6 +31,7 @@
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
+import android.app.AppOpsManager;
import android.content.Context;
import android.content.res.Resources;
import android.hardware.SensorManager;
@@ -41,7 +42,6 @@
import android.os.Looper;
import android.os.PowerManager;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.VibrationAttributes;
import android.os.Vibrator;
import android.os.test.TestLooper;
@@ -82,8 +82,12 @@
@Mock private StatusBarManagerInternal mStatusBarManagerInternal;
@Mock private WakeLockLog mWakeLockLog;
+ @Mock private IBatteryStats mBatteryStats;
+
@Mock private PowerManagerFlags mPowerManagerFlags;
+ @Mock private AppOpsManager mAppOpsManager;
+
private PowerManagerService mService;
private Context mContextSpy;
private Resources mResourcesSpy;
@@ -230,7 +234,7 @@
public void testOnWakeLockListener_RemoteException_NoRethrow() {
when(mPowerManagerFlags.improveWakelockLatency()).thenReturn(true);
createNotifier();
-
+ clearInvocations(mWakeLockLog, mBatteryStats, mAppOpsManager);
IWakeLockCallback exceptingCallback = new IWakeLockCallback.Stub() {
@Override public void onStateChanged(boolean enabled) throws RemoteException {
throw new RemoteException("Just testing");
@@ -245,6 +249,7 @@
verifyZeroInteractions(mWakeLockLog);
mTestLooper.dispatchAll();
verify(mWakeLockLog).onWakeLockReleased("wakelockTag", uid, 1);
+
mNotifier.onWakeLockAcquired(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag",
"my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
exceptingCallback);
@@ -277,6 +282,115 @@
verify(mWakeLockLog).onWakeLockReleased("wakelockTag", uid, -1);
}
+
+ @Test
+ public void testOnWakeLockListener_FullWakeLock_ProcessesOnHandler() throws RemoteException {
+ when(mPowerManagerFlags.improveWakelockLatency()).thenReturn(true);
+ createNotifier();
+
+ IWakeLockCallback exceptingCallback = new IWakeLockCallback.Stub() {
+ @Override public void onStateChanged(boolean enabled) throws RemoteException {
+ throw new RemoteException("Just testing");
+ }
+ };
+ clearInvocations(mWakeLockLog, mBatteryStats, mAppOpsManager);
+
+ final int uid = 1234;
+ final int pid = 5678;
+
+ // Release the wakelock
+ mNotifier.onWakeLockReleased(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "wakelockTag",
+ "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
+ exceptingCallback);
+
+ // No interaction because we expect that to happen in async
+ verifyZeroInteractions(mWakeLockLog, mBatteryStats, mAppOpsManager);
+
+ // Progressing the looper, and validating all the interactions
+ mTestLooper.dispatchAll();
+ verify(mWakeLockLog).onWakeLockReleased("wakelockTag", uid, 1);
+ verify(mBatteryStats).noteStopWakelock(uid, pid, "wakelockTag", /* historyTag= */ null,
+ BatteryStats.WAKE_TYPE_FULL);
+ verify(mAppOpsManager).finishOp(AppOpsManager.OP_WAKE_LOCK, uid,
+ "my.package.name", null);
+
+ clearInvocations(mWakeLockLog, mBatteryStats, mAppOpsManager);
+
+ // Acquire the wakelock
+ mNotifier.onWakeLockAcquired(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "wakelockTag",
+ "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
+ exceptingCallback);
+
+ // No interaction because we expect that to happen in async
+ verifyNoMoreInteractions(mWakeLockLog, mBatteryStats, mAppOpsManager);
+
+ // Progressing the looper, and validating all the interactions
+ mTestLooper.dispatchAll();
+ verify(mWakeLockLog).onWakeLockAcquired("wakelockTag", uid,
+ PowerManager.SCREEN_BRIGHT_WAKE_LOCK, 1);
+ verify(mBatteryStats).noteStartWakelock(uid, pid, "wakelockTag", /* historyTag= */ null,
+ BatteryStats.WAKE_TYPE_FULL, false);
+ verify(mAppOpsManager).startOpNoThrow(AppOpsManager.OP_WAKE_LOCK, uid,
+ "my.package.name", false, null, null);
+
+ // Test with improveWakelockLatency flag false, hence the wakelock log will run on the same
+ // thread
+ clearInvocations(mWakeLockLog, mBatteryStats, mAppOpsManager);
+ when(mPowerManagerFlags.improveWakelockLatency()).thenReturn(false);
+
+ mNotifier.onWakeLockAcquired(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "wakelockTag",
+ "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
+ exceptingCallback);
+ verify(mWakeLockLog).onWakeLockAcquired("wakelockTag", uid,
+ PowerManager.SCREEN_BRIGHT_WAKE_LOCK, -1);
+
+ mNotifier.onWakeLockReleased(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "wakelockTag",
+ "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
+ exceptingCallback);
+ verify(mWakeLockLog).onWakeLockReleased("wakelockTag", uid, -1);
+ }
+
+ @Test
+ public void testOnWakeLockListener_FullWakeLock_ProcessesInSync() throws RemoteException {
+ createNotifier();
+
+ IWakeLockCallback exceptingCallback = new IWakeLockCallback.Stub() {
+ @Override public void onStateChanged(boolean enabled) throws RemoteException {
+ throw new RemoteException("Just testing");
+ }
+ };
+ clearInvocations(mWakeLockLog, mBatteryStats, mAppOpsManager);
+
+ final int uid = 1234;
+ final int pid = 5678;
+
+ // Release the wakelock
+ mNotifier.onWakeLockReleased(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "wakelockTag",
+ "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
+ exceptingCallback);
+
+ verify(mWakeLockLog).onWakeLockReleased("wakelockTag", uid, -1);
+ verify(mBatteryStats).noteStopWakelock(uid, pid, "wakelockTag", /* historyTag= */ null,
+ BatteryStats.WAKE_TYPE_FULL);
+ verify(mAppOpsManager).finishOp(AppOpsManager.OP_WAKE_LOCK, uid,
+ "my.package.name", null);
+
+ clearInvocations(mWakeLockLog, mBatteryStats, mAppOpsManager);
+
+ // Acquire the wakelock
+ mNotifier.onWakeLockAcquired(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "wakelockTag",
+ "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
+ exceptingCallback);
+
+ mTestLooper.dispatchAll();
+ verify(mWakeLockLog).onWakeLockAcquired("wakelockTag", uid,
+ PowerManager.SCREEN_BRIGHT_WAKE_LOCK, -1);
+ verify(mBatteryStats).noteStartWakelock(uid, pid, "wakelockTag", /* historyTag= */ null,
+ BatteryStats.WAKE_TYPE_FULL, false);
+ verify(mAppOpsManager).startOpNoThrow(AppOpsManager.OP_WAKE_LOCK, uid,
+ "my.package.name", false, null, null);
+ }
+
private final PowerManagerService.Injector mInjector = new PowerManagerService.Injector() {
@Override
Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
@@ -365,13 +479,17 @@
public WakeLockLog getWakeLockLog(Context context) {
return mWakeLockLog;
}
+
+ @Override
+ public AppOpsManager getAppOpsManager(Context context) {
+ return mAppOpsManager;
+ }
};
mNotifier = new Notifier(
mTestLooper.getLooper(),
mContextSpy,
- IBatteryStats.Stub.asInterface(ServiceManager.getService(
- BatteryStats.SERVICE_NAME)),
+ mBatteryStats,
mInjector.createSuspendBlocker(mService, "testBlocker"),
null,
null,