Merge "ColorFade draw should not be called after PowerState stopped" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index c477a75..9e30843 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -180,6 +180,12 @@
],
}
+cc_aconfig_library {
+ name: "android_location_flags_c_lib",
+ vendor_available: true,
+ aconfig_declarations: "android.location.flags-aconfig",
+}
+
java_aconfig_library {
name: "android.location.flags-aconfig-java",
aconfig_declarations: "android.location.flags-aconfig",
@@ -256,7 +262,6 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
-
// OS
aconfig_declarations {
name: "android.os.flags-aconfig",
diff --git a/Android.bp b/Android.bp
index f6a9328..82b844b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -132,6 +132,7 @@
":libcamera_client_aidl",
":libcamera_client_framework_aidl",
":libupdate_engine_aidl",
+ ":libupdate_engine_stable-V2-java-source",
":logd_aidl",
":resourcemanager_aidl",
":storaged_aidl",
@@ -217,6 +218,7 @@
"apex_aidl_interface-java",
"packagemanager_aidl-java",
"framework-protos",
+ "libtombstone_proto_java",
"updatable-driver-protos",
"ota_metadata_proto_java",
"android.hidl.base-V1.0-java",
diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp
index e7adf20..d03bbd2 100644
--- a/ProtoLibraries.bp
+++ b/ProtoLibraries.bp
@@ -34,7 +34,6 @@
":ipconnectivity-proto-src",
":libstats_atom_enum_protos",
":libstats_atom_message_protos",
- ":libtombstone_proto-src",
"core/proto/**/*.proto",
"libs/incident/**/*.proto",
],
diff --git a/STABILITY_OWNERS b/STABILITY_OWNERS
new file mode 100644
index 0000000..a7ecb4d
--- /dev/null
+++ b/STABILITY_OWNERS
@@ -0,0 +1,2 @@
+gaillard@google.com
+
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 117faa2..d59775f 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -132,6 +132,9 @@
},
{
"name": "vts_treble_vintf_vendor_test"
+ },
+ {
+ "name": "CtsStrictJavaPackagesTestCases"
}
],
"postsubmit-ravenwood": [
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 5a32a02..abf8008 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -115,6 +115,7 @@
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.ThreadLocalWorkSource;
import android.os.Trace;
import android.os.UserHandle;
@@ -229,6 +230,9 @@
private static final long TEMPORARY_QUOTA_DURATION = INTERVAL_DAY;
+ // System property read on some device configurations to initialize time properly.
+ private static final String TIMEOFFSET_PROPERTY = "persist.sys.time.offset";
+
private final Intent mBackgroundIntent
= new Intent().addFlags(Intent.FLAG_FROM_BACKGROUND);
@@ -2142,6 +2146,9 @@
// "GMT" if the ID is unrecognized). The parameter ID is used here rather than
// newZone.getId(). It will be rejected if it is invalid.
timeZoneWasChanged = SystemTimeZone.setTimeZoneId(tzId, confidence, logInfo);
+
+ final int gmtOffset = newZone.getOffset(mInjector.getCurrentTimeMillis());
+ SystemProperties.set(TIMEOFFSET_PROPERTY, String.valueOf(gmtOffset));
}
// Clear the default time zone in the system server process. This forces the next call
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index 28b2d4b..ef1fa60 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -900,10 +900,19 @@
],
api_levels_sdk_type: "system",
extensions_info_file: ":sdk-extensions-info",
+ dists: [
+ // Make the api-versions.xml file for the system API available in the
+ // sdk build target.
+ {
+ targets: ["sdk"],
+ dest: "api-versions_system.xml",
+ tag: ".api_versions.xml",
+ },
+ ],
}
// This module can be built with:
-// m out/soong/.intermediates/frameworks/base/api_versions_module_lib/android_common/metalava/api-versions.xml
+// m out/soong/.intermediates/frameworks/base/api/api_versions_module_lib/android_common/metalava/api-versions.xml
droidstubs {
name: "api_versions_module_lib",
srcs: [":android_module_stubs_current_with_test_libs{.jar}"],
diff --git a/core/api/current.txt b/core/api/current.txt
index 8109e04..8d8c6ee 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -102,6 +102,7 @@
field public static final String FOREGROUND_SERVICE_HEALTH = "android.permission.FOREGROUND_SERVICE_HEALTH";
field public static final String FOREGROUND_SERVICE_LOCATION = "android.permission.FOREGROUND_SERVICE_LOCATION";
field public static final String FOREGROUND_SERVICE_MEDIA_PLAYBACK = "android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK";
+ field @FlaggedApi("android.content.pm.introduce_media_processing_type") public static final String FOREGROUND_SERVICE_MEDIA_PROCESSING = "android.permission.FOREGROUND_SERVICE_MEDIA_PROCESSING";
field public static final String FOREGROUND_SERVICE_MEDIA_PROJECTION = "android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION";
field public static final String FOREGROUND_SERVICE_MICROPHONE = "android.permission.FOREGROUND_SERVICE_MICROPHONE";
field public static final String FOREGROUND_SERVICE_PHONE_CALL = "android.permission.FOREGROUND_SERVICE_PHONE_CALL";
@@ -12368,7 +12369,6 @@
public final class ModuleInfo implements android.os.Parcelable {
method public int describeContents();
- method @FlaggedApi("android.content.pm.provide_info_of_apk_in_apex") @NonNull public java.util.Collection<java.lang.String> getApkInApexPackageNames();
method @Nullable public CharSequence getName();
method @Nullable public String getPackageName();
method public boolean isHidden();
@@ -12817,7 +12817,7 @@
method public boolean isPackageSuspended();
method @CheckResult public abstract boolean isPermissionRevokedByPolicy(@NonNull String, @NonNull String);
method public abstract boolean isSafeMode();
- method @FlaggedApi("android.content.pm.get_package_info") @WorkerThread public <T> T parseAndroidManifest(@NonNull String, @NonNull java.util.function.Function<android.content.res.XmlResourceParser,T>) throws java.io.IOException;
+ method @FlaggedApi("android.content.pm.get_package_info") @WorkerThread public <T> T parseAndroidManifest(@NonNull java.io.File, @NonNull java.util.function.Function<android.content.res.XmlResourceParser,T>) throws java.io.IOException;
method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryActivityProperty(@NonNull String);
method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryApplicationProperty(@NonNull String);
method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(@NonNull android.content.Intent, int);
@@ -13287,6 +13287,7 @@
field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_LOCATION}, anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_LOCATION = 8; // 0x8
field public static final int FOREGROUND_SERVICE_TYPE_MANIFEST = -1; // 0xffffffff
field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK = 2; // 0x2
+ field @FlaggedApi("android.content.pm.introduce_media_processing_type") @RequiresPermission(android.Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROCESSING) public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING = 8192; // 0x2000
field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION = 32; // 0x20
field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_MICROPHONE}, anyOf={android.Manifest.permission.CAPTURE_AUDIO_OUTPUT, android.Manifest.permission.RECORD_AUDIO}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_MICROPHONE = 128; // 0x80
field @Deprecated public static final int FOREGROUND_SERVICE_TYPE_NONE = 0; // 0x0
@@ -22102,7 +22103,7 @@
}
@FlaggedApi("android.media.audio.loudness_configurator_api") public class LoudnessCodecConfigurator {
- method @FlaggedApi("android.media.audio.loudness_configurator_api") public void addMediaCodec(@NonNull android.media.MediaCodec);
+ method @FlaggedApi("android.media.audio.loudness_configurator_api") public boolean addMediaCodec(@NonNull android.media.MediaCodec);
method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public static android.media.LoudnessCodecConfigurator create();
method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public static android.media.LoudnessCodecConfigurator create(@NonNull java.util.concurrent.Executor, @NonNull android.media.LoudnessCodecConfigurator.OnLoudnessCodecUpdateListener);
method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public android.os.Bundle getLoudnessCodecParams(@NonNull android.media.AudioTrack, @NonNull android.media.MediaCodec);
@@ -26291,6 +26292,7 @@
field public static final int STATE_FAST_FORWARDING = 4; // 0x4
field public static final int STATE_NONE = 0; // 0x0
field public static final int STATE_PAUSED = 2; // 0x2
+ field @FlaggedApi("com.android.media.flags.enable_notifying_activity_manager_with_media_session_status_change") public static final int STATE_PLAYBACK_SUPPRESSED = 12; // 0xc
field public static final int STATE_PLAYING = 3; // 0x3
field public static final int STATE_REWINDING = 5; // 0x5
field public static final int STATE_SKIPPING_TO_NEXT = 10; // 0xa
@@ -28825,6 +28827,8 @@
method public boolean isSecureNfcEnabled();
method public boolean isSecureNfcSupported();
method @FlaggedApi("android.nfc.enable_nfc_charging") public boolean isWlcEnabled();
+ method @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public void resetDiscoveryTechnology(@NonNull android.app.Activity);
+ method @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public void setDiscoveryTechnology(@NonNull android.app.Activity, int, int);
field public static final String ACTION_ADAPTER_STATE_CHANGED = "android.nfc.action.ADAPTER_STATE_CHANGED";
field public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
field @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static final String ACTION_PREFERRED_PAYMENT_CHANGED = "android.nfc.action.PREFERRED_PAYMENT_CHANGED";
@@ -28840,6 +28844,13 @@
field public static final String EXTRA_READER_PRESENCE_CHECK_DELAY = "presence";
field public static final String EXTRA_SECURE_ELEMENT_NAME = "android.nfc.extra.SECURE_ELEMENT_NAME";
field public static final String EXTRA_TAG = "android.nfc.extra.TAG";
+ field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_DISABLE = 0; // 0x0
+ field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_KEEP = -1; // 0xffffffff
+ field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_NFC_PASSIVE_A = 1; // 0x1
+ field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_NFC_PASSIVE_B = 2; // 0x2
+ field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_NFC_PASSIVE_F = 4; // 0x4
+ field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_READER_DISABLE = 0; // 0x0
+ field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_READER_KEEP = -1; // 0xffffffff
field public static final int FLAG_READER_NFC_A = 1; // 0x1
field public static final int FLAG_READER_NFC_B = 2; // 0x2
field public static final int FLAG_READER_NFC_BARCODE = 16; // 0x10
@@ -53958,11 +53969,14 @@
field public static final String PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH";
field public static final String PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE = "android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";
field public static final String PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE = "android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE";
+ field @FlaggedApi("com.android.window.flags.app_compat_properties_api") public static final String PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED = "android.window.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED";
field @FlaggedApi("com.android.window.flags.app_compat_properties_api") public static final String PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE = "android.window.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE";
field public static final String PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE = "android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE";
+ field @FlaggedApi("com.android.window.flags.app_compat_properties_api") public static final String PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES = "android.window.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES";
field public static final String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS = "android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS";
field public static final String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
field public static final String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION = "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
+ field @FlaggedApi("com.android.window.flags.supports_multi_instance_system_ui") public static final String PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI = "android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI";
}
public static class WindowManager.BadTokenException extends java.lang.RuntimeException {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 0d4169f..9077d02 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -95,7 +95,7 @@
field public static final String BYPASS_ROLE_QUALIFICATION = "android.permission.BYPASS_ROLE_QUALIFICATION";
field public static final String CALL_AUDIO_INTERCEPTION = "android.permission.CALL_AUDIO_INTERCEPTION";
field public static final String CAMERA_DISABLE_TRANSMIT_LED = "android.permission.CAMERA_DISABLE_TRANSMIT_LED";
- field public static final String CAMERA_HEADLESS_SYSTEM_USER = "android.permission.CAMERA_HEADLESS_SYSTEM_USER";
+ field @FlaggedApi("com.android.internal.camera.flags.camera_hsum_permission") public static final String CAMERA_HEADLESS_SYSTEM_USER = "android.permission.CAMERA_HEADLESS_SYSTEM_USER";
field public static final String CAMERA_OPEN_CLOSE_LISTENER = "android.permission.CAMERA_OPEN_CLOSE_LISTENER";
field public static final String CAPTURE_AUDIO_HOTWORD = "android.permission.CAPTURE_AUDIO_HOTWORD";
field public static final String CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD = "android.permission.CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD";
@@ -473,7 +473,7 @@
field public static final int config_defaultCallScreening = 17039398; // 0x1040026
field public static final int config_defaultDialer = 17039395; // 0x1040023
field public static final int config_defaultNotes = 17039429; // 0x1040045
- field public static final int config_defaultRetailDemo;
+ field @FlaggedApi("android.permission.flags.retail_demo_role_enabled") public static final int config_defaultRetailDemo;
field public static final int config_defaultSms = 17039396; // 0x1040024
field public static final int config_devicePolicyManagement = 17039421; // 0x104003d
field public static final int config_feedbackIntentExtraKey = 17039391; // 0x104001f
@@ -4141,7 +4141,7 @@
field public static final int PROTECTION_FLAG_MODULE = 4194304; // 0x400000
field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000
field public static final int PROTECTION_FLAG_RECENTS = 33554432; // 0x2000000
- field @Deprecated public static final int PROTECTION_FLAG_RETAIL_DEMO = 16777216; // 0x1000000
+ field public static final int PROTECTION_FLAG_RETAIL_DEMO = 16777216; // 0x1000000
field public static final int PROTECTION_FLAG_ROLE = 67108864; // 0x4000000
field public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 65536; // 0x10000
field public static final int PROTECTION_FLAG_VENDOR_PRIVILEGED = 32768; // 0x8000
@@ -10571,7 +10571,7 @@
method @RequiresPermission(anyOf={android.Manifest.permission.RECOVERY, android.Manifest.permission.REBOOT}) public static int rebootAndApply(@NonNull android.content.Context, @NonNull String, boolean) throws java.io.IOException;
method @RequiresPermission(allOf={android.Manifest.permission.RECOVERY, android.Manifest.permission.REBOOT}) public static void rebootWipeAb(android.content.Context, java.io.File, String) throws java.io.IOException;
method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void scheduleUpdateOnBoot(android.content.Context, java.io.File) throws java.io.IOException;
- method public static boolean verifyPackageCompatibility(java.io.File) throws java.io.IOException;
+ method @Deprecated public static boolean verifyPackageCompatibility(java.io.File) throws java.io.IOException;
field public static final int RESUME_ON_REBOOT_REBOOT_ERROR_INVALID_PACKAGE_NAME = 2000; // 0x7d0
field public static final int RESUME_ON_REBOOT_REBOOT_ERROR_LSKF_NOT_CAPTURED = 3000; // 0xbb8
field public static final int RESUME_ON_REBOOT_REBOOT_ERROR_PROVIDER_PREPARATION_FAILURE = 5000; // 0x1388
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index ad8b685..572be19 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1010,6 +1010,7 @@
field public static final long OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH = 264304459L; // 0xfc0f74bL
field public static final long OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE = 264301586L; // 0xfc0ec12L
field public static final long OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS = 263259275L; // 0xfb1048bL
+ field @FlaggedApi("com.android.window.flags.app_compat_properties_api") public static final long OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED = 273509367L; // 0x104d6bf7L
field public static final long OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION = 254631730L; // 0xf2d5f32L
field public static final long OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE = 266124927L; // 0xfdcbe7fL
field public static final long OVERRIDE_MIN_ASPECT_RATIO = 174042980L; // 0xa5faf64L
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 5d4d5e2..f9583d2 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -24,7 +24,9 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.inMultiWindowMode;
import static android.os.Process.myUid;
+
import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext;
+
import static java.lang.Character.MIN_VALUE;
import android.annotation.AnimRes;
@@ -45,6 +47,7 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UiContext;
+import android.app.ActivityOptions.SceneTransitionInfo;
import android.app.VoiceInteractor.Request;
import android.app.admin.DevicePolicyManager;
import android.app.assist.AssistContent;
@@ -930,8 +933,8 @@
@UnsupportedAppUsage
final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
- /** The options for scene transition. */
- ActivityOptions mPendingOptions;
+ /** The scene transition info. */
+ SceneTransitionInfo mSceneTransitionInfo;
/** Whether this activity was launched from a bubble. **/
boolean mLaunchedFromBubble;
@@ -5807,10 +5810,9 @@
private Bundle transferSpringboardActivityOptions(@Nullable Bundle options) {
if (options == null && (mWindow != null && !mWindow.isActive())) {
- final ActivityOptions activityOptions = getActivityOptions();
- if (activityOptions != null &&
- activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
- return activityOptions.toBundle();
+ final SceneTransitionInfo info = getSceneTransitionInfo();
+ if (info != null) {
+ return ActivityOptions.makeBasic().setSceneTransitionInfo(info).toBundle();
}
}
return options;
@@ -8079,8 +8081,10 @@
*
* @param callback the method to call when all visible activities behind this one have been
* drawn and it is safe to make this activity translucent again.
- * @param options activity options delivered to the activity below this one. The options
- * are retrieved using {@link #getActivityOptions}.
+ * @param options activity options that created from
+ * {@link ActivityOptions#makeSceneTransitionAnimation} which will be converted to
+ * {@link SceneTransitionInfo} and delivered to the activity below this one. The
+ * options are retrieved using {@link #getSceneTransitionInfo}.
* @return <code>true</code> if Window was opaque and will become translucent or
* <code>false</code> if window was translucent and no change needed to be made.
*
@@ -8116,27 +8120,27 @@
}
/** @hide */
- public void onNewActivityOptions(ActivityOptions options) {
- mActivityTransitionState.setEnterActivityOptions(this, options);
+ public void onNewSceneTransitionInfo(ActivityOptions.SceneTransitionInfo info) {
+ mActivityTransitionState.setEnterSceneTransitionInfo(this, info);
if (!mStopped) {
mActivityTransitionState.enterReady(this);
}
}
/**
- * Takes the ActivityOptions passed in from the launching activity or passed back
+ * Takes the {@link SceneTransitionInfo} passed in from the launching activity or passed back
* from an activity launched by this activity in its call to {@link
* #convertToTranslucent(TranslucentConversionListener, ActivityOptions)}
*
- * @return The ActivityOptions passed to {@link #convertToTranslucent}.
+ * @return The {@link SceneTransitionInfo} which based on the ActivityOptions that originally
+ * passed to {@link #convertToTranslucent}.
* @hide
*/
- @UnsupportedAppUsage
- ActivityOptions getActivityOptions() {
- final ActivityOptions options = mPendingOptions;
- // The option only applies once.
- mPendingOptions = null;
- return options;
+ SceneTransitionInfo getSceneTransitionInfo() {
+ final SceneTransitionInfo sceneTransitionInfo = mSceneTransitionInfo;
+ // The info only applies once.
+ mSceneTransitionInfo = null;
+ return sceneTransitionInfo;
}
/**
@@ -8780,7 +8784,7 @@
mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(
com.android.internal.R.styleable.Window_windowNoDisplay, false);
mFragments.dispatchActivityCreated();
- mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
+ mActivityTransitionState.setEnterSceneTransitionInfo(this, getSceneTransitionInfo());
dispatchActivityPostCreated(icicle);
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
@@ -8798,7 +8802,7 @@
+ mComponent.getClassName());
}
dispatchActivityPreStarted();
- mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
+ mActivityTransitionState.setEnterSceneTransitionInfo(this, getSceneTransitionInfo());
mFragments.noteStateNotSaved();
mCalled = false;
mFragments.execPendingActions();
diff --git a/core/java/android/app/ActivityOptions.aidl b/core/java/android/app/ActivityOptions.aidl
new file mode 100644
index 0000000..bd5cd88
--- /dev/null
+++ b/core/java/android/app/ActivityOptions.aidl
@@ -0,0 +1,20 @@
+/**
+ * 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 android.app;
+
+/** @hide */
+parcelable ActivityOptions.SceneTransitionInfo;
\ No newline at end of file
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 9c279c3..8af7ed1 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -357,22 +357,7 @@
private static final String KEY_APPLY_NO_USER_ACTION_FLAG_FOR_SHORTCUT =
"android:activity.applyNoUserActionFlagForShortcut";
- /**
- * For Activity transitions, the calling Activity's TransitionListener used to
- * notify the called Activity when the shared element and the exit transitions
- * complete.
- */
- private static final String KEY_TRANSITION_COMPLETE_LISTENER
- = "android:activity.transitionCompleteListener";
-
- private static final String KEY_TRANSITION_IS_RETURNING
- = "android:activity.transitionIsReturning";
- private static final String KEY_TRANSITION_SHARED_ELEMENTS
- = "android:activity.sharedElementNames";
- private static final String KEY_RESULT_DATA = "android:activity.resultData";
- private static final String KEY_RESULT_CODE = "android:activity.resultCode";
- private static final String KEY_EXIT_COORDINATOR_INDEX
- = "android:activity.exitCoordinatorIndex";
+ private static final String KEY_SCENE_TRANSITION_INFO = "android:activity.sceneTransitionInfo";
/** See {@link SourceInfo}. */
private static final String KEY_SOURCE_INFO = "android.activity.sourceInfo";
@@ -472,12 +457,7 @@
private int mHeight;
private IRemoteCallback mAnimationStartedListener;
private IRemoteCallback mAnimationFinishedListener;
- private ResultReceiver mTransitionReceiver;
- private boolean mIsReturning;
- private ArrayList<String> mSharedElementNames;
- private Intent mResultData;
- private int mResultCode;
- private int mExitCoordinatorIndex;
+ private SceneTransitionInfo mSceneTransitionInfo;
private PendingIntent mUsageTimeReport;
private int mLaunchDisplayId = INVALID_DISPLAY;
private int mCallerDisplayId = INVALID_DISPLAY;
@@ -1006,8 +986,11 @@
ExitTransitionCoordinator exit = makeSceneTransitionAnimation(
new ActivityExitTransitionCallbacks(activity), activity.mExitTransitionListener,
activity.getWindow(), opts, sharedElements);
- opts.mExitCoordinatorIndex =
- activity.mActivityTransitionState.addExitTransitionCoordinator(exit);
+ final SceneTransitionInfo info = opts.getSceneTransitionInfo();
+ if (info != null) {
+ info.setExitCoordinatorKey(
+ activity.mActivityTransitionState.addExitTransitionCoordinator(exit));
+ }
return opts;
}
@@ -1029,13 +1012,16 @@
ActivityOptions opts = new ActivityOptions();
ExitTransitionCoordinator exit = makeSceneTransitionAnimation(
exitCallbacks, callback, window, opts, sharedElements);
- opts.mExitCoordinatorIndex = -1;
+ final SceneTransitionInfo info = opts.getSceneTransitionInfo();
+ if (info != null) {
+ info.setExitCoordinatorKey(-1);
+ }
return Pair.create(opts, exit);
}
/**
- * This method should be called when the
- * {@link #startSharedElementAnimation(Window, ExitTransitionCallbacks, Pair[])}
+ * This method should be called when the {@link #startSharedElementAnimation(Window,
+ * ExitTransitionCallbacks, SharedElementCallback, Pair[])}
* animation must be stopped and the Views reset. This can happen if there was an error
* from startActivity or a springboard activity and the animation should stop and reset.
*
@@ -1088,9 +1074,11 @@
ExitTransitionCoordinator exit = new ExitTransitionCoordinator(exitCallbacks, window,
callback, names, names, views, false);
- opts.mTransitionReceiver = exit;
- opts.mSharedElementNames = names;
- opts.mIsReturning = false;
+ final SceneTransitionInfo info = new SceneTransitionInfo();
+ info.setResultReceiver(exit);
+ info.setSharedElementNames(names);
+ info.setReturning(false);
+ opts.setSceneTransitionInfo(info);
return exit;
}
@@ -1111,17 +1099,20 @@
int resultCode, Intent resultData) {
ActivityOptions opts = new ActivityOptions();
opts.mAnimationType = ANIM_SCENE_TRANSITION;
- opts.mSharedElementNames = sharedElementNames;
- opts.mTransitionReceiver = exitCoordinator;
- opts.mIsReturning = true;
- opts.mResultCode = resultCode;
- opts.mResultData = resultData;
+ final SceneTransitionInfo info = new SceneTransitionInfo();
+ info.setSharedElementNames(sharedElementNames);
+ info.setResultReceiver(exitCoordinator);
+ info.setReturning(true);
+ info.setResultCode(resultCode);
+ info.setResultData(resultData);
if (activity == null) {
- opts.mExitCoordinatorIndex = -1;
+ info.setExitCoordinatorKey(-1);
} else {
- opts.mExitCoordinatorIndex =
- activity.mActivityTransitionState.addExitTransitionCoordinator(exitCoordinator);
+ info.setExitCoordinatorKey(
+ activity.mActivityTransitionState.addExitTransitionCoordinator(
+ exitCoordinator));
}
+ opts.setSceneTransitionInfo(info);
return opts;
}
@@ -1269,12 +1260,8 @@
break;
case ANIM_SCENE_TRANSITION:
- mTransitionReceiver = opts.getParcelable(KEY_TRANSITION_COMPLETE_LISTENER, android.os.ResultReceiver.class);
- mIsReturning = opts.getBoolean(KEY_TRANSITION_IS_RETURNING, false);
- mSharedElementNames = opts.getStringArrayList(KEY_TRANSITION_SHARED_ELEMENTS);
- mResultData = opts.getParcelable(KEY_RESULT_DATA, android.content.Intent.class);
- mResultCode = opts.getInt(KEY_RESULT_CODE);
- mExitCoordinatorIndex = opts.getInt(KEY_EXIT_COORDINATOR_INDEX);
+ mSceneTransitionInfo = opts.getParcelable(KEY_SCENE_TRANSITION_INFO,
+ SceneTransitionInfo.class);
break;
}
mLockTaskMode = opts.getBoolean(KEY_LOCK_TASK_MODE, false);
@@ -1437,9 +1424,6 @@
}
/** @hide */
- public int getExitCoordinatorKey() { return mExitCoordinatorIndex; }
-
- /** @hide */
public void abort() {
if (mAnimationStartedListener != null) {
try {
@@ -1450,35 +1434,17 @@
}
/** @hide */
- public boolean isReturning() {
- return mIsReturning;
- }
-
- /**
- * Returns whether or not the ActivityOptions was created with
- * {@link #startSharedElementAnimation(Window, Pair[])}.
- *
- * @hide
- */
- boolean isCrossTask() {
- return mExitCoordinatorIndex < 0;
+ public ActivityOptions setSceneTransitionInfo(SceneTransitionInfo info) {
+ mSceneTransitionInfo = info;
+ return this;
}
/** @hide */
- public ArrayList<String> getSharedElementNames() {
- return mSharedElementNames;
+ public SceneTransitionInfo getSceneTransitionInfo() {
+ return mSceneTransitionInfo;
}
/** @hide */
- public ResultReceiver getResultReceiver() { return mTransitionReceiver; }
-
- /** @hide */
- public int getResultCode() { return mResultCode; }
-
- /** @hide */
- public Intent getResultData() { return mResultData; }
-
- /** @hide */
public PendingIntent getUsageTimeReport() {
return mUsageTimeReport;
}
@@ -2102,12 +2068,7 @@
mPackageName = otherOptions.mPackageName;
}
mUsageTimeReport = otherOptions.mUsageTimeReport;
- mTransitionReceiver = null;
- mSharedElementNames = null;
- mIsReturning = false;
- mResultData = null;
- mResultCode = 0;
- mExitCoordinatorIndex = 0;
+ mSceneTransitionInfo = null;
mAnimationType = otherOptions.mAnimationType;
switch (otherOptions.mAnimationType) {
case ANIM_CUSTOM:
@@ -2157,14 +2118,9 @@
mAnimationStartedListener = otherOptions.mAnimationStartedListener;
break;
case ANIM_SCENE_TRANSITION:
- mTransitionReceiver = otherOptions.mTransitionReceiver;
- mSharedElementNames = otherOptions.mSharedElementNames;
- mIsReturning = otherOptions.mIsReturning;
+ mSceneTransitionInfo = otherOptions.mSceneTransitionInfo;
mThumbnail = null;
mAnimationStartedListener = null;
- mResultData = otherOptions.mResultData;
- mResultCode = otherOptions.mResultCode;
- mExitCoordinatorIndex = otherOptions.mExitCoordinatorIndex;
break;
}
mLockTaskMode = otherOptions.mLockTaskMode;
@@ -2240,14 +2196,9 @@
!= null ? mAnimationStartedListener.asBinder() : null);
break;
case ANIM_SCENE_TRANSITION:
- if (mTransitionReceiver != null) {
- b.putParcelable(KEY_TRANSITION_COMPLETE_LISTENER, mTransitionReceiver);
+ if (mSceneTransitionInfo != null) {
+ b.putParcelable(KEY_SCENE_TRANSITION_INFO, mSceneTransitionInfo);
}
- b.putBoolean(KEY_TRANSITION_IS_RETURNING, mIsReturning);
- b.putStringArrayList(KEY_TRANSITION_SHARED_ELEMENTS, mSharedElementNames);
- b.putParcelable(KEY_RESULT_DATA, mResultData);
- b.putInt(KEY_RESULT_CODE, mResultCode);
- b.putInt(KEY_EXIT_COORDINATOR_INDEX, mExitCoordinatorIndex);
break;
}
if (mLockTaskMode) {
@@ -2607,4 +2558,124 @@
}
};
}
+
+ /**
+ * This class contains necessary information for Activity Scene Transition.
+ *
+ * @hide
+ */
+ public static class SceneTransitionInfo implements Parcelable {
+ private boolean mIsReturning;
+ private int mResultCode;
+ @Nullable
+ private Intent mResultData;
+ @Nullable
+ private ArrayList<String> mSharedElementNames;
+ @Nullable
+ private ResultReceiver mResultReceiver;
+ private int mExitCoordinatorIndex;
+
+ public SceneTransitionInfo() {
+ }
+
+ SceneTransitionInfo(Parcel in) {
+ mIsReturning = in.readBoolean();
+ mResultCode = in.readInt();
+ mResultData = in.readTypedObject(Intent.CREATOR);
+ mSharedElementNames = in.createStringArrayList();
+ mResultReceiver = in.readTypedObject(ResultReceiver.CREATOR);
+ mExitCoordinatorIndex = in.readInt();
+ }
+
+ public static final Creator<SceneTransitionInfo> CREATOR = new Creator<>() {
+ @Override
+ public SceneTransitionInfo createFromParcel(Parcel in) {
+ return new SceneTransitionInfo(in);
+ }
+
+ @Override
+ public SceneTransitionInfo[] newArray(int size) {
+ return new SceneTransitionInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mIsReturning);
+ dest.writeInt(mResultCode);
+ dest.writeTypedObject(mResultData, flags);
+ dest.writeStringList(mSharedElementNames);
+ dest.writeTypedObject(mResultReceiver, flags);
+ dest.writeInt(mExitCoordinatorIndex);
+ }
+
+ public void setReturning(boolean isReturning) {
+ mIsReturning = isReturning;
+ }
+
+ public boolean isReturning() {
+ return mIsReturning;
+ }
+
+ public void setResultCode(int resultCode) {
+ mResultCode = resultCode;
+ }
+
+ public int getResultCode() {
+ return mResultCode;
+ }
+
+ public void setResultData(Intent resultData) {
+ mResultData = resultData;
+ }
+
+ @Nullable
+ public Intent getResultData() {
+ return mResultData;
+ }
+
+ public void setSharedElementNames(ArrayList<String> sharedElementNames) {
+ mSharedElementNames = sharedElementNames;
+ }
+
+ @Nullable
+ public ArrayList<String> getSharedElementNames() {
+ return mSharedElementNames;
+ }
+
+ public void setResultReceiver(ResultReceiver resultReceiver) {
+ mResultReceiver = resultReceiver;
+ }
+
+ @Nullable
+ public ResultReceiver getResultReceiver() {
+ return mResultReceiver;
+ }
+
+ public void setExitCoordinatorKey(int exitCoordinatorKey) {
+ mExitCoordinatorIndex = exitCoordinatorKey;
+ }
+
+ public int getExitCoordinatorKey() {
+ return mExitCoordinatorIndex;
+ }
+
+ boolean isCrossTask() {
+ return mExitCoordinatorIndex < 0;
+ }
+
+ @Override
+ public String toString() {
+ return "SceneTransitionInfo, mIsReturning=" + mIsReturning
+ + ", mResultCode=" + mResultCode + ", mResultData=" + mResultData
+ + ", mSharedElementNames=" + mSharedElementNames
+ + ", mTransitionReceiver=" + mResultReceiver
+ + ", mExitCoordinatorIndex=" + mExitCoordinatorIndex;
+ }
+ }
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 7e5326e..949e2ba 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -43,6 +43,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityOptions.SceneTransitionInfo;
import android.app.RemoteServiceException.BadForegroundServiceNotificationException;
import android.app.RemoteServiceException.BadUserInitiatedJobNotificationException;
import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException;
@@ -632,8 +633,8 @@
@UnsupportedAppUsage
boolean mPreserveWindow;
- /** The options for scene transition. */
- ActivityOptions mActivityOptions;
+ /** The scene transition info. */
+ SceneTransitionInfo mSceneTransitionInfo;
/** Whether this activiy was launched from a bubble. */
boolean mLaunchedFromBubble;
@@ -660,7 +661,7 @@
ActivityInfo info, Configuration overrideConfig,
String referrer, IVoiceInteractor voiceInteractor, Bundle state,
PersistableBundle persistentState, List<ResultInfo> pendingResults,
- List<ReferrerIntent> pendingNewIntents, ActivityOptions activityOptions,
+ List<ReferrerIntent> pendingNewIntents, SceneTransitionInfo sceneTransitionInfo,
boolean isForward, ProfilerInfo profilerInfo, ClientTransactionHandler client,
IBinder assistToken, IBinder shareableActivityToken, boolean launchedFromBubble,
IBinder taskFragmentToken) {
@@ -680,7 +681,7 @@
this.profilerInfo = profilerInfo;
this.overrideConfig = overrideConfig;
this.packageInfo = client.getPackageInfoNoCheck(activityInfo.applicationInfo);
- mActivityOptions = activityOptions;
+ mSceneTransitionInfo = sceneTransitionInfo;
mLaunchedFromBubble = launchedFromBubble;
mTaskFragmentToken = taskFragmentToken;
init();
@@ -1960,9 +1961,9 @@
sendMessage(H.TRANSLUCENT_CONVERSION_COMPLETE, token, drawComplete ? 1 : 0);
}
- public void scheduleOnNewActivityOptions(IBinder token, Bundle options) {
- sendMessage(H.ON_NEW_ACTIVITY_OPTIONS,
- new Pair<IBinder, ActivityOptions>(token, ActivityOptions.fromBundle(options)));
+ public void scheduleOnNewSceneTransitionInfo(IBinder token, SceneTransitionInfo info) {
+ sendMessage(H.ON_NEW_SCENE_TRANSITION_INFO,
+ new Pair<IBinder, SceneTransitionInfo>(token, info));
}
public void setProcessState(int state) {
@@ -2258,7 +2259,7 @@
public static final int TRANSLUCENT_CONVERSION_COMPLETE = 144;
@UnsupportedAppUsage
public static final int INSTALL_PROVIDER = 145;
- public static final int ON_NEW_ACTIVITY_OPTIONS = 146;
+ public static final int ON_NEW_SCENE_TRANSITION_INFO = 146;
@UnsupportedAppUsage
public static final int ENTER_ANIMATION_COMPLETE = 149;
public static final int START_BINDER_TRACKING = 150;
@@ -2314,7 +2315,7 @@
case REQUEST_ASSIST_CONTEXT_EXTRAS: return "REQUEST_ASSIST_CONTEXT_EXTRAS";
case TRANSLUCENT_CONVERSION_COMPLETE: return "TRANSLUCENT_CONVERSION_COMPLETE";
case INSTALL_PROVIDER: return "INSTALL_PROVIDER";
- case ON_NEW_ACTIVITY_OPTIONS: return "ON_NEW_ACTIVITY_OPTIONS";
+ case ON_NEW_SCENE_TRANSITION_INFO: return "ON_NEW_SCENE_TRANSITION_INFO";
case ENTER_ANIMATION_COMPLETE: return "ENTER_ANIMATION_COMPLETE";
case LOCAL_VOICE_INTERACTION_STARTED: return "LOCAL_VOICE_INTERACTION_STARTED";
case ATTACH_AGENT: return "ATTACH_AGENT";
@@ -2520,9 +2521,10 @@
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
break;
- case ON_NEW_ACTIVITY_OPTIONS:
- Pair<IBinder, ActivityOptions> pair = (Pair<IBinder, ActivityOptions>) msg.obj;
- onNewActivityOptions(pair.first, pair.second);
+ case ON_NEW_SCENE_TRANSITION_INFO:
+ Pair<IBinder, SceneTransitionInfo> pair =
+ (Pair<IBinder, SceneTransitionInfo>) msg.obj;
+ onNewSceneTransitionInfo(pair.first, pair.second);
break;
case ENTER_ANIMATION_COMPLETE:
handleEnterAnimationComplete((IBinder) msg.obj);
@@ -3921,9 +3923,9 @@
activity.setTheme(theme);
}
- if (r.mActivityOptions != null) {
- activity.mPendingOptions = r.mActivityOptions;
- r.mActivityOptions = null;
+ if (r.mSceneTransitionInfo != null) {
+ activity.mSceneTransitionInfo = r.mSceneTransitionInfo;
+ r.mSceneTransitionInfo = null;
}
activity.mLaunchedFromBubble = r.mLaunchedFromBubble;
activity.mCalled = false;
@@ -3962,7 +3964,7 @@
@Override
public void handleStartActivity(ActivityClientRecord r,
- PendingTransactionActions pendingActions, ActivityOptions activityOptions) {
+ PendingTransactionActions pendingActions, SceneTransitionInfo sceneTransitionInfo) {
final Activity activity = r.activity;
if (!r.stopped) {
throw new IllegalStateException("Can't start activity that is not stopped.");
@@ -3973,8 +3975,8 @@
}
unscheduleGcIdler();
- if (activityOptions != null) {
- activity.mPendingOptions = activityOptions;
+ if (sceneTransitionInfo != null) {
+ activity.mSceneTransitionInfo = sceneTransitionInfo;
}
// Start
@@ -4349,10 +4351,10 @@
}
}
- public void onNewActivityOptions(IBinder token, ActivityOptions options) {
+ public void onNewSceneTransitionInfo(IBinder token, SceneTransitionInfo info) {
ActivityClientRecord r = mActivities.get(token);
if (r != null) {
- r.activity.onNewActivityOptions(options);
+ r.activity.onNewSceneTransitionInfo(info);
}
}
diff --git a/core/java/android/app/ActivityTransitionState.java b/core/java/android/app/ActivityTransitionState.java
index 6f4bb45..d947a9b 100644
--- a/core/java/android/app/ActivityTransitionState.java
+++ b/core/java/android/app/ActivityTransitionState.java
@@ -15,6 +15,7 @@
*/
package android.app;
+import android.app.ActivityOptions.SceneTransitionInfo;
import android.content.Intent;
import android.os.Bundle;
import android.os.ResultReceiver;
@@ -81,9 +82,9 @@
private EnterTransitionCoordinator mEnterTransitionCoordinator;
/**
- * ActivityOptions used on entering this Activity.
+ * {@link SceneTransitionInfo} used on entering this Activity.
*/
- private ActivityOptions mEnterActivityOptions;
+ private SceneTransitionInfo mEnterSceneTransitionInfo;
/**
* Has an exit transition been started? If so, we don't want to double-exit.
@@ -165,7 +166,7 @@
}
}
- public void setEnterActivityOptions(Activity activity, ActivityOptions options) {
+ public void setEnterSceneTransitionInfo(Activity activity, SceneTransitionInfo info) {
final Window window = activity.getWindow();
if (window == null) {
return;
@@ -173,16 +174,15 @@
// ensure Decor View has been created so that the window features are activated
window.getDecorView();
if (window.hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
- && options != null && mEnterActivityOptions == null
- && mEnterTransitionCoordinator == null
- && options.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
- mEnterActivityOptions = options;
+ && info != null && mEnterSceneTransitionInfo == null
+ && mEnterTransitionCoordinator == null) {
+ mEnterSceneTransitionInfo = info;
mIsEnterTriggered = false;
- if (mEnterActivityOptions.isReturning()) {
+ if (mEnterSceneTransitionInfo.isReturning()) {
restoreExitedViews();
- int result = mEnterActivityOptions.getResultCode();
+ int result = mEnterSceneTransitionInfo.getResultCode();
if (result != 0) {
- Intent intent = mEnterActivityOptions.getResultData();
+ Intent intent = mEnterSceneTransitionInfo.getResultData();
if (intent != null) {
intent.setExtrasClassLoader(activity.getClassLoader());
}
@@ -193,25 +193,26 @@
}
public void enterReady(Activity activity) {
- if (mEnterActivityOptions == null || mIsEnterTriggered) {
+ if (mEnterSceneTransitionInfo == null || mIsEnterTriggered) {
return;
}
mIsEnterTriggered = true;
mHasExited = false;
- ArrayList<String> sharedElementNames = mEnterActivityOptions.getSharedElementNames();
- ResultReceiver resultReceiver = mEnterActivityOptions.getResultReceiver();
- final boolean isReturning = mEnterActivityOptions.isReturning();
+ final ArrayList<String> sharedElementNames =
+ mEnterSceneTransitionInfo.getSharedElementNames();
+ ResultReceiver resultReceiver = mEnterSceneTransitionInfo.getResultReceiver();
+ final boolean isReturning = mEnterSceneTransitionInfo.isReturning();
if (isReturning) {
restoreExitedViews();
activity.getWindow().getDecorView().setVisibility(View.VISIBLE);
}
getPendingExitNames(); // Set mPendingExitNames before resetting mEnterTransitionCoordinator
mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity,
- resultReceiver, sharedElementNames, mEnterActivityOptions.isReturning(),
- mEnterActivityOptions.isCrossTask());
- if (mEnterActivityOptions.isCrossTask()) {
- mExitingFrom = new ArrayList<>(mEnterActivityOptions.getSharedElementNames());
- mExitingTo = new ArrayList<>(mEnterActivityOptions.getSharedElementNames());
+ resultReceiver, sharedElementNames, mEnterSceneTransitionInfo.isReturning(),
+ mEnterSceneTransitionInfo.isCrossTask());
+ if (mEnterSceneTransitionInfo.isCrossTask() && sharedElementNames != null) {
+ mExitingFrom = new ArrayList<>(sharedElementNames);
+ mExitingTo = new ArrayList<>(sharedElementNames);
}
if (!mIsEnterPostponed) {
@@ -248,7 +249,7 @@
mExitingFrom = null;
mExitingTo = null;
mExitingToView = null;
- mEnterActivityOptions = null;
+ mEnterSceneTransitionInfo = null;
}
public void onStop(Activity activity) {
@@ -296,7 +297,7 @@
mExitingToView = null;
mCalledExitCoordinator = null;
mEnterTransitionCoordinator = null;
- mEnterActivityOptions = null;
+ mEnterSceneTransitionInfo = null;
mExitTransitionCoordinators = null;
}
@@ -386,9 +387,10 @@
mExitTransitionCoordinators == null) {
return;
}
- ActivityOptions activityOptions = new ActivityOptions(options);
- if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
- int key = activityOptions.getExitCoordinatorKey();
+ final ActivityOptions activityOptions = new ActivityOptions(options);
+ final SceneTransitionInfo info = activityOptions.getSceneTransitionInfo();
+ if (info != null) {
+ int key = info.getExitCoordinatorKey();
int index = mExitTransitionCoordinators.indexOfKey(key);
if (index >= 0) {
mCalledExitCoordinator = mExitTransitionCoordinators.valueAt(index).get();
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 287d2bd..34c44f9 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -131,6 +131,7 @@
import libcore.util.EmptyArray;
+import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
@@ -4038,11 +4039,11 @@
}
@Override
- public <T> T parseAndroidManifest(@NonNull String apkFilePath,
+ public <T> T parseAndroidManifest(@NonNull File apkFile,
@NonNull Function<XmlResourceParser, T> parserFunction) throws IOException {
- Objects.requireNonNull(apkFilePath, "apkFilePath cannot be null");
+ Objects.requireNonNull(apkFile, "apkFile cannot be null");
Objects.requireNonNull(parserFunction, "parserFunction cannot be null");
- try (XmlResourceParser xmlResourceParser = getAndroidManifestParser(apkFilePath)) {
+ try (XmlResourceParser xmlResourceParser = getAndroidManifestParser(apkFile)) {
return parserFunction.apply(xmlResourceParser);
} catch (IOException e) {
Log.w(TAG, "Failed to get the android manifest parser", e);
@@ -4050,11 +4051,11 @@
}
}
- private static XmlResourceParser getAndroidManifestParser(@NonNull String apkFilePath)
+ private static XmlResourceParser getAndroidManifestParser(@NonNull File apkFile)
throws IOException {
ApkAssets apkAssets = null;
try {
- apkAssets = ApkAssets.loadFromPath(apkFilePath);
+ apkAssets = ApkAssets.loadFromPath(apkFile.getAbsolutePath());
return apkAssets.openXml(ApkLiteParseUtils.ANDROID_MANIFEST_FILENAME);
} finally {
if (apkAssets != null) {
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index 25075e9..b300674 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -17,6 +17,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityOptions.SceneTransitionInfo;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.DestroyActivityItem;
@@ -207,7 +208,7 @@
/** Perform activity start. */
public abstract void handleStartActivity(@NonNull ActivityClientRecord r,
- PendingTransactionActions pendingActions, ActivityOptions activityOptions);
+ PendingTransactionActions pendingActions, SceneTransitionInfo sceneTransitionInfo);
/** Get package info. */
public abstract LoadedApk getPackageInfoNoCheck(ApplicationInfo ai);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 014ddd41..edeec77 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -3481,24 +3481,13 @@
}
mResources = r;
- // only do this if the user already has more than one preferred locale
- if (android.content.res.Flags.defaultLocale()
- && r.getConfiguration().getLocales().size() > 1) {
- LocaleConfig lc;
- if (getUserId() < 0) {
- lc = LocaleConfig.fromContextIgnoringOverride(this);
- } else {
- // This is needed because the app might have locale config overrides that need to
- // be read from disk in order for resources to correctly choose which values to
- // load.
- StrictMode.ThreadPolicy policy = StrictMode.allowThreadDiskReads();
- try {
- lc = new LocaleConfig(this);
- } finally {
- StrictMode.setThreadPolicy(policy);
- }
+ if (r != null) {
+ // only do this if the user already has more than one preferred locale
+ if (android.content.res.Flags.defaultLocale()
+ && r.getConfiguration().getLocales().size() > 1) {
+ LocaleConfig lc = LocaleConfig.fromContextIgnoringOverride(this);
+ mResourcesManager.setLocaleConfig(lc);
}
- mResourcesManager.setLocaleConfig(lc);
}
}
diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
index ac9c497..d1e517b 100644
--- a/core/java/android/app/ForegroundServiceTypePolicy.java
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -30,6 +30,7 @@
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE;
@@ -577,6 +578,26 @@
);
/**
+ * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING}.
+ *
+ * @hide
+ */
+ public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_MEDIA_PROCESSING =
+ new ForegroundServiceTypePolicyInfo(
+ FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(
+ Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROCESSING)
+ }, true),
+ null /* anyOfPermissions */,
+ null /* permissionEnforcementFlag */,
+ true /* permissionEnforcementFlagDefaultValue */,
+ false /* foregroundOnlyPermission */
+ );
+
+ /**
* The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE}.
*
* @hide
@@ -1331,6 +1352,8 @@
FGS_TYPE_POLICY_SYSTEM_EXEMPTED);
mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_SHORT_SERVICE,
FGS_TYPE_POLICY_SHORT_SERVICE);
+ mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING,
+ FGS_TYPE_POLICY_MEDIA_PROCESSING);
// TODO (b/271950506): revisit it in the next release.
// Hide the file management type for now. If anyone uses it, will default to "none".
mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_SPECIAL_USE,
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index a301c18..59e0e99 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -16,6 +16,7 @@
package android.app;
+import android.app.ActivityOptions.SceneTransitionInfo;
import android.app.ContentProviderHolder;
import android.app.IInstrumentationWatcher;
import android.app.IUiAutomationConnection;
@@ -114,7 +115,7 @@
void scheduleCreateBackupAgent(in ApplicationInfo app,
int backupMode, int userId, int operationType);
void scheduleDestroyBackupAgent(in ApplicationInfo app, int userId);
- void scheduleOnNewActivityOptions(IBinder token, in Bundle options);
+ void scheduleOnNewSceneTransitionInfo(IBinder token, in SceneTransitionInfo info);
void scheduleSuicide();
void dispatchPackageBroadcast(int cmd, in String[] packages);
void scheduleCrash(in String msg, int typeId, in Bundle extras);
diff --git a/core/java/android/app/LocalActivityManager.java b/core/java/android/app/LocalActivityManager.java
index 6b5f19a..1b19ecd 100644
--- a/core/java/android/app/LocalActivityManager.java
+++ b/core/java/android/app/LocalActivityManager.java
@@ -179,7 +179,7 @@
}
mActivityThread.handleStartActivity(clientRecord, pendingActions,
- null /* activityOptions */);
+ null /* sceneTransitionInfo */);
r.curState = STARTED;
if (desiredState == RESUMED) {
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index be420de..62db90f 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -744,20 +744,21 @@
params, userId, /* getCropped = */ true);
Trace.endSection();
- if (pfd != null) {
- try (InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
- ImageDecoder.Source src = ImageDecoder.createSource(is.readAllBytes());
- return ImageDecoder.decodeBitmap(src, ((decoder, info, source) -> {
- // Mutable and hardware config can't be set at the same time.
- decoder.setMutableRequired(!hardware);
- // Let's do color management
- if (cmProxy != null) {
- cmProxy.doColorManagement(decoder, info);
- }
- }));
- } catch (OutOfMemoryError | IOException e) {
- Log.w(TAG, "Can't decode file", e);
- }
+ if (pfd == null) {
+ return null;
+ }
+ try (InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
+ ImageDecoder.Source src = ImageDecoder.createSource(context.getResources(), is);
+ return ImageDecoder.decodeBitmap(src, ((decoder, info, source) -> {
+ // Mutable and hardware config can't be set at the same time.
+ decoder.setMutableRequired(!hardware);
+ // Let's do color management
+ if (cmProxy != null) {
+ cmProxy.doColorManagement(decoder, info);
+ }
+ }));
+ } catch (OutOfMemoryError | IOException e) {
+ Log.w(TAG, "Can't decode file", e);
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index 1190bf6..4d53701 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -23,7 +23,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityClient;
-import android.app.ActivityOptions;
+import android.app.ActivityOptions.SceneTransitionInfo;
import android.app.ActivityThread;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
@@ -73,7 +73,7 @@
private PersistableBundle mPersistentState;
private List<ResultInfo> mPendingResults;
private List<ReferrerIntent> mPendingNewIntents;
- private ActivityOptions mActivityOptions;
+ private SceneTransitionInfo mSceneTransitionInfo;
private boolean mIsForward;
private ProfilerInfo mProfilerInfo;
private IBinder mAssistToken;
@@ -104,8 +104,8 @@
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
ActivityClientRecord r = new ActivityClientRecord(mActivityToken, mIntent, mIdent, mInfo,
mOverrideConfig, mReferrer, mVoiceInteractor, mState, mPersistentState,
- mPendingResults, mPendingNewIntents, mActivityOptions, mIsForward, mProfilerInfo,
- client, mAssistToken, mShareableActivityToken, mLaunchedFromBubble,
+ mPendingResults, mPendingNewIntents, mSceneTransitionInfo, mIsForward,
+ mProfilerInfo, client, mAssistToken, mShareableActivityToken, mLaunchedFromBubble,
mTaskFragmentToken);
client.handleLaunchActivity(r, pendingActions, mDeviceId, null /* customIntent */);
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
@@ -136,7 +136,7 @@
@Nullable IVoiceInteractor voiceInteractor, int procState, @Nullable Bundle state,
@Nullable PersistableBundle persistentState, @Nullable List<ResultInfo> pendingResults,
@Nullable List<ReferrerIntent> pendingNewIntents,
- @Nullable ActivityOptions activityOptions,
+ @Nullable SceneTransitionInfo sceneTransitionInfo,
boolean isForward, @Nullable ProfilerInfo profilerInfo, @NonNull IBinder assistToken,
@Nullable IActivityClientController activityClientController,
@NonNull IBinder shareableActivityToken, boolean launchedFromBubble,
@@ -152,7 +152,7 @@
persistentState != null ? new PersistableBundle(persistentState) : null,
pendingResults != null ? new ArrayList<>(pendingResults) : null,
pendingNewIntents != null ? new ArrayList<>(pendingNewIntents) : null,
- activityOptions, isForward,
+ sceneTransitionInfo, isForward,
profilerInfo != null ? new ProfilerInfo(profilerInfo) : null,
assistToken, activityClientController, shareableActivityToken,
launchedFromBubble, taskFragmentToken);
@@ -193,7 +193,7 @@
dest.writePersistableBundle(mPersistentState);
dest.writeTypedList(mPendingResults, flags);
dest.writeTypedList(mPendingNewIntents, flags);
- dest.writeBundle(mActivityOptions != null ? mActivityOptions.toBundle() : null);
+ dest.writeTypedObject(mSceneTransitionInfo, flags);
dest.writeBoolean(mIsForward);
dest.writeTypedObject(mProfilerInfo, flags);
dest.writeStrongBinder(mAssistToken);
@@ -213,7 +213,8 @@
in.readPersistableBundle(getClass().getClassLoader()),
in.createTypedArrayList(ResultInfo.CREATOR),
in.createTypedArrayList(ReferrerIntent.CREATOR),
- ActivityOptions.fromBundle(in.readBundle()), in.readBoolean(),
+ in.readTypedObject(SceneTransitionInfo.CREATOR),
+ in.readBoolean(),
in.readTypedObject(ProfilerInfo.CREATOR),
in.readStrongBinder(),
IActivityClientController.Stub.asInterface(in.readStrongBinder()),
@@ -253,7 +254,7 @@
&& areBundlesEqualRoughly(mPersistentState, other.mPersistentState)
&& Objects.equals(mPendingResults, other.mPendingResults)
&& Objects.equals(mPendingNewIntents, other.mPendingNewIntents)
- && (mActivityOptions == null) == (other.mActivityOptions == null)
+ && (mSceneTransitionInfo == null) == (other.mSceneTransitionInfo == null)
&& mIsForward == other.mIsForward
&& Objects.equals(mProfilerInfo, other.mProfilerInfo)
&& Objects.equals(mAssistToken, other.mAssistToken)
@@ -276,7 +277,7 @@
result = 31 * result + getRoughBundleHashCode(mPersistentState);
result = 31 * result + Objects.hashCode(mPendingResults);
result = 31 * result + Objects.hashCode(mPendingNewIntents);
- result = 31 * result + (mActivityOptions != null ? 1 : 0);
+ result = 31 * result + (mSceneTransitionInfo != null ? 1 : 0);
result = 31 * result + (mIsForward ? 1 : 0);
result = 31 * result + Objects.hashCode(mProfilerInfo);
result = 31 * result + Objects.hashCode(mAssistToken);
@@ -325,7 +326,7 @@
+ ",persistentState=" + mPersistentState
+ ",pendingResults=" + mPendingResults
+ ",pendingNewIntents=" + mPendingNewIntents
- + ",options=" + mActivityOptions
+ + ",sceneTransitionInfo=" + mSceneTransitionInfo
+ ",profilerInfo=" + mProfilerInfo
+ ",assistToken=" + mAssistToken
+ ",shareableActivityToken=" + mShareableActivityToken + "}";
@@ -340,7 +341,7 @@
int procState, @Nullable Bundle state, @Nullable PersistableBundle persistentState,
@Nullable List<ResultInfo> pendingResults,
@Nullable List<ReferrerIntent> pendingNewIntents,
- @Nullable ActivityOptions activityOptions, boolean isForward,
+ @Nullable SceneTransitionInfo sceneTransitionInfo, boolean isForward,
@Nullable ProfilerInfo profilerInfo, @Nullable IBinder assistToken,
@Nullable IActivityClientController activityClientController,
@Nullable IBinder shareableActivityToken, boolean launchedFromBubble,
@@ -359,7 +360,7 @@
instance.mPersistentState = persistentState;
instance.mPendingResults = pendingResults;
instance.mPendingNewIntents = pendingNewIntents;
- instance.mActivityOptions = activityOptions;
+ instance.mSceneTransitionInfo = sceneTransitionInfo;
instance.mIsForward = isForward;
instance.mProfilerInfo = profilerInfo;
instance.mAssistToken = assistToken;
diff --git a/core/java/android/app/servertransaction/StartActivityItem.java b/core/java/android/app/servertransaction/StartActivityItem.java
index 8b98b21..a0f93ce 100644
--- a/core/java/android/app/servertransaction/StartActivityItem.java
+++ b/core/java/android/app/servertransaction/StartActivityItem.java
@@ -20,7 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityOptions;
+import android.app.ActivityOptions.SceneTransitionInfo;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
import android.os.IBinder;
@@ -35,13 +35,13 @@
private static final String TAG = "StartActivityItem";
- private ActivityOptions mActivityOptions;
+ private SceneTransitionInfo mSceneTransitionInfo;
@Override
public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
@NonNull PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "startActivityItem");
- client.handleStartActivity(r, pendingActions, mActivityOptions);
+ client.handleStartActivity(r, pendingActions, mSceneTransitionInfo);
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -57,13 +57,13 @@
/** Obtain an instance initialized with provided params. */
@NonNull
public static StartActivityItem obtain(@NonNull IBinder activityToken,
- @Nullable ActivityOptions activityOptions) {
+ @Nullable SceneTransitionInfo sceneTransitionInfo) {
StartActivityItem instance = ObjectPool.obtain(StartActivityItem.class);
if (instance == null) {
instance = new StartActivityItem();
}
instance.setActivityToken(activityToken);
- instance.mActivityOptions = activityOptions;
+ instance.mSceneTransitionInfo = sceneTransitionInfo;
return instance;
}
@@ -71,7 +71,7 @@
@Override
public void recycle() {
super.recycle();
- mActivityOptions = null;
+ mSceneTransitionInfo = null;
ObjectPool.recycle(this);
}
@@ -81,13 +81,13 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
- dest.writeBundle(mActivityOptions != null ? mActivityOptions.toBundle() : null);
+ dest.writeTypedObject(mSceneTransitionInfo, flags);
}
/** Read from Parcel. */
private StartActivityItem(@NonNull Parcel in) {
super(in);
- mActivityOptions = ActivityOptions.fromBundle(in.readBundle());
+ mSceneTransitionInfo = in.readTypedObject(SceneTransitionInfo.CREATOR);
}
public static final @NonNull Creator<StartActivityItem> CREATOR = new Creator<>() {
@@ -109,21 +109,21 @@
return false;
}
final StartActivityItem other = (StartActivityItem) o;
- return (mActivityOptions == null) == (other.mActivityOptions == null);
+ return (mSceneTransitionInfo == null) == (other.mSceneTransitionInfo == null);
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + super.hashCode();
- result = 31 * result + (mActivityOptions != null ? 1 : 0);
+ result = 31 * result + (mSceneTransitionInfo != null ? 1 : 0);
return result;
}
@Override
public String toString() {
return "StartActivityItem{" + super.toString()
- + ",options=" + mActivityOptions + "}";
+ + ",sceneTransitionInfo=" + mSceneTransitionInfo + "}";
}
}
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index 2e47fee..ba94077 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -324,7 +324,7 @@
break;
case ON_START:
mTransactionHandler.handleStartActivity(r, mPendingActions,
- null /* activityOptions */);
+ null /* sceneTransitionInfo */);
break;
case ON_RESUME:
mTransactionHandler.handleResumeActivity(r, false /* finalStateRequest */,
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index 161fa79..843158c 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -71,6 +71,12 @@
* @see CompanionDeviceManager#disassociate(int)
*/
private final boolean mRevoked;
+ /**
+ * Indicates that the association is waiting for its corresponding companion app to be installed
+ * before it can be added to CDM. This is likely because it was restored onto the device from a
+ * backup.
+ */
+ private final boolean mPending;
private final long mTimeApprovedMs;
/**
* A long value indicates the last time connected reported by selfManaged devices
@@ -88,7 +94,7 @@
@Nullable String tag, @Nullable MacAddress macAddress,
@Nullable CharSequence displayName, @Nullable String deviceProfile,
@Nullable AssociatedDevice associatedDevice, boolean selfManaged,
- boolean notifyOnDeviceNearby, boolean revoked, long timeApprovedMs,
+ boolean notifyOnDeviceNearby, boolean revoked, boolean pending, long timeApprovedMs,
long lastTimeConnectedMs, int systemDataSyncFlags) {
if (id <= 0) {
throw new IllegalArgumentException("Association ID should be greater than 0");
@@ -109,6 +115,7 @@
mSelfManaged = selfManaged;
mNotifyOnDeviceNearby = notifyOnDeviceNearby;
mRevoked = revoked;
+ mPending = pending;
mTimeApprovedMs = timeApprovedMs;
mLastTimeConnectedMs = lastTimeConnectedMs;
mSystemDataSyncFlags = systemDataSyncFlags;
@@ -236,6 +243,15 @@
}
/**
+ * @return true if the association is waiting for its corresponding app to be installed
+ * before it can be added to CDM.
+ * @hide
+ */
+ public boolean isPending() {
+ return mPending;
+ }
+
+ /**
* @return the last time self reported disconnected for selfManaged only.
* @hide
*/
@@ -318,6 +334,7 @@
+ ", mAssociatedDevice=" + mAssociatedDevice
+ ", mNotifyOnDeviceNearby=" + mNotifyOnDeviceNearby
+ ", mRevoked=" + mRevoked
+ + ", mPending=" + mPending
+ ", mTimeApprovedMs=" + new Date(mTimeApprovedMs)
+ ", mLastTimeConnectedMs=" + (
mLastTimeConnectedMs == Long.MAX_VALUE
@@ -336,6 +353,7 @@
&& mSelfManaged == that.mSelfManaged
&& mNotifyOnDeviceNearby == that.mNotifyOnDeviceNearby
&& mRevoked == that.mRevoked
+ && mPending == that.mPending
&& mTimeApprovedMs == that.mTimeApprovedMs
&& mLastTimeConnectedMs == that.mLastTimeConnectedMs
&& Objects.equals(mPackageName, that.mPackageName)
@@ -351,7 +369,7 @@
public int hashCode() {
return Objects.hash(mId, mUserId, mPackageName, mTag, mDeviceMacAddress, mDisplayName,
mDeviceProfile, mAssociatedDevice, mSelfManaged, mNotifyOnDeviceNearby, mRevoked,
- mTimeApprovedMs, mLastTimeConnectedMs, mSystemDataSyncFlags);
+ mPending, mTimeApprovedMs, mLastTimeConnectedMs, mSystemDataSyncFlags);
}
@Override
@@ -372,6 +390,7 @@
dest.writeBoolean(mSelfManaged);
dest.writeBoolean(mNotifyOnDeviceNearby);
dest.writeBoolean(mRevoked);
+ dest.writeBoolean(mPending);
dest.writeLong(mTimeApprovedMs);
dest.writeLong(mLastTimeConnectedMs);
dest.writeInt(mSystemDataSyncFlags);
@@ -389,6 +408,7 @@
mSelfManaged = in.readBoolean();
mNotifyOnDeviceNearby = in.readBoolean();
mRevoked = in.readBoolean();
+ mPending = in.readBoolean();
mTimeApprovedMs = in.readLong();
mLastTimeConnectedMs = in.readLong();
mSystemDataSyncFlags = in.readInt();
@@ -427,6 +447,7 @@
private boolean mSelfManaged;
private boolean mNotifyOnDeviceNearby;
private boolean mRevoked;
+ private boolean mPending;
private long mTimeApprovedMs;
private long mLastTimeConnectedMs;
private int mSystemDataSyncFlags;
@@ -453,6 +474,31 @@
mSelfManaged = info.mSelfManaged;
mNotifyOnDeviceNearby = info.mNotifyOnDeviceNearby;
mRevoked = info.mRevoked;
+ mPending = info.mPending;
+ mTimeApprovedMs = info.mTimeApprovedMs;
+ mLastTimeConnectedMs = info.mLastTimeConnectedMs;
+ mSystemDataSyncFlags = info.mSystemDataSyncFlags;
+ }
+
+ /**
+ * This builder is used specifically to create a new association to be restored to a device
+ * that is potentially using a different user ID from the backed-up device.
+ *
+ * @hide
+ */
+ public Builder(int id, int userId, @NonNull String packageName, AssociationInfo info) {
+ mId = id;
+ mUserId = userId;
+ mPackageName = packageName;
+ mTag = info.mTag;
+ mDeviceMacAddress = info.mDeviceMacAddress;
+ mDisplayName = info.mDisplayName;
+ mDeviceProfile = info.mDeviceProfile;
+ mAssociatedDevice = info.mAssociatedDevice;
+ mSelfManaged = info.mSelfManaged;
+ mNotifyOnDeviceNearby = info.mNotifyOnDeviceNearby;
+ mRevoked = info.mRevoked;
+ mPending = info.mPending;
mTimeApprovedMs = info.mTimeApprovedMs;
mLastTimeConnectedMs = info.mLastTimeConnectedMs;
mSystemDataSyncFlags = info.mSystemDataSyncFlags;
@@ -526,6 +572,14 @@
}
/** @hide */
+ @NonNull
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public Builder setPending(boolean pending) {
+ mPending = pending;
+ return this;
+ }
+
+ /** @hide */
@TestApi
@NonNull
@SuppressLint("MissingGetterMatchingBuilder")
@@ -583,6 +637,7 @@
mSelfManaged,
mNotifyOnDeviceNearby,
mRevoked,
+ mPending,
mTimeApprovedMs,
mLastTimeConnectedMs,
mSystemDataSyncFlags
diff --git a/core/java/android/companion/datatransfer/PermissionSyncRequest.java b/core/java/android/companion/datatransfer/PermissionSyncRequest.java
index 973fca30..34865f0 100644
--- a/core/java/android/companion/datatransfer/PermissionSyncRequest.java
+++ b/core/java/android/companion/datatransfer/PermissionSyncRequest.java
@@ -48,6 +48,15 @@
}
/** @hide */
+ @Override
+ public PermissionSyncRequest copyWithNewId(int associationId) {
+ PermissionSyncRequest newRequest = new PermissionSyncRequest(associationId);
+ newRequest.mUserId = this.mUserId;
+ newRequest.mUserConsented = this.mUserConsented;
+ return newRequest;
+ }
+
+ /** @hide */
@NonNull
public static final Creator<PermissionSyncRequest> CREATOR =
new Creator<PermissionSyncRequest>() {
diff --git a/core/java/android/companion/datatransfer/SystemDataTransferRequest.java b/core/java/android/companion/datatransfer/SystemDataTransferRequest.java
index 38a553d..c3a2aa4 100644
--- a/core/java/android/companion/datatransfer/SystemDataTransferRequest.java
+++ b/core/java/android/companion/datatransfer/SystemDataTransferRequest.java
@@ -103,4 +103,15 @@
public int describeContents() {
return 0;
}
+
+ /**
+ * Creates a copy of itself with new association ID.
+ *
+ * This method must be implemented to ensure that backup-and-restore can correctly re-map
+ * the restored requests to the restored associations that can potentially have different
+ * IDs than what was originally backed up.
+ *
+ * @hide
+ */
+ public abstract SystemDataTransferRequest copyWithNewId(int associationId);
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 183b9b0..ee1d117b 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -4207,7 +4207,9 @@
* new state of quiet mode. This is only sent to registered receivers, not manifest receivers.
*
* <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_AVAILABLE} but functions as a
- * generic broadcast for all profile users.
+ * generic broadcast for all users of type {@link android.os.UserManager#isProfile()}}. In
+ * case of a managed profile, both {@link #ACTION_MANAGED_PROFILE_AVAILABLE} and
+ * {@link #ACTION_PROFILE_AVAILABLE} broadcasts are sent.
*/
@FlaggedApi(FLAG_ALLOW_PRIVATE_PROFILE)
public static final String ACTION_PROFILE_AVAILABLE =
@@ -4221,7 +4223,9 @@
* new state of quiet mode. This is only sent to registered receivers, not manifest receivers.
*
* <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_UNAVAILABLE} but functions as
- * a generic broadcast for all profile users.
+ * a generic broadcast for all users of type {@link android.os.UserManager#isProfile()}}. In
+ * case of a managed profile, both {@link #ACTION_MANAGED_PROFILE_UNAVAILABLE} and
+ * {@link #ACTION_PROFILE_UNAVAILABLE} broadcasts are sent.
*/
@FlaggedApi(FLAG_ALLOW_PRIVATE_PROFILE)
public static final String ACTION_PROFILE_UNAVAILABLE =
@@ -6971,16 +6975,21 @@
public static final int FLAG_DEBUG_LOG_RESOLUTION = 0x00000008;
/**
* If set, this intent will not match any components in packages that
- * are currently stopped. If this is not set, then the default behavior
- * is to include such applications in the result.
+ * are currently
+ * {@linkplain android.content.pm.ApplicationInfo#FLAG_STOPPED stopped}.
+ * If this is not set, then the default behavior is to include such
+ * applications in the result.
*/
public static final int FLAG_EXCLUDE_STOPPED_PACKAGES = 0x00000010;
/**
* If set, this intent will always match any components in packages that
- * are currently stopped. This is the default behavior when
+ * are currently
+ * {@linkplain android.content.pm.ApplicationInfo#FLAG_STOPPED stopped}.
+ * This is the default behavior when
* {@link #FLAG_EXCLUDE_STOPPED_PACKAGES} is not set. If both of these
* flags are set, this one wins (it allows overriding of exclude for
- * places where the framework may automatically set the exclude flag).
+ * places where the framework may automatically set the exclude flag,
+ * such as broadcasts).
*/
public static final int FLAG_INCLUDE_STOPPED_PACKAGES = 0x00000020;
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 12da665..9fe8af5 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -16,13 +16,13 @@
package android.content.pm;
+import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.app.Activity;
-import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.Disabled;
import android.compat.annotation.EnabledSince;
@@ -36,12 +36,12 @@
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
-import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Printer;
import android.window.OnBackInvokedCallback;
import com.android.internal.util.Parcelling;
+import com.android.window.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -1099,6 +1099,8 @@
@ChangeId
@Overridable
@Disabled
+ @TestApi
+ @FlaggedApi(Flags.FLAG_APP_COMPAT_PROPERTIES_API)
public static final long OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED =
273509367L; // buganizer id
@@ -1786,8 +1788,7 @@
* @hide
*/
public boolean isChangeEnabled(long changeId) {
- return CompatChanges.isChangeEnabled(changeId, applicationInfo.packageName,
- UserHandle.getUserHandleForUid(applicationInfo.uid));
+ return applicationInfo.isChangeEnabled(changeId);
}
/** @hide */
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 3713380..f0a8996 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -26,6 +26,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.app.compat.CompatChanges;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -375,6 +376,19 @@
/**
* Value for {@link #flags}: true if this application's package is in
* the stopped state.
+ *
+ * <p>Stopped is the initial state after an app is installed, before it is launched
+ * or otherwise directly interacted with by the user. The system tries not to
+ * start it unless initiated by a user interaction (typically launching its icon
+ * from the launcher, could also include user actions like adding it as an app widget,
+ * selecting it as a live wallpaper, selecting it as a keyboard, etc). Stopped
+ * applications will not receive broadcasts unless the sender specifies
+ * {@link android.content.Intent#FLAG_INCLUDE_STOPPED_PACKAGES}.
+ *
+ * <p>Applications should avoid launching activies, binding to or starting services, or
+ * otherwise causing a stopped application to run unless initiated by the user.
+ *
+ * <p>An app can also return to the stopped state by a "force stop".
*/
public static final int FLAG_STOPPED = 1<<21;
@@ -2632,6 +2646,17 @@
}
/**
+ * Checks if a changeId is enabled for the current user
+ * @param changeId The changeId to verify
+ * @return True of the changeId is enabled
+ * @hide
+ */
+ public boolean isChangeEnabled(long changeId) {
+ return CompatChanges.isChangeEnabled(changeId, packageName,
+ UserHandle.getUserHandleForUid(uid));
+ }
+
+ /**
* @return whether the app has requested exemption from the foreground service restrictions.
* This does not take any affect for now.
* @hide
diff --git a/core/java/android/content/pm/ModuleInfo.java b/core/java/android/content/pm/ModuleInfo.java
index a1c8747..c6e93bb 100644
--- a/core/java/android/content/pm/ModuleInfo.java
+++ b/core/java/android/content/pm/ModuleInfo.java
@@ -16,7 +16,6 @@
package android.content.pm;
-import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
@@ -122,18 +121,15 @@
return mApexModuleName;
}
- /** @hide Sets the list of the package name of APK-in-APEX apps in this module. */
+ /** @hide Set the list of the package names of all APK-in-APEX apps in this module. */
public ModuleInfo setApkInApexPackageNames(@NonNull Collection<String> apkInApexPackageNames) {
Objects.requireNonNull(apkInApexPackageNames);
mApkInApexPackageNames = List.copyOf(apkInApexPackageNames);
return this;
}
- /**
- * Gets the list of the package name of all APK-in-APEX apps in the module.
- */
+ /** @hide Get the list of the package names of all APK-in-APEX apps in the module. */
@NonNull
- @FlaggedApi(android.content.pm.Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX)
public Collection<String> getApkInApexPackageNames() {
if (mApkInApexPackageNames == null) {
return Collections.emptyList();
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 8151a91..8e5e825 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -11024,7 +11024,7 @@
}
/**
- * Returns the property defined in the given package's <appliction> tag.
+ * Returns the property defined in the given package's <application> tag.
*
* @throws NameNotFoundException if either the given package is not installed or if the
* given property is not defined within the <application> tag.
@@ -11534,14 +11534,14 @@
}
/**
- * Retrieve AndroidManifest.xml information for the given application apk path.
+ * Retrieve AndroidManifest.xml information for the given application apk file.
*
* <p>Example:
*
* <pre><code>
* Bundle result;
* try {
- * result = getContext().getPackageManager().parseAndroidManifest(apkFilePath,
+ * result = getContext().getPackageManager().parseAndroidManifest(apkFile,
* xmlResourceParser -> {
* Bundle bundle = new Bundle();
* // Search the start tag
@@ -11570,9 +11570,10 @@
*
* Note: When the parserFunction is invoked, the client can read the AndroidManifest.xml
* information by the XmlResourceParser object. After leaving the parserFunction, the
- * XmlResourceParser object will be closed.
+ * XmlResourceParser object will be closed. The caller should also handle the exception for
+ * calling this method.
*
- * @param apkFilePath The path of an application apk file.
+ * @param apkFile The file of an application apk.
* @param parserFunction The parserFunction will be invoked with the XmlResourceParser object
* after getting the AndroidManifest.xml of an application package.
*
@@ -11583,7 +11584,7 @@
*/
@FlaggedApi(android.content.pm.Flags.FLAG_GET_PACKAGE_INFO)
@WorkerThread
- public <T> T parseAndroidManifest(@NonNull String apkFilePath,
+ public <T> T parseAndroidManifest(@NonNull File apkFile,
@NonNull Function<XmlResourceParser, T> parserFunction) throws IOException {
throw new UnsupportedOperationException(
"parseAndroidManifest not implemented in subclass");
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index 012b6c4..cdda12e 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -273,9 +273,6 @@
* to the <code>retailDemo</code> value of
* {@link android.R.attr#protectionLevel}.
*
- * @deprecated This flag has been replaced by the retail demo role and is a no-op since Android
- * V.
- *
* @hide
*/
@SystemApi
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 4d704c3..ae46c027 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -17,6 +17,7 @@
package android.content.pm;
import android.Manifest;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.RequiresPermission;
import android.os.Parcel;
@@ -471,6 +472,17 @@
public static final int FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT = 1 << 12;
/**
+ * Constant corresponding to {@code mediaProcessing} in
+ * the {@link android.R.attr#foregroundServiceType} attribute.
+ * Media processing use cases such as video or photo editing and processing.
+ */
+ @RequiresPermission(
+ value = Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROCESSING
+ )
+ @FlaggedApi(Flags.FLAG_INTRODUCE_MEDIA_PROCESSING_TYPE)
+ public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING = 1 << 13;
+
+ /**
* Constant corresponding to {@code specialUse} in
* the {@link android.R.attr#foregroundServiceType} attribute.
* Use cases that can't be categorized into any other foreground service types, but also
@@ -554,6 +566,7 @@
FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED,
FOREGROUND_SERVICE_TYPE_SHORT_SERVICE,
FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT,
+ FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING,
FOREGROUND_SERVICE_TYPE_SPECIAL_USE,
})
@Retention(RetentionPolicy.SOURCE)
@@ -640,6 +653,8 @@
return "shortService";
case FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT:
return "fileManagement";
+ case FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING:
+ return "mediaProcessing";
case FOREGROUND_SERVICE_TYPE_SPECIAL_USE:
return "specialUse";
default:
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 94bec35..0b60977 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -131,3 +131,10 @@
bug: "310801107"
is_fixed_read_only: true
}
+
+flag {
+ name: "introduce_media_processing_type"
+ namespace: "backstage_power"
+ description: "Add a new FGS type for media processing use cases."
+ bug: "317788011"
+}
diff --git a/core/java/android/content/res/Element.java b/core/java/android/content/res/Element.java
index e511469..89f4985 100644
--- a/core/java/android/content/res/Element.java
+++ b/core/java/android/content/res/Element.java
@@ -26,6 +26,8 @@
import com.android.internal.R;
+import java.util.Set;
+
/**
* Defines the string attribute length and child tag count restrictions for a xml element.
*
@@ -37,7 +39,11 @@
private static final int MAX_ATTR_LEN_URL_COMPONENT = 256;
private static final int MAX_ATTR_LEN_PERMISSION_GROUP = 256;
private static final int MAX_ATTR_LEN_PACKAGE = 256;
- private static final int MAX_ATTR_LEN_MIMETYPE = 512;
+ /**
+ * The mime type max length restriction here should match the restriction that is also
+ * placed in {@link android.content.pm.PackageManager#setMimeGroup(String, Set)}
+ */
+ private static final int MAX_ATTR_LEN_MIMETYPE = 255;
private static final int MAX_ATTR_LEN_NAME = 1024;
private static final int MAX_ATTR_LEN_PATH = 4000;
private static final int MAX_ATTR_LEN_VALUE = 32_768;
@@ -103,6 +109,7 @@
protected static final String TAG_ATTR_HOST = "host";
protected static final String TAG_ATTR_MANAGE_SPACE_ACTIVITY = "manageSpaceActivity";
protected static final String TAG_ATTR_MIMETYPE = "mimeType";
+ protected static final String TAG_ATTR_MIMEGROUP = "mimeGroup";
protected static final String TAG_ATTR_NAME = "name";
protected static final String TAG_ATTR_PACKAGE = "package";
protected static final String TAG_ATTR_PATH = "path";
@@ -367,6 +374,7 @@
case TAG_ATTR_BACKUP_AGENT:
case TAG_ATTR_CATEGORY:
case TAG_ATTR_MANAGE_SPACE_ACTIVITY:
+ case TAG_ATTR_MIMEGROUP:
case TAG_ATTR_NAME:
case TAG_ATTR_PARENT_ACTIVITY_NAME:
case TAG_ATTR_PERMISSION:
@@ -520,6 +528,8 @@
return MAX_ATTR_LEN_URL_COMPONENT;
case R.styleable.AndroidManifestData_mimeType:
return MAX_ATTR_LEN_MIMETYPE;
+ case R.styleable.AndroidManifestData_mimeGroup:
+ return MAX_ATTR_LEN_NAME;
case R.styleable.AndroidManifestData_path:
case R.styleable.AndroidManifestData_pathPattern:
case R.styleable.AndroidManifestData_pathPrefix:
diff --git a/core/java/android/content/rollback/OWNERS b/core/java/android/content/rollback/OWNERS
index 3093fd6..8e5a0d8 100644
--- a/core/java/android/content/rollback/OWNERS
+++ b/core/java/android/content/rollback/OWNERS
@@ -1,5 +1,5 @@
-# Bug component: 557916
+# Bug component: 819107
-narayan@google.com
-nandana@google.com
-olilan@google.com
+ancr@google.com
+harshitmahajan@google.com
+robertogil@google.com
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 3fcb3da..47ee76e 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -26,6 +26,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.annotation.TestApi;
+import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
@@ -755,7 +756,10 @@
@Override
public void onPendingIntent(PendingIntent pendingIntent) {
try {
- mContext.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
+ mContext.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0,
+ ActivityOptions.makeBasic()
+ .setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED).toBundle());
} catch (IntentSender.SendIntentException e) {
Log.e(
TAG,
diff --git a/core/java/android/hardware/face/FaceAuthenticateOptions.java b/core/java/android/hardware/face/FaceAuthenticateOptions.java
index 1c6de04..518f902a 100644
--- a/core/java/android/hardware/face/FaceAuthenticateOptions.java
+++ b/core/java/android/hardware/face/FaceAuthenticateOptions.java
@@ -261,7 +261,7 @@
* The reason for this operation when requested by the system (sysui),
* otherwise AUTHENTICATE_REASON_UNKNOWN.
*
- * See frameworks/base/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
+ * See packages/SystemUI/src/com/android/systemui/deviceentry/shared/FaceAuthReason.kt
* for more details about each reason.
*/
@DataClass.Generated.Member
@@ -524,7 +524,7 @@
* The reason for this operation when requested by the system (sysui),
* otherwise AUTHENTICATE_REASON_UNKNOWN.
*
- * See frameworks/base/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
+ * See packages/SystemUI/src/com/android/systemui/deviceentry/shared/FaceAuthReason.kt
* for more details about each reason.
*/
@DataClass.Generated.Member
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index 967a0cc..286cf28 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -95,4 +95,6 @@
void registerWlcStateListener(in INfcWlcStateListener listener);
void unregisterWlcStateListener(in INfcWlcStateListener listener);
WlcLDeviceInfo getWlcLDeviceInfo();
+
+ void updateDiscoveryTechnology(IBinder b, int pollFlags, int listenFlags);
}
diff --git a/core/java/android/nfc/INfcCardEmulation.aidl b/core/java/android/nfc/INfcCardEmulation.aidl
index 191385a..f4b4604 100644
--- a/core/java/android/nfc/INfcCardEmulation.aidl
+++ b/core/java/android/nfc/INfcCardEmulation.aidl
@@ -43,4 +43,7 @@
ApduServiceInfo getPreferredPaymentService(int userHandle);
boolean setServiceEnabledForCategoryOther(int userHandle, in ComponentName app, boolean status);
boolean isDefaultPaymentRegistered();
+
+ boolean overrideRoutingTable(int userHandle, String protocol, String technology);
+ boolean recoverRoutingTable(int userHandle);
}
diff --git a/core/java/android/nfc/NfcActivityManager.java b/core/java/android/nfc/NfcActivityManager.java
index 8d75cac..f03fc0a 100644
--- a/core/java/android/nfc/NfcActivityManager.java
+++ b/core/java/android/nfc/NfcActivityManager.java
@@ -112,6 +112,9 @@
Bundle readerModeExtras = null;
Binder token;
+ int mPollTech = NfcAdapter.FLAG_USE_ALL_TECH;
+ int mListenTech = NfcAdapter.FLAG_USE_ALL_TECH;
+
public NfcActivityState(Activity activity) {
if (activity.isDestroyed()) {
throw new IllegalStateException("activity is already destroyed");
@@ -132,6 +135,9 @@
readerModeFlags = 0;
readerModeExtras = null;
token = null;
+
+ mPollTech = NfcAdapter.FLAG_USE_ALL_TECH;
+ mListenTech = NfcAdapter.FLAG_USE_ALL_TECH;
}
@Override
public String toString() {
@@ -278,6 +284,9 @@
int readerModeFlags = 0;
Bundle readerModeExtras = null;
Binder token;
+ int pollTech;
+ int listenTech;
+
synchronized (NfcActivityManager.this) {
NfcActivityState state = findActivityState(activity);
if (DBG) Log.d(TAG, "onResume() for " + activity + " " + state);
@@ -286,9 +295,15 @@
token = state.token;
readerModeFlags = state.readerModeFlags;
readerModeExtras = state.readerModeExtras;
+
+ pollTech = state.mPollTech;
+ listenTech = state.mListenTech;
}
if (readerModeFlags != 0) {
setReaderMode(token, readerModeFlags, readerModeExtras);
+ } else if (listenTech != NfcAdapter.FLAG_USE_ALL_TECH
+ || pollTech != NfcAdapter.FLAG_USE_ALL_TECH) {
+ changeDiscoveryTech(token, pollTech, listenTech);
}
requestNfcServiceCallback();
}
@@ -298,6 +313,9 @@
public void onActivityPaused(Activity activity) {
boolean readerModeFlagsSet;
Binder token;
+ int pollTech;
+ int listenTech;
+
synchronized (NfcActivityManager.this) {
NfcActivityState state = findActivityState(activity);
if (DBG) Log.d(TAG, "onPause() for " + activity + " " + state);
@@ -305,10 +323,17 @@
state.resumed = false;
token = state.token;
readerModeFlagsSet = state.readerModeFlags != 0;
+
+ pollTech = state.mPollTech;
+ listenTech = state.mListenTech;
}
if (readerModeFlagsSet) {
// Restore default p2p modes
setReaderMode(token, 0, null);
+ } else if (listenTech != NfcAdapter.FLAG_USE_ALL_TECH
+ || pollTech != NfcAdapter.FLAG_USE_ALL_TECH) {
+ changeDiscoveryTech(token,
+ NfcAdapter.FLAG_USE_ALL_TECH, NfcAdapter.FLAG_USE_ALL_TECH);
}
}
@@ -333,4 +358,53 @@
}
}
+ /** setDiscoveryTechnology() implementation */
+ public void setDiscoveryTech(Activity activity, int pollTech, int listenTech) {
+ boolean isResumed;
+ Binder token;
+ boolean readerModeFlagsSet;
+ synchronized (NfcActivityManager.this) {
+ NfcActivityState state = getActivityState(activity);
+ readerModeFlagsSet = state.readerModeFlags != 0;
+ state.mListenTech = listenTech;
+ state.mPollTech = pollTech;
+ token = state.token;
+ isResumed = state.resumed;
+ }
+ if (!readerModeFlagsSet && isResumed) {
+ changeDiscoveryTech(token, pollTech, listenTech);
+ } else if (readerModeFlagsSet) {
+ throw new IllegalStateException("Cannot be used when the Reader Mode is enabled");
+ }
+ }
+
+ /** resetDiscoveryTechnology() implementation */
+ public void resetDiscoveryTech(Activity activity) {
+ boolean isResumed;
+ Binder token;
+ boolean readerModeFlagsSet;
+ synchronized (NfcActivityManager.this) {
+ NfcActivityState state = getActivityState(activity);
+ readerModeFlagsSet = state.readerModeFlags != 0;
+ state.mListenTech = NfcAdapter.FLAG_USE_ALL_TECH;
+ state.mPollTech = NfcAdapter.FLAG_USE_ALL_TECH;
+ token = state.token;
+ isResumed = state.resumed;
+ }
+ if (readerModeFlagsSet) {
+ disableReaderMode(activity);
+ } else if (isResumed) {
+ changeDiscoveryTech(token, NfcAdapter.FLAG_USE_ALL_TECH, NfcAdapter.FLAG_USE_ALL_TECH);
+ }
+
+ }
+
+ private void changeDiscoveryTech(Binder token, int pollTech, int listenTech) {
+ try {
+ NfcAdapter.sService.updateDiscoveryTechnology(token, pollTech, listenTech);
+ } catch (RemoteException e) {
+ mAdapter.attemptDeadServiceRecovery(e);
+ }
+ }
+
}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 21e23ae..5a40e42 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -333,6 +333,19 @@
*/
public static final int FLAG_READER_NFC_BARCODE = 0x10;
+ /** @hide */
+ @IntDef(flag = true, prefix = {"FLAG_READER_"}, value = {
+ FLAG_READER_KEEP,
+ FLAG_READER_DISABLE,
+ FLAG_READER_NFC_A,
+ FLAG_READER_NFC_B,
+ FLAG_READER_NFC_F,
+ FLAG_READER_NFC_V,
+ FLAG_READER_NFC_BARCODE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PollTechnology {}
+
/**
* Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.
* <p>
@@ -360,6 +373,76 @@
public static final String EXTRA_READER_PRESENCE_CHECK_DELAY = "presence";
/**
+ * Flag for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+ * <p>
+ * Setting this flag enables listening for Nfc-A technology.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+ public static final int FLAG_LISTEN_NFC_PASSIVE_A = 0x1;
+
+ /**
+ * Flag for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+ * <p>
+ * Setting this flag enables listening for Nfc-B technology.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+ public static final int FLAG_LISTEN_NFC_PASSIVE_B = 1 << 1;
+
+ /**
+ * Flag for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+ * <p>
+ * Setting this flag enables listening for Nfc-F technology.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+ public static final int FLAG_LISTEN_NFC_PASSIVE_F = 1 << 2;
+
+ /**
+ * Flags for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+ * <p>
+ * Setting this flag disables listening.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+ public static final int FLAG_LISTEN_DISABLE = 0x0;
+
+ /**
+ * Flags for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+ * <p>
+ * Setting this flag disables polling.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+ public static final int FLAG_READER_DISABLE = 0x0;
+
+ /**
+ * Flags for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+ * <p>
+ * Setting this flag makes listening to use current flags.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+ public static final int FLAG_LISTEN_KEEP = -1;
+
+ /**
+ * Flags for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+ * <p>
+ * Setting this flag makes polling to use current flags.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+ public static final int FLAG_READER_KEEP = -1;
+
+ /** @hide */
+ public static final int FLAG_USE_ALL_TECH = 0xff;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = {"FLAG_LISTEN_"}, value = {
+ FLAG_LISTEN_KEEP,
+ FLAG_LISTEN_DISABLE,
+ FLAG_LISTEN_NFC_PASSIVE_A,
+ FLAG_LISTEN_NFC_PASSIVE_B,
+ FLAG_LISTEN_NFC_PASSIVE_F
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ListenTechnology {}
+
+ /**
* @hide
* @removed
*/
@@ -437,12 +520,14 @@
@Retention(RetentionPolicy.SOURCE)
public @interface TagIntentAppPreferenceResult {}
- // Guarded by NfcAdapter.class
+ // Guarded by sLock
static boolean sIsInitialized = false;
static boolean sHasNfcFeature;
static boolean sHasCeFeature;
static boolean sHasNfcWlcFeature;
+ static Object sLock = new Object();
+
// Final after first constructor, except for
// attemptDeadServiceRecovery() when NFC crashes - we accept a best effort
// recovery
@@ -1235,7 +1320,7 @@
@java.lang.Deprecated
@UnsupportedAppUsage
public void setBeamPushUris(Uri[] uris, Activity activity) {
- synchronized (NfcAdapter.class) {
+ synchronized (sLock) {
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
@@ -1305,7 +1390,7 @@
@java.lang.Deprecated
@UnsupportedAppUsage
public void setBeamPushUrisCallback(CreateBeamUrisCallback callback, Activity activity) {
- synchronized (NfcAdapter.class) {
+ synchronized (sLock) {
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
@@ -1390,7 +1475,7 @@
@UnsupportedAppUsage
public void setNdefPushMessage(NdefMessage message, Activity activity,
Activity ... activities) {
- synchronized (NfcAdapter.class) {
+ synchronized (sLock) {
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
@@ -1404,7 +1489,7 @@
@SystemApi
@UnsupportedAppUsage
public void setNdefPushMessage(NdefMessage message, Activity activity, int flags) {
- synchronized (NfcAdapter.class) {
+ synchronized (sLock) {
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
@@ -1483,7 +1568,7 @@
@UnsupportedAppUsage
public void setNdefPushMessageCallback(CreateNdefMessageCallback callback, Activity activity,
Activity ... activities) {
- synchronized (NfcAdapter.class) {
+ synchronized (sLock) {
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
@@ -1534,7 +1619,7 @@
@UnsupportedAppUsage
public void setOnNdefPushCompleteCallback(OnNdefPushCompleteCallback callback,
Activity activity, Activity ... activities) {
- synchronized (NfcAdapter.class) {
+ synchronized (sLock) {
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
@@ -1577,7 +1662,7 @@
*/
public void enableForegroundDispatch(Activity activity, PendingIntent intent,
IntentFilter[] filters, String[][] techLists) {
- synchronized (NfcAdapter.class) {
+ synchronized (sLock) {
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
@@ -1612,7 +1697,7 @@
* @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
*/
public void disableForegroundDispatch(Activity activity) {
- synchronized (NfcAdapter.class) {
+ synchronized (sLock) {
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
@@ -1648,7 +1733,7 @@
*/
public void enableReaderMode(Activity activity, ReaderCallback callback, int flags,
Bundle extras) {
- synchronized (NfcAdapter.class) {
+ synchronized (sLock) {
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
@@ -1665,7 +1750,7 @@
* @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
*/
public void disableReaderMode(Activity activity) {
- synchronized (NfcAdapter.class) {
+ synchronized (sLock) {
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
@@ -1693,7 +1778,7 @@
@FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
@SuppressLint("VisiblySynchronized")
public void setReaderMode(boolean enablePolling) {
- synchronized (NfcAdapter.class) {
+ synchronized (sLock) {
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
@@ -1708,6 +1793,80 @@
}
/**
+ * Set the NFC controller to enable specific poll/listen technologies,
+ * as specified in parameters, while this Activity is in the foreground.
+ *
+ * Use {@link #FLAG_READER_KEEP} to keep current polling technology.
+ * Use {@link #FLAG_LISTEN_KEEP} to keep current listenig technology.
+ * Use {@link #FLAG_READER_DISABLE} to disable polling.
+ * Use {@link #FLAG_LISTEN_DISABLE} to disable listening.
+ * Also refer to {@link #resetDiscoveryTechnology(Activity)} to restore these changes.
+ * </p>
+ * The pollTech, listenTech parameters can be one or several of below list.
+ * <pre>
+ * Poll Listen
+ * Passive A 0x01 (NFC_A) 0x01 (NFC_PASSIVE_A)
+ * Passive B 0x02 (NFC_B) 0x02 (NFC_PASSIVE_B)
+ * Passive F 0x04 (NFC_F) 0x04 (NFC_PASSIVE_F)
+ * ISO 15693 0x08 (NFC_V) -
+ * Kovio 0x10 (NFC_BARCODE) -
+ * </pre>
+ * <p>Example usage in an Activity that requires to disable poll,
+ * keep current listen technologies:
+ * <pre>
+ * protected void onResume() {
+ * mNfcAdapter = NfcAdapter.getDefaultAdapter(getApplicationContext());
+ * mNfcAdapter.setDiscoveryTechnology(this,
+ * NfcAdapter.FLAG_READER_DISABLE, NfcAdapter.FLAG_LISTEN_KEEP);
+ * }</pre></p>
+ * @param activity The Activity that requests NFC controller to enable specific technologies.
+ * @param pollTech Flags indicating poll technologies.
+ * @param listenTech Flags indicating listen technologies.
+ * @throws UnsupportedOperationException if FEATURE_NFC,
+ * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF are unavailable.
+ */
+
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+ public void setDiscoveryTechnology(@NonNull Activity activity,
+ @PollTechnology int pollTech, @ListenTechnology int listenTech) {
+ if (listenTech == FLAG_LISTEN_DISABLE) {
+ synchronized (sLock) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
+ mNfcActivityManager.enableReaderMode(activity, null, pollTech, null);
+ return;
+ }
+ if (pollTech == FLAG_READER_DISABLE) {
+ synchronized (sLock) {
+ if (!sHasCeFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
+ } else {
+ synchronized (sLock) {
+ if (!sHasNfcFeature || !sHasCeFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+ mNfcActivityManager.setDiscoveryTech(activity, pollTech, listenTech);
+ }
+
+ /**
+ * Restore the poll/listen technologies of NFC controller,
+ * which were changed by {@link #setDiscoveryTechnology(Activity , int , int)}
+ *
+ * @param activity The Activity that requests to changed technologies.
+ */
+
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+ public void resetDiscoveryTechnology(@NonNull Activity activity) {
+ mNfcActivityManager.resetDiscoveryTech(activity);
+ }
+
+ /**
* Manually invoke Android Beam to share data.
*
* <p>The Android Beam animation is normally only shown when two NFC-capable
@@ -1737,7 +1896,7 @@
@java.lang.Deprecated
@UnsupportedAppUsage
public boolean invokeBeam(Activity activity) {
- synchronized (NfcAdapter.class) {
+ synchronized (sLock) {
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
@@ -1775,7 +1934,7 @@
@Deprecated
@UnsupportedAppUsage
public void enableForegroundNdefPush(Activity activity, NdefMessage message) {
- synchronized (NfcAdapter.class) {
+ synchronized (sLock) {
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
@@ -1805,7 +1964,7 @@
@Deprecated
@UnsupportedAppUsage
public void disableForegroundNdefPush(Activity activity) {
- synchronized (NfcAdapter.class) {
+ synchronized (sLock) {
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
@@ -2085,7 +2244,7 @@
@java.lang.Deprecated
@UnsupportedAppUsage
public boolean isNdefPushEnabled() {
- synchronized (NfcAdapter.class) {
+ synchronized (sLock) {
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
@@ -2199,7 +2358,7 @@
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
public boolean addNfcUnlockHandler(final NfcUnlockHandler unlockHandler,
String[] tagTechnologies) {
- synchronized (NfcAdapter.class) {
+ synchronized (sLock) {
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
@@ -2248,7 +2407,7 @@
@SystemApi
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
public boolean removeNfcUnlockHandler(NfcUnlockHandler unlockHandler) {
- synchronized (NfcAdapter.class) {
+ synchronized (sLock) {
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
index 58b6179..ad86d70 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -998,6 +998,87 @@
}
}
+ /**
+ * Setting NFC controller routing table, which includes Protocol Route and Technology Route,
+ * while this Activity is in the foreground.
+ *
+ * The parameter set to null can be used to keep current values for that entry.
+ * <p>
+ * Example usage in an Activity that requires to set proto route to "ESE" and keep tech route:
+ * <pre>
+ * protected void onResume() {
+ * mNfcAdapter.overrideRoutingTable(this , "ESE" , null);
+ * }</pre>
+ * </p>
+ * Also activities must call this method when it goes to the background,
+ * with all parameters set to null.
+ * @param activity The Activity that requests NFC controller routing table to be changed.
+ * @param protocol ISO-DEP route destination, which can be "DH" or "UICC" or "ESE".
+ * @param technology Tech-A, Tech-B route destination, which can be "DH" or "UICC" or "ESE".
+ * @return true if operation is successful and false otherwise
+ *
+ * This is a high risk API and only included to support mainline effort
+ * @hide
+ */
+ public boolean overrideRoutingTable(Activity activity, String protocol, String technology) {
+ if (activity == null) {
+ throw new NullPointerException("activity or service or category is null");
+ }
+ if (!activity.isResumed()) {
+ throw new IllegalArgumentException("Activity must be resumed.");
+ }
+ try {
+ return sService.overrideRoutingTable(UserHandle.myUserId(), protocol, technology);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.overrideRoutingTable(UserHandle.myUserId(), protocol, technology);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Restore the NFC controller routing table,
+ * which was changed by {@link #overrideRoutingTable(Activity, String, String)}
+ *
+ * @param activity The Activity that requested NFC controller routing table to be changed.
+ * @return true if operation is successful and false otherwise
+ *
+ * @hide
+ */
+ public boolean recoverRoutingTable(Activity activity) {
+ if (activity == null) {
+ throw new NullPointerException("activity is null");
+ }
+ if (!activity.isResumed()) {
+ throw new IllegalArgumentException("Activity must be resumed.");
+ }
+ try {
+ return sService.recoverRoutingTable(UserHandle.myUserId());
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.recoverRoutingTable(UserHandle.myUserId());
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return false;
+ }
+ }
+ }
+
void recoverService() {
NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
sService = adapter.getCardEmulationService();
diff --git a/core/java/android/nfc/flags.aconfig b/core/java/android/nfc/flags.aconfig
index ce4f777..01a4570 100644
--- a/core/java/android/nfc/flags.aconfig
+++ b/core/java/android/nfc/flags.aconfig
@@ -62,3 +62,10 @@
description: "Flag for NFC charging changes"
bug: "292143899"
}
+
+flag {
+ name: "enable_nfc_set_discovery_tech"
+ namespace: "nfc"
+ description: "Flag for NFC set discovery tech API"
+ bug: "300351519"
+}
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index a9b7257..5871717 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -1315,9 +1315,7 @@
if (IS_ENG) return true;
if (IS_TREBLE_ENABLED) {
- // If we can run this code, the device should already pass AVB.
- // So, we don't need to check AVB here.
- int result = VintfObject.verifyWithoutAvb();
+ int result = VintfObject.verifyBuildAtBoot();
if (result != 0) {
Slog.e(TAG, "Vendor interface is incompatible, error="
diff --git a/core/java/android/os/ISystemConfig.aidl b/core/java/android/os/ISystemConfig.aidl
index 61b24aa..b7649ba 100644
--- a/core/java/android/os/ISystemConfig.aidl
+++ b/core/java/android/os/ISystemConfig.aidl
@@ -52,4 +52,9 @@
* @see SystemConfigManager#getDefaultVrComponents
*/
List<ComponentName> getDefaultVrComponents();
+
+ /**
+ * @see SystemConfigManager#getPreventUserDisablePackages
+ */
+ List<String> getPreventUserDisablePackages();
}
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index fbec518..c60f949 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -57,7 +57,6 @@
@UnsupportedAppUsage
Message mMessages;
- private Message mLast;
@UnsupportedAppUsage
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
@@ -67,10 +66,6 @@
// Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
private boolean mBlocked;
- // Tracks the number of async message. We use this in enqueueMessage() to avoid searching the
- // queue for async messages when inserting a message at the tail.
- private int mAsyncMessageCount;
-
// The next barrier token.
// Barriers are indicated by messages with a null target whose arg1 field carries the token.
@UnsupportedAppUsage
@@ -369,21 +364,12 @@
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
- if (prevMsg.next == null) {
- mLast = prevMsg;
- }
} else {
mMessages = msg.next;
- if (msg.next == null) {
- mLast = null;
- }
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
- if (msg.isAsynchronous()) {
- mAsyncMessageCount--;
- }
return msg;
}
} else {
@@ -506,14 +492,6 @@
msg.when = when;
msg.arg1 = token;
- if (Flags.messageQueueTailTracking() && mLast != null && mLast.when <= when) {
- /* Message goes to tail of list */
- mLast.next = msg;
- mLast = msg;
- msg.next = null;
- return token;
- }
-
Message prev = null;
Message p = mMessages;
if (when != 0) {
@@ -522,12 +500,6 @@
p = p.next;
}
}
-
- if (p == null) {
- /* We reached the tail of the list, or list is empty. */
- mLast = msg;
- }
-
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
@@ -568,15 +540,9 @@
final boolean needWake;
if (prev != null) {
prev.next = p.next;
- if (prev.next == null) {
- mLast = prev;
- }
needWake = false;
} else {
mMessages = p.next;
- if (mMessages == null) {
- mLast = null;
- }
needWake = mMessages == null || mMessages.target != null;
}
p.recycleUnchecked();
@@ -616,77 +582,24 @@
msg.next = p;
mMessages = msg;
needWake = mBlocked;
- if (p == null) {
- mLast = mMessages;
- }
} else {
- // Message is to be inserted at tail or middle of queue. Usually we don't have to
- // wake up the event queue unless there is a barrier at the head of the queue and
- // the message is the earliest asynchronous message in the queue.
- //
- // For readability, we split this portion of the function into two blocks based on
- // whether tail tracking is enabled. This has a minor implication for the case
- // where tail tracking is disabled. See the comment below.
- if (Flags.messageQueueTailTracking()) {
- needWake = mBlocked && p.target == null && msg.isAsynchronous()
- && mAsyncMessageCount == 0;
- if (when >= mLast.when) {
- msg.next = null;
- mLast.next = msg;
- mLast = msg;
- } else {
- // Inserted within the middle of the queue.
- Message prev;
- for (;;) {
- prev = p;
- p = p.next;
- if (p == null || when < p.when) {
- break;
- }
- }
- if (p == null) {
- /* Inserting at tail of queue */
- mLast = msg;
- }
- msg.next = p; // invariant: p == prev.next
- prev.next = msg;
+ // Inserted within the middle of the queue. Usually we don't have to wake
+ // up the event queue unless there is a barrier at the head of the queue
+ // and the message is the earliest asynchronous message in the queue.
+ needWake = mBlocked && p.target == null && msg.isAsynchronous();
+ Message prev;
+ for (;;) {
+ prev = p;
+ p = p.next;
+ if (p == null || when < p.when) {
+ break;
}
- } else {
- needWake = mBlocked && p.target == null && msg.isAsynchronous();
- Message prev;
- for (;;) {
- prev = p;
- p = p.next;
- if (p == null || when < p.when) {
- break;
- }
- if (needWake && p.isAsynchronous()) {
- needWake = false;
- }
+ if (needWake && p.isAsynchronous()) {
+ needWake = false;
}
- msg.next = p; // invariant: p == prev.next
- prev.next = msg;
-
- /*
- * If this block is executing then we have a build without tail tracking -
- * specifically: Flags.messageQueueTailTracking() == false. This is determined
- * at build time so the flag won't change on us during runtime.
- *
- * Since we don't want to pepper the code with extra checks, we only check
- * for tail tracking when we might use mLast. Otherwise, we continue to update
- * mLast as the tail of the list.
- *
- * In this case however we are not maintaining mLast correctly. Since we never
- * use it, this is fine. However, we run the risk of leaking a reference.
- * So set mLast to null in this case to avoid any Message leaks. The other
- * sites will never use the value so we are safe against null pointer derefs.
- */
- mLast = null;
}
- }
-
- if (msg.isAsynchronous()) {
- mAsyncMessageCount++;
+ msg.next = p; // invariant: p == prev.next
+ prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
@@ -779,17 +692,10 @@
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
- if (p.isAsynchronous()) {
- mAsyncMessageCount--;
- }
p.recycleUnchecked();
p = n;
}
- if (p == null) {
- mLast = mMessages;
- }
-
// Remove all messages after front.
while (p != null) {
Message n = p.next;
@@ -797,14 +703,8 @@
if (n.target == h && n.what == what
&& (object == null || n.obj == object)) {
Message nn = n.next;
- if (n.isAsynchronous()) {
- mAsyncMessageCount--;
- }
n.recycleUnchecked();
p.next = nn;
- if (p.next == null) {
- mLast = p;
- }
continue;
}
}
@@ -826,17 +726,10 @@
&& (object == null || object.equals(p.obj))) {
Message n = p.next;
mMessages = n;
- if (p.isAsynchronous()) {
- mAsyncMessageCount--;
- }
p.recycleUnchecked();
p = n;
}
- if (p == null) {
- mLast = mMessages;
- }
-
// Remove all messages after front.
while (p != null) {
Message n = p.next;
@@ -844,14 +737,8 @@
if (n.target == h && n.what == what
&& (object == null || object.equals(n.obj))) {
Message nn = n.next;
- if (n.isAsynchronous()) {
- mAsyncMessageCount--;
- }
n.recycleUnchecked();
p.next = nn;
- if (p.next == null) {
- mLast = p;
- }
continue;
}
}
@@ -873,17 +760,10 @@
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
- if (p.isAsynchronous()) {
- mAsyncMessageCount--;
- }
p.recycleUnchecked();
p = n;
}
- if (p == null) {
- mLast = mMessages;
- }
-
// Remove all messages after front.
while (p != null) {
Message n = p.next;
@@ -891,14 +771,8 @@
if (n.target == h && n.callback == r
&& (object == null || n.obj == object)) {
Message nn = n.next;
- if (n.isAsynchronous()) {
- mAsyncMessageCount--;
- }
n.recycleUnchecked();
p.next = nn;
- if (p.next == null) {
- mLast = p;
- }
continue;
}
}
@@ -920,17 +794,10 @@
&& (object == null || object.equals(p.obj))) {
Message n = p.next;
mMessages = n;
- if (p.isAsynchronous()) {
- mAsyncMessageCount--;
- }
p.recycleUnchecked();
p = n;
}
- if (p == null) {
- mLast = mMessages;
- }
-
// Remove all messages after front.
while (p != null) {
Message n = p.next;
@@ -938,14 +805,8 @@
if (n.target == h && n.callback == r
&& (object == null || object.equals(n.obj))) {
Message nn = n.next;
- if (n.isAsynchronous()) {
- mAsyncMessageCount--;
- }
n.recycleUnchecked();
p.next = nn;
- if (p.next == null) {
- mLast = p;
- }
continue;
}
}
@@ -968,31 +829,18 @@
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
- if (p.isAsynchronous()) {
- mAsyncMessageCount--;
- }
p.recycleUnchecked();
p = n;
}
- if (p == null) {
- mLast = mMessages;
- }
-
// Remove all messages after front.
while (p != null) {
Message n = p.next;
if (n != null) {
if (n.target == h && (object == null || n.obj == object)) {
Message nn = n.next;
- if (n.isAsynchronous()) {
- mAsyncMessageCount--;
- }
n.recycleUnchecked();
p.next = nn;
- if (p.next == null) {
- mLast = p;
- }
continue;
}
}
@@ -1014,31 +862,18 @@
&& (object == null || object.equals(p.obj))) {
Message n = p.next;
mMessages = n;
- if (p.isAsynchronous()) {
- mAsyncMessageCount--;
- }
p.recycleUnchecked();
p = n;
}
- if (p == null) {
- mLast = mMessages;
- }
-
// Remove all messages after front.
while (p != null) {
Message n = p.next;
if (n != null) {
if (n.target == h && (object == null || object.equals(n.obj))) {
Message nn = n.next;
- if (n.isAsynchronous()) {
- mAsyncMessageCount--;
- }
n.recycleUnchecked();
p.next = nn;
- if (p.next == null) {
- mLast = p;
- }
continue;
}
}
@@ -1055,8 +890,6 @@
p = n;
}
mMessages = null;
- mLast = null;
- mAsyncMessageCount = 0;
}
private void removeAllFutureMessagesLocked() {
@@ -1078,14 +911,9 @@
p = n;
}
p.next = null;
- mLast = p;
-
do {
p = n;
n = p.next;
- if (p.isAsynchronous()) {
- mAsyncMessageCount--;
- }
p.recycleUnchecked();
} while (n != null);
}
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index d3f2c7a..eb5b511 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -94,4 +94,8 @@
per-file Temperature.java = file:/THERMAL_OWNERS
# SecurityStateManager
-per-file *SecurityStateManager* = file:/SECURITY_STATE_OWNERS
\ No newline at end of file
+per-file *SecurityStateManager* = file:/SECURITY_STATE_OWNERS
+
+# SystemConfig
+per-file ISystemConfig.aidl = file:/PACKAGE_MANAGER_OWNERS
+per-file SystemConfigManager.java = file:/PACKAGE_MANAGER_OWNERS
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index f71c269..07f7690 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -18,8 +18,6 @@
import static android.view.Display.DEFAULT_DISPLAY;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -47,11 +45,8 @@
import android.util.Log;
import android.view.Display;
-import libcore.io.Streams;
-
import java.io.ByteArrayInputStream;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
@@ -75,7 +70,6 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
-import java.util.zip.ZipInputStream;
import sun.security.pkcs.PKCS7;
import sun.security.pkcs.SignerInfo;
@@ -426,72 +420,43 @@
} finally {
raf.close();
}
-
- // Additionally verify the package compatibility.
- if (!readAndVerifyPackageCompatibilityEntry(packageFile)) {
- throw new SignatureException("package compatibility verification failed");
- }
}
/**
* Verifies the compatibility entry from an {@link InputStream}.
*
- * @return the verification result.
+ * @param inputStream The stream that contains the package compatibility info.
+ * @throws IOException Never.
+ * @return {@code true}.
+ * @deprecated This function no longer checks {@code inputStream} and
+ * unconditionally returns true. Instead, check compatibility when the
+ * OTA package is generated.
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(
+ publicAlternatives = "Use {@code true} directly",
+ maxTargetSdk = Build.VERSION_CODES.VANILLA_ICE_CREAM)
private static boolean verifyPackageCompatibility(InputStream inputStream) throws IOException {
- ArrayList<String> list = new ArrayList<>();
- ZipInputStream zis = new ZipInputStream(inputStream);
- ZipEntry entry;
- while ((entry = zis.getNextEntry()) != null) {
- long entrySize = entry.getSize();
- if (entrySize > Integer.MAX_VALUE || entrySize < 0) {
- throw new IOException(
- "invalid entry size (" + entrySize + ") in the compatibility file");
- }
- byte[] bytes = new byte[(int) entrySize];
- Streams.readFully(zis, bytes);
- list.add(new String(bytes, UTF_8));
- }
- if (list.isEmpty()) {
- throw new IOException("no entries found in the compatibility file");
- }
- return (VintfObject.verify(list.toArray(new String[list.size()])) == 0);
- }
-
- /**
- * Reads and verifies the compatibility entry in an OTA zip package. The compatibility entry is
- * a zip file (inside the OTA package zip).
- *
- * @return {@code true} if the entry doesn't exist or verification passes.
- */
- private static boolean readAndVerifyPackageCompatibilityEntry(File packageFile)
- throws IOException {
- try (ZipFile zip = new ZipFile(packageFile)) {
- ZipEntry entry = zip.getEntry("compatibility.zip");
- if (entry == null) {
- return true;
- }
- InputStream inputStream = zip.getInputStream(entry);
- return verifyPackageCompatibility(inputStream);
- }
+ return true;
}
/**
* Verifies the package compatibility info against the current system.
*
* @param compatibilityFile the {@link File} that contains the package compatibility info.
- * @throws IOException if there were any errors reading the compatibility file.
- * @return the compatibility verification result.
+ * @throws IOException Never.
+ * @return {@code true}
+ * @deprecated This function no longer checks {@code compatibilityFile} and
+ * unconditionally returns true. Instead, check compatibility when the
+ * OTA package is generated.
*
* {@hide}
*/
+ @Deprecated
@SystemApi
@SuppressLint("RequiresPermission")
public static boolean verifyPackageCompatibility(File compatibilityFile) throws IOException {
- try (InputStream inputStream = new FileInputStream(compatibilityFile)) {
- return verifyPackageCompatibility(inputStream);
- }
+ return true;
}
/**
diff --git a/core/java/android/os/SystemConfigManager.java b/core/java/android/os/SystemConfigManager.java
index 77843d9..21ffbf1 100644
--- a/core/java/android/os/SystemConfigManager.java
+++ b/core/java/android/os/SystemConfigManager.java
@@ -161,4 +161,18 @@
}
return Collections.emptyList();
}
+
+ /**
+ * Return the packages that are prevented from being disabled, where if
+ * disabled it would result in a non-functioning system or similar.
+ * @hide
+ */
+ @NonNull
+ public List<String> getPreventUserDisablePackages() {
+ try {
+ return mInterface.getPreventUserDisablePackages();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/os/UpdateEngineStable.java b/core/java/android/os/UpdateEngineStable.java
new file mode 100644
index 0000000..9e2593e
--- /dev/null
+++ b/core/java/android/os/UpdateEngineStable.java
@@ -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 android.os;
+
+import android.annotation.IntDef;
+
+/**
+ * UpdateEngineStable handles calls to the update engine stalbe which takes care of A/B OTA updates.
+ * This interface has lesser functionalities than UpdateEngine and doesn't allow cancel.
+ *
+ * <p>The minimal flow is:
+ *
+ * <ol>
+ * <li>Create a new UpdateEngineStable instance.
+ * <li>Call {@link #bind}, provide callback function.
+ * <li>Call {@link #applyPayloadFd}.
+ * </ol>
+ *
+ * The APIs defined in this class and UpdateEngineStableCallback class must be in sync with the ones
+ * in {@code system/update_engine/stable/android/os/IUpdateEngineStable.aidl} and {@code
+ * ssystem/update_engine/stable/android/os/IUpdateEngineStableCallback.aidl}.
+ *
+ * @hide
+ */
+public class UpdateEngineStable {
+ private static final String TAG = "UpdateEngineStable";
+
+ private static final String UPDATE_ENGINE_STABLE_SERVICE =
+ "android.os.UpdateEngineStableService";
+
+ /**
+ * Error codes from update engine upon finishing a call to {@link applyPayloadFd}. Values will
+ * be passed via the callback function {@link
+ * UpdateEngineStableCallback#onPayloadApplicationComplete}. Values must agree with the ones in
+ * {@code system/update_engine/common/error_code.h}.
+ */
+ /** @hide */
+ @IntDef(
+ value = {
+ UpdateEngine.ErrorCodeConstants.SUCCESS,
+ UpdateEngine.ErrorCodeConstants.ERROR,
+ UpdateEngine.ErrorCodeConstants.FILESYSTEM_COPIER_ERROR,
+ UpdateEngine.ErrorCodeConstants.POST_INSTALL_RUNNER_ERROR,
+ UpdateEngine.ErrorCodeConstants.PAYLOAD_MISMATCHED_TYPE_ERROR,
+ UpdateEngine.ErrorCodeConstants.INSTALL_DEVICE_OPEN_ERROR,
+ UpdateEngine.ErrorCodeConstants.KERNEL_DEVICE_OPEN_ERROR,
+ UpdateEngine.ErrorCodeConstants.DOWNLOAD_TRANSFER_ERROR,
+ UpdateEngine.ErrorCodeConstants.PAYLOAD_HASH_MISMATCH_ERROR,
+ UpdateEngine.ErrorCodeConstants.PAYLOAD_SIZE_MISMATCH_ERROR,
+ UpdateEngine.ErrorCodeConstants.DOWNLOAD_PAYLOAD_VERIFICATION_ERROR,
+ UpdateEngine.ErrorCodeConstants.PAYLOAD_TIMESTAMP_ERROR,
+ UpdateEngine.ErrorCodeConstants.UPDATED_BUT_NOT_ACTIVE,
+ UpdateEngine.ErrorCodeConstants.NOT_ENOUGH_SPACE,
+ UpdateEngine.ErrorCodeConstants.DEVICE_CORRUPTED,
+ })
+ public @interface ErrorCode {}
+
+ private final IUpdateEngineStable mUpdateEngineStable;
+ private IUpdateEngineStableCallback mUpdateEngineStableCallback = null;
+ private final Object mUpdateEngineStableCallbackLock = new Object();
+
+ /**
+ * Creates a new instance.
+ *
+ * @hide
+ */
+ public UpdateEngineStable() {
+ mUpdateEngineStable =
+ IUpdateEngineStable.Stub.asInterface(
+ ServiceManager.getService(UPDATE_ENGINE_STABLE_SERVICE));
+ if (mUpdateEngineStable == null) {
+ throw new IllegalStateException("Failed to find " + UPDATE_ENGINE_STABLE_SERVICE);
+ }
+ }
+
+ /**
+ * Prepares this instance for use. The callback will be notified on any status change, and when
+ * the update completes. A handler can be supplied to control which thread runs the callback, or
+ * null.
+ *
+ * @hide
+ */
+ public boolean bind(final UpdateEngineStableCallback callback, final Handler handler) {
+ synchronized (mUpdateEngineStableCallbackLock) {
+ mUpdateEngineStableCallback =
+ new IUpdateEngineStableCallback.Stub() {
+ @Override
+ public void onStatusUpdate(final int status, final float percent) {
+ if (handler != null) {
+ handler.post(
+ new Runnable() {
+ @Override
+ public void run() {
+ callback.onStatusUpdate(status, percent);
+ }
+ });
+ } else {
+ callback.onStatusUpdate(status, percent);
+ }
+ }
+
+ @Override
+ public void onPayloadApplicationComplete(final int errorCode) {
+ if (handler != null) {
+ handler.post(
+ new Runnable() {
+ @Override
+ public void run() {
+ callback.onPayloadApplicationComplete(errorCode);
+ }
+ });
+ } else {
+ callback.onPayloadApplicationComplete(errorCode);
+ }
+ }
+
+ @Override
+ public int getInterfaceVersion() {
+ return super.VERSION;
+ }
+
+ @Override
+ public String getInterfaceHash() {
+ return super.HASH;
+ }
+ };
+
+ try {
+ return mUpdateEngineStable.bind(mUpdateEngineStableCallback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Equivalent to {@code bind(callback, null)}.
+ *
+ * @hide
+ */
+ public boolean bind(final UpdateEngineStableCallback callback) {
+ return bind(callback, null);
+ }
+
+ /**
+ * Applies payload from given ParcelFileDescriptor. Usage is same as UpdateEngine#applyPayload
+ *
+ * @hide
+ */
+ public void applyPayloadFd(
+ ParcelFileDescriptor fd, long offset, long size, String[] headerKeyValuePairs) {
+ try {
+ mUpdateEngineStable.applyPayloadFd(fd, offset, size, headerKeyValuePairs);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Unbinds the last bound callback function.
+ *
+ * @hide
+ */
+ public boolean unbind() {
+ synchronized (mUpdateEngineStableCallbackLock) {
+ if (mUpdateEngineStableCallback == null) {
+ return true;
+ }
+ try {
+ boolean result = mUpdateEngineStable.unbind(mUpdateEngineStableCallback);
+ mUpdateEngineStableCallback = null;
+ return result;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+}
diff --git a/core/java/android/os/UpdateEngineStableCallback.java b/core/java/android/os/UpdateEngineStableCallback.java
new file mode 100644
index 0000000..4bcfb4b
--- /dev/null
+++ b/core/java/android/os/UpdateEngineStableCallback.java
@@ -0,0 +1,46 @@
+/*
+ * 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 android.os;
+
+/**
+ * Callback function for UpdateEngineStable. Used to keep the caller up to date with progress, so
+ * the UI (if any) can be updated.
+ *
+ * <p>The APIs defined in this class and UpdateEngineStable class must be in sync with the ones in
+ * system/update_engine/stable/android/os/IUpdateEngineStable.aidl and
+ * system/update_engine/stable/android/os/IUpdateEngineStableCallback.aidl.
+ *
+ * <p>{@hide}
+ */
+public abstract class UpdateEngineStableCallback {
+
+ /**
+ * Invoked when anything changes. The value of {@code status} will be one of the values from
+ * {@link UpdateEngine.UpdateStatusConstants}, and {@code percent} will be valid
+ *
+ * @hide
+ */
+ public abstract void onStatusUpdate(int status, float percent);
+
+ /**
+ * Invoked when the payload has been applied, whether successfully or unsuccessfully. The value
+ * of {@code errorCode} will be one of the values from {@link UpdateEngine.ErrorCodeConstants}.
+ *
+ * @hide
+ */
+ public abstract void onPayloadApplicationComplete(@UpdateEngineStable.ErrorCode int errorCode);
+}
diff --git a/core/java/android/os/VintfObject.java b/core/java/android/os/VintfObject.java
index 1f11197..4fc5131 100644
--- a/core/java/android/os/VintfObject.java
+++ b/core/java/android/os/VintfObject.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.annotation.TestApi;
-import android.util.Slog;
import java.util.Map;
@@ -44,44 +43,8 @@
public static native String[] report();
/**
- * Verify that the given metadata for an OTA package is compatible with
- * this device.
- *
- * @param packageInfo a list of serialized form of HalManifest's /
- * CompatibilityMatri'ces (XML).
- * @return = 0 if success (compatible)
- * > 0 if incompatible
- * < 0 if any error (mount partition fails, illformed XML, etc.)
- *
- * @deprecated Checking compatibility against an OTA package is no longer
- * supported because the format of VINTF metadata in the OTA package may not
- * be recognized by the current system.
- *
- * <p>
- * <ul>
- * <li>This function always returns 0 for non-empty {@code packageInfo}.
- * </li>
- * <li>This function returns the result of {@link #verifyWithoutAvb} for
- * null or empty {@code packageInfo}.</li>
- * </ul>
- *
- * @hide
- */
- @Deprecated
- public static int verify(String[] packageInfo) {
- if (packageInfo != null && packageInfo.length > 0) {
- Slog.w(LOG_TAG, "VintfObject.verify() with non-empty packageInfo is deprecated. "
- + "Skipping compatibility checks for update package.");
- return 0;
- }
- Slog.w(LOG_TAG, "VintfObject.verify() is deprecated. Call verifyWithoutAvb() instead.");
- return verifyWithoutAvb();
- }
-
- /**
- * Verify Vintf compatibility on the device without checking AVB
- * (Android Verified Boot). It is useful to verify a running system
- * image where AVB check is irrelevant.
+ * Verify Vintf compatibility on the device at boot time. Certain checks
+ * like kernel checks, AVB checks are disabled.
*
* @return = 0 if success (compatible)
* > 0 if incompatible
@@ -89,7 +52,7 @@
*
* @hide
*/
- public static native int verifyWithoutAvb();
+ public static native int verifyBuildAtBoot();
/**
* @return a list of HAL names and versions that is supported by this
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 0db90bf..d646146 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -98,11 +98,3 @@
description: "Guards StrictMode APIs for detecting restricted network access."
bug: "317250784"
}
-
-flag {
- name: "message_queue_tail_tracking"
- namespace: "system_performance"
- description: "track tail of message queue."
- bug: "305311707"
- is_fixed_read_only: true
-}
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index d09c229..fae7cb3 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -64,3 +64,10 @@
description: "enable Permission PREPARE_FACTORY_RESET."
bug: "302016478"
}
+
+flag {
+ name: "retail_demo_role_enabled"
+ namespace: "permissions"
+ description: "default retail demo role holder"
+ bug: "274132354"
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 54cc5f4..144e64f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -108,10 +108,10 @@
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.net.URISyntaxException;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -3585,10 +3585,12 @@
|| applicationInfo.isSignedWithPlatformKey();
}
- public ArrayMap<String, String> getStringsForPrefix(ContentResolver cr, String prefix,
- List<String> names) {
+ private ArrayMap<String, String> getStringsForPrefixStripPrefix(
+ ContentResolver cr, String prefix, String[] names) {
String namespace = prefix.substring(0, prefix.length() - 1);
ArrayMap<String, String> keyValues = new ArrayMap<>();
+ int substringLength = prefix.length();
+
int currentGeneration = -1;
boolean needsGenerationTracker = false;
@@ -3613,10 +3615,13 @@
if (DEBUG) {
Log.i(TAG, "Cache hit for prefix:" + prefix);
}
- if (!names.isEmpty()) {
+ if (names.length > 0) {
for (String name : names) {
- if (mValues.containsKey(name)) {
- keyValues.put(name, mValues.get(name));
+ String value = mValues.get(name);
+ if (value != null) {
+ keyValues.put(
+ name.substring(substringLength),
+ value);
}
}
} else {
@@ -3625,7 +3630,10 @@
// Explicitly exclude the prefix as it is only there to
// signal that the prefix has been cached.
if (key.startsWith(prefix) && !key.equals(prefix)) {
- keyValues.put(key, mValues.get(key));
+ String value = mValues.valueAt(i);
+ keyValues.put(
+ key.substring(substringLength),
+ value);
}
}
}
@@ -3685,14 +3693,22 @@
Map<String, String> flagsToValues =
(HashMap) b.getSerializable(Settings.NameValueTable.VALUE, java.util.HashMap.class);
// Only the flags requested by the caller
- if (!names.isEmpty()) {
- for (Map.Entry<String, String> flag : flagsToValues.entrySet()) {
- if (names.contains(flag.getKey())) {
- keyValues.put(flag.getKey(), flag.getValue());
+ if (names.length > 0) {
+ for (String name : names) {
+ String value = flagsToValues.get(name);
+ if (value != null) {
+ keyValues.put(
+ name.substring(substringLength),
+ value);
}
}
} else {
- keyValues.putAll(flagsToValues);
+ keyValues.ensureCapacity(keyValues.size() + flagsToValues.size());
+ for (Map.Entry<String, String> flag : flagsToValues.entrySet()) {
+ keyValues.put(
+ flag.getKey().substring(substringLength),
+ flag.getValue());
+ }
}
synchronized (NameValueCache.this) {
@@ -7428,6 +7444,20 @@
public static final String DEFAULT_INPUT_METHOD = "default_input_method";
/**
+ * Used only by {@link com.android.server.inputmethod.InputMethodManagerService} as a
+ * temporary data store of {@link #DEFAULT_INPUT_METHOD} while a virtual-device-specific
+ * input method is set as default.</p>
+ *
+ * <p>This should be considered to be an implementation detail of
+ * {@link com.android.server.inputmethod.InputMethodManagerService}. Other system
+ * components should never rely on this value.</p>
+ *
+ * @see #DEFAULT_INPUT_METHOD
+ * @hide
+ */
+ public static final String DEFAULT_DEVICE_INPUT_METHOD = "default_device_input_method";
+
+ /**
* Setting to record the input method subtype used by default, holding the ID
* of the desired method.
*/
@@ -12158,6 +12188,7 @@
CLONE_TO_MANAGED_PROFILE.add(SHOW_IME_WITH_HARD_KEYBOARD);
CLONE_TO_MANAGED_PROFILE.add(ACCESSIBILITY_BOUNCE_KEYS);
CLONE_TO_MANAGED_PROFILE.add(NOTIFICATION_BUBBLES);
+ CLONE_TO_MANAGED_PROFILE.add(NOTIFICATION_HISTORY_ENABLED);
}
/** @hide */
@@ -14347,6 +14378,19 @@
"mute_alarm_stream_with_ringer_mode";
/**
+ * The user's choice for whether or not Alarm stream should always be muted with Ringer.
+ *
+ * <p>Note that this is different from {@link #MUTE_ALARM_STREAM_WITH_RINGER_MODE}, which
+ * controls the real state of whether or not the Alarm stream and Ringer association occurs.
+ * The two Settings are not necessarily equal, if the final decision for the association
+ * depends on factors beyond the user's preference.
+ *
+ * @hide
+ */
+ public static final String MUTE_ALARM_STREAM_WITH_RINGER_MODE_USER_PREFERENCE =
+ "mute_alarm_stream_with_ringer_mode_user_preference";
+
+ /**
* Overlay display devices setting.
* The associated value is a specially formatted string that describes the
* size and density of simulated secondary display devices.
@@ -19647,6 +19691,15 @@
@Readable
public static final String WRIST_DETECTION_AUTO_LOCKING_ENABLED =
"wear_wrist_detection_auto_locking_enabled";
+
+ /**
+ * Whether consistent notification blocking experience is enabled.
+ *
+ * @hide
+ */
+ @Readable
+ public static final String CONSISTENT_NOTIFICATION_BLOCKING_ENABLED =
+ "consistent_notification_blocking_enabled";
}
}
@@ -19807,21 +19860,15 @@
@RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
public static Map<String, String> getStrings(@NonNull ContentResolver resolver,
@NonNull String namespace, @NonNull List<String> names) {
- List<String> compositeNames = new ArrayList<>(names.size());
- for (String name : names) {
- compositeNames.add(createCompositeName(namespace, name));
+ String[] compositeNames = new String[names.size()];
+ for (int i = 0, size = names.size(); i < size; ++i) {
+ compositeNames[i] = createCompositeName(namespace, names.get(i));
}
String prefix = createPrefix(namespace);
- ArrayMap<String, String> rawKeyValues = sNameValueCache.getStringsForPrefix(
+
+ ArrayMap<String, String> keyValues = sNameValueCache.getStringsForPrefixStripPrefix(
resolver, prefix, compositeNames);
- int size = rawKeyValues.size();
- int substringLength = prefix.length();
- ArrayMap<String, String> keyValues = new ArrayMap<>(size);
- for (int i = 0; i < size; ++i) {
- keyValues.put(rawKeyValues.keyAt(i).substring(substringLength),
- rawKeyValues.valueAt(i));
- }
return keyValues;
}
@@ -20147,12 +20194,13 @@
private static String createCompositeName(@NonNull String namespace, @NonNull String name) {
Preconditions.checkNotNull(namespace);
Preconditions.checkNotNull(name);
- return createPrefix(namespace) + name;
+ var sb = new StringBuilder(namespace.length() + 1 + name.length());
+ return sb.append(namespace).append('/').append(name).toString();
}
private static String createPrefix(@NonNull String namespace) {
Preconditions.checkNotNull(namespace);
- return namespace + "/";
+ return namespace + '/';
}
private static Uri createNamespaceUri(@NonNull String namespace) {
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index e4af2da..d47ff2e 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -368,11 +368,13 @@
* <p>
* As of Android 11 apps will need specific permission to query other packages. To use
* this method an app must include in their AndroidManifest:
+ * <pre>{@code
* <queries>
* <intent>
* <action android:name="android.provider.Telephony.SMS_DELIVER"/>
* </intent>
* </queries>
+ * }</pre>
* Which will allow them to query packages which declare intent filters that include
* the {@link android.provider.Telephony.Sms.Intents#SMS_DELIVER_ACTION} intent.
* </p>
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 692dad4..1a2be15 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -1439,12 +1439,12 @@
// to make sure the wallpaper is stopped even after the events
// onSurfaceCreated() and onSurfaceChanged().
if (noConsecutiveVisibilityEvents()) {
- if (DEBUG) Log.v(TAG, "toggling doVisibilityChanged");
- Trace.beginSection("WPMS.Engine.doVisibilityChanged-true");
- doVisibilityChanged(true);
+ if (DEBUG) Log.v(TAG, "toggling onVisibilityChanged");
+ Trace.beginSection("WPMS.Engine.onVisibilityChanged-true");
+ onVisibilityChanged(true);
Trace.endSection();
- Trace.beginSection("WPMS.Engine.doVisibilityChanged-false");
- doVisibilityChanged(false);
+ Trace.beginSection("WPMS.Engine.onVisibilityChanged-false");
+ onVisibilityChanged(false);
Trace.endSection();
} else {
if (DEBUG) {
diff --git a/core/java/android/view/DisplayAddress.java b/core/java/android/view/DisplayAddress.java
index 99e811a..c3c4ab5 100644
--- a/core/java/android/view/DisplayAddress.java
+++ b/core/java/android/view/DisplayAddress.java
@@ -138,6 +138,30 @@
out.writeLong(mPhysicalDisplayId);
}
+ /**
+ * This method is meant to check to see if the ports match
+ * @param a1 Address to compare
+ * @param a2 Address to compare
+ *
+ * @return true if the arguments have the same port, and at least one does not specify
+ * a model.
+ */
+ public static boolean isPortMatch(DisplayAddress a1, DisplayAddress a2) {
+ // Both displays must be of type Physical
+ if (!(a1 instanceof Physical && a2 instanceof Physical)) {
+ return false;
+ }
+ Physical p1 = (Physical) a1;
+ Physical p2 = (Physical) a2;
+
+ // If both addresses specify a model, fallback to a basic match check (which
+ // also checks the port).
+ if (p1.getModel() != null && p2.getModel() != null) {
+ return p1.equals(p2);
+ }
+ return p1.getPort() == p2.getPort();
+ }
+
private Physical(long physicalDisplayId) {
mPhysicalDisplayId = physicalDisplayId;
}
diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java
index 19e6836..9c430cd 100644
--- a/core/java/android/view/InputEventReceiver.java
+++ b/core/java/android/view/InputEventReceiver.java
@@ -54,6 +54,7 @@
InputChannel inputChannel, MessageQueue messageQueue);
private static native void nativeDispose(long receiverPtr);
private static native void nativeFinishInputEvent(long receiverPtr, int seq, boolean handled);
+ private static native boolean nativeProbablyHasInput(long receiverPtr);
private static native void nativeReportTimeline(long receiverPtr, int inputEventId,
long gpuCompletedTime, long presentTime);
private static native boolean nativeConsumeBatchedInputEvents(long receiverPtr,
@@ -92,6 +93,17 @@
}
/**
+ * Checks the receiver for input availability.
+ * May return false negatives.
+ */
+ public boolean probablyHasInput() {
+ if (mReceiverPtr == 0) {
+ return false;
+ }
+ return nativeProbablyHasInput(mReceiverPtr);
+ }
+
+ /**
* Disposes the receiver.
* Must be called on the same Looper thread to which the receiver is attached.
*/
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 75be729..2b99e1e 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -22856,6 +22856,36 @@
}
/**
+ * Determines whether an unprocessed input event is available on the window.
+ *
+ * This is only a performance hint (a.k.a. the Input Hint) and may return false negative
+ * results. Callers should not rely on availability of the input event based on the return
+ * value of this method.
+ *
+ * The Input Hint functionality is experimental, and can be removed in the future OS releases.
+ *
+ * This method only returns nontrivial results on a View that is attached to a Window. Such View
+ * can be acquired using `Activity.getWindow().getDecorView()`, and only after the view
+ * hierarchy is attached (via {@link android.app.Activity#setContentView(android.view.View)}).
+ *
+ * In multi-window mode the View can provide the Input Hint only for the window it is attached
+ * to. Therefore, checking input availability for the whole application would require asking
+ * for the hint from more than one View.
+ *
+ * The initial implementation does not return false positives, but callers should not rely on
+ * it: false positives may occur in future OS releases.
+ *
+ * @hide
+ */
+ public boolean probablyHasInput() {
+ ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl == null) {
+ return false;
+ }
+ return viewRootImpl.probablyHasInput();
+ }
+
+ /**
* Destroys all hardware rendering resources. This method is invoked
* when the system needs to reclaim resources. Upon execution of this
* method, you should free any OpenGL resources created by the view.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 32afe06..8529b4e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -10554,6 +10554,18 @@
}
/**
+ * Checks the input event receiver for input availability.
+ * May return false negatives.
+ * @hide
+ */
+ public boolean probablyHasInput() {
+ if (mInputEventReceiver == null) {
+ return false;
+ }
+ return mInputEventReceiver.probablyHasInput();
+ }
+
+ /**
* Adds a scroll capture callback to this window.
*
* @param callback the callback to add
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index f76822f..b4ac9a2 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -972,10 +972,8 @@
* android:value="false"/>
* </application>
* </pre>
- *
- * @hide
*/
- // TODO(b/274924641): Make this public API.
+ @FlaggedApi(Flags.FLAG_APP_COMPAT_PROPERTIES_API)
String PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED =
"android.window.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED";
@@ -1309,9 +1307,8 @@
* android:value="true|false"/>
* </application>
* </pre>
- * @hide
*/
- // TODO(b/280052089): Make this public API.
+ @FlaggedApi(Flags.FLAG_APP_COMPAT_PROPERTIES_API)
String PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES =
"android.window.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES";
@@ -1497,6 +1494,30 @@
"android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED";
/**
+ * Activity or Application level {@link android.content.pm.PackageManager.Property
+ * PackageManager.Property} for an app to declare that System UI should be shown for this
+ * app/component to allow it to be launched as multiple instances. This property only affects
+ * SystemUI behavior and does _not_ affect whether a component can actually be launched into
+ * multiple instances, which is determined by the Activity's {@code launchMode} or the launching
+ * Intent's flags. If the property is set on the Application, then all components within that
+ * application will use that value unless specified per component.
+ *
+ * The value must be a boolean string.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * <activity>
+ * <property
+ * android:name="android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI"
+ * android:value="true|false"/>
+ * </activity>
+ * </pre>
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI)
+ public static final String PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI =
+ "android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI";
+
+ /**
* Request for app's keyboard shortcuts to be retrieved asynchronously.
*
* @param receiver The callback to be triggered when the result is ready.
@@ -3166,6 +3187,12 @@
public static final int PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS = 1 << 10;
/**
+ * Flag to indicate that the window is forcibly to go edge-to-edge.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED = 1 << 11;
+
+ /**
* Flag to indicate that the window frame should be the requested frame adding the display
* cutout frame. This will only be applied if a specific size smaller than the parent frame
* is given, and the window is covering the display cutout. The extended frame will not be
@@ -3341,6 +3368,7 @@
PRIVATE_FLAG_SYSTEM_ERROR,
PRIVATE_FLAG_OPTIMIZE_MEASURE,
PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
+ PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED,
PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME,
@@ -3403,6 +3431,10 @@
equals = PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
name = "DISABLE_WALLPAPER_TOUCH_EVENTS"),
@ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED,
+ equals = PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED,
+ name = "EDGE_TO_EDGE_ENFORCED"),
+ @ViewDebug.FlagToString(
mask = PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
equals = PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
name = "LAYOUT_SIZE_EXTENDED_BY_CUTOUT"),
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index 0cc19fb..48f8f1b 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -24,6 +24,13 @@
}
flag {
+ namespace: "accessibility"
+ name: "braille_display_hid"
+ description: "Enables new APIs for an AccessibilityService to communicate with a HID Braille display"
+ bug: "303522222"
+}
+
+flag {
name: "cleanup_accessibility_warning_dialog"
namespace: "accessibility"
description: "Cleans up duplicated or broken logic surrounding the accessibility warning dialog."
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index feccc6b..3bc02a6 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -354,7 +354,11 @@
* @hide
*/
public static void ensureDefaultInstanceForDefaultDisplayIfNecessary() {
- forContextInternal(Display.DEFAULT_DISPLAY, Looper.getMainLooper());
+ // Skip this call if we are in system_server, as the system code should not use this
+ // deprecated instance.
+ if (!ActivityThread.isSystem()) {
+ forContextInternal(Display.DEFAULT_DISPLAY, Looper.getMainLooper());
+ }
}
private static final Object sLock = new Object();
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 216acdc..f2bce9c 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -38,6 +38,14 @@
}
flag {
+ name: "draw_magnifier_border_outside_wmlock"
+ namespace: "windowing_frontend"
+ description: "Avoid holding WM locks for a long time when executing lockCanvas"
+ bug: "316075123"
+ is_fixed_read_only: true
+}
+
+flag {
name: "introduce_smoother_dimmer"
namespace: "windowing_frontend"
description: "Refactor dim to fix flickers"
@@ -74,4 +82,12 @@
description: "Enable record activity snapshot by default"
bug: "259497289"
is_fixed_read_only: true
+}
+
+flag {
+ name: "supports_multi_instance_system_ui"
+ namespace: "multitasking"
+ description: "Feature flag to enable a multi-instance system ui component property."
+ bug: "262864589"
+ is_fixed_read_only: true
}
\ No newline at end of file
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index 1bd0982..eeea17b 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -68,10 +68,10 @@
// TODO b/291899544: for released flags, use resource config values
/** Value used by polite notif. feature */
public static final Flag NOTIF_COOLDOWN_T1 = devFlag(
- "persist.debug.sysui.notification.notif_cooldown_t1", 5000);
+ "persist.debug.sysui.notification.notif_cooldown_t1", 60000);
/** Value used by polite notif. feature */
public static final Flag NOTIF_COOLDOWN_T2 = devFlag(
- "persist.debug.sysui.notification.notif_cooldown_t2", 3000);
+ "persist.debug.sysui.notification.notif_cooldown_t2", 5000);
/** Value used by polite notif. feature */
public static final Flag NOTIF_VOLUME1 = devFlag(
"persist.debug.sysui.notification.notif_volume1", 30);
@@ -80,12 +80,6 @@
/** Value used by polite notif. feature. -1 to ignore the counter */
public static final Flag NOTIF_COOLDOWN_COUNTER_RESET = devFlag(
"persist.debug.sysui.notification.notif_cooldown_counter_reset", 10);
- /**
- * Value used by polite notif. feature: cooldown behavior/strategy. Valid values: rule1,
- * rule2
- */
- public static final Flag NOTIF_COOLDOWN_RULE = devFlag(
- "persist.debug.sysui.notification.notif_cooldown_rule", "rule1");
/** b/303716154: For debugging only: use short bitmap duration. */
public static final Flag DEBUG_SHORT_BITMAP_DURATION = devFlag(
diff --git a/core/java/com/android/internal/policy/IKeyguardService.aidl b/core/java/com/android/internal/policy/IKeyguardService.aidl
index 36b7ee5..d62c8f3 100644
--- a/core/java/com/android/internal/policy/IKeyguardService.aidl
+++ b/core/java/com/android/internal/policy/IKeyguardService.aidl
@@ -130,4 +130,10 @@
* Note that it's called only if the device is interactive.
*/
void onSystemKeyPressed(int keycode);
+
+ /**
+ * Requests to show the keyguard immediately without locking the device. Keyguard will show
+ * whether a screen lock was configured or not (including if screen lock is SWIPE or NONE).
+ */
+ void showDismissibleKeyguard();
}
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 31910ac..4e3b64c 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -31,6 +31,7 @@
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
@@ -46,6 +47,7 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources.Theme;
@@ -294,9 +296,9 @@
private int mFrameResource = 0;
private int mTextColor = 0;
- int mStatusBarColor = 0;
- int mNavigationBarColor = 0;
- int mNavigationBarDividerColor = 0;
+ int mStatusBarColor = Color.TRANSPARENT;
+ int mNavigationBarColor = Color.TRANSPARENT;
+ int mNavigationBarDividerColor = Color.TRANSPARENT;
private boolean mForcedStatusBarColor = false;
private boolean mForcedNavigationBarColor = false;
@@ -388,11 +390,9 @@
mProxyOnBackInvokedDispatcher = new ProxyOnBackInvokedDispatcher(context);
mAllowFloatingWindowsFillScreen = context.getResources().getBoolean(
com.android.internal.R.bool.config_allowFloatingWindowsFillScreen);
- mEdgeToEdgeEnforced =
- context.getApplicationInfo().targetSdkVersion >= ENFORCE_EDGE_TO_EDGE_SDK_VERSION
- || (CompatChanges.isChangeEnabled(ENFORCE_EDGE_TO_EDGE)
- && Flags.enforceEdgeToEdge());
+ mEdgeToEdgeEnforced = isEdgeToEdgeEnforced(context.getApplicationInfo(), true /* local */);
if (mEdgeToEdgeEnforced) {
+ getAttributes().privateFlags |= PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
mDecorFitsSystemWindows = false;
}
}
@@ -431,6 +431,22 @@
mActivityConfigCallback = activityConfigCallback;
}
+ /**
+ * Returns whether the given application is enforced to go edge-to-edge.
+ *
+ * @param info The application to query.
+ * @param local Whether this is called from the process of the given application.
+ * @return {@code true} if edge-to-edge is enforced. Otherwise, {@code false}.
+ */
+ public static boolean isEdgeToEdgeEnforced(ApplicationInfo info, boolean local) {
+ return info.targetSdkVersion >= ENFORCE_EDGE_TO_EDGE_SDK_VERSION
+ || (Flags.enforceEdgeToEdge() && (local
+ // Calling this doesn't require a permission.
+ ? CompatChanges.isChangeEnabled(ENFORCE_EDGE_TO_EDGE)
+ // Calling this requires permissions.
+ : info.isChangeEnabled(ENFORCE_EDGE_TO_EDGE)));
+ }
+
@Override
public final void setContainer(Window container) {
super.setContainer(container);
@@ -2548,17 +2564,10 @@
final boolean targetPreL = targetSdk < android.os.Build.VERSION_CODES.LOLLIPOP;
final boolean targetPreQ = targetSdk < Build.VERSION_CODES.Q;
- if (!mForcedStatusBarColor) {
- final int statusBarCompatibleColor = context.getColor(R.color.status_bar_compatible);
- final int statusBarDefaultColor = context.getColor(R.color.status_bar_default);
- final int statusBarColor = a.getColor(R.styleable.Window_statusBarColor,
- statusBarDefaultColor);
-
- mStatusBarColor = statusBarColor == statusBarDefaultColor && !mEdgeToEdgeEnforced
- ? statusBarCompatibleColor
- : statusBarColor;
+ if (!mForcedStatusBarColor && !mEdgeToEdgeEnforced) {
+ mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, Color.BLACK);
}
- if (!mForcedNavigationBarColor) {
+ if (!mForcedNavigationBarColor && !mEdgeToEdgeEnforced) {
final int navBarCompatibleColor = context.getColor(R.color.navigation_bar_compatible);
final int navBarDefaultColor = context.getColor(R.color.navigation_bar_default);
final int navBarColor = a.getColor(R.styleable.Window_navigationBarColor,
@@ -2566,7 +2575,6 @@
mNavigationBarColor =
navBarColor == navBarDefaultColor
- && !mEdgeToEdgeEnforced
&& !context.getResources().getBoolean(
R.bool.config_navBarDefaultTransparent)
? navBarCompatibleColor
@@ -2575,7 +2583,7 @@
mNavigationBarDividerColor = a.getColor(R.styleable.Window_navigationBarDividerColor,
Color.TRANSPARENT);
}
- if (!targetPreQ) {
+ if (!targetPreQ && !mEdgeToEdgeEnforced) {
mEnsureStatusBarContrastWhenTransparent = a.getBoolean(
R.styleable.Window_enforceStatusBarContrast, false);
mEnsureNavigationBarContrastWhenTransparent = a.getBoolean(
@@ -3899,6 +3907,9 @@
@Override
public void setStatusBarColor(int color) {
+ if (mEdgeToEdgeEnforced) {
+ return;
+ }
if (mStatusBarColor == color && mForcedStatusBarColor) {
return;
}
@@ -3920,6 +3931,9 @@
@Override
public void setNavigationBarColor(int color) {
+ if (mEdgeToEdgeEnforced) {
+ return;
+ }
if (mNavigationBarColor == color && mForcedNavigationBarColor) {
return;
}
@@ -3936,6 +3950,9 @@
@Override
public void setNavigationBarDividerColor(int navigationBarDividerColor) {
+ if (mEdgeToEdgeEnforced) {
+ return;
+ }
mNavigationBarDividerColor = navigationBarDividerColor;
if (mDecor != null) {
mDecor.updateColorViews(null, false /* animate */);
@@ -3949,6 +3966,9 @@
@Override
public void setStatusBarContrastEnforced(boolean ensureContrast) {
+ if (mEdgeToEdgeEnforced) {
+ return;
+ }
mEnsureStatusBarContrastWhenTransparent = ensureContrast;
if (mDecor != null) {
mDecor.updateColorViews(null, false /* animate */);
@@ -3962,6 +3982,9 @@
@Override
public void setNavigationBarContrastEnforced(boolean enforceContrast) {
+ if (mEdgeToEdgeEnforced) {
+ return;
+ }
mEnsureNavigationBarContrastWhenTransparent = enforceContrast;
if (mDecor != null) {
mDecor.updateColorViews(null, false /* animate */);
@@ -4031,6 +4054,9 @@
@Override
public void setDecorFitsSystemWindows(boolean decorFitsSystemWindows) {
+ if (mEdgeToEdgeEnforced) {
+ return;
+ }
mDecorFitsSystemWindows = decorFitsSystemWindows;
applyDecorFitsSystemWindows();
}
diff --git a/core/java/com/android/server/OWNERS b/core/java/com/android/server/OWNERS
deleted file mode 100644
index 1c2d19d..0000000
--- a/core/java/com/android/server/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-per-file SystemConfig.java = file:/PACKAGE_MANAGER_OWNERS
diff --git a/core/jni/android_os_VintfObject.cpp b/core/jni/android_os_VintfObject.cpp
index 1baea2a..b651711 100644
--- a/core/jni/android_os_VintfObject.cpp
+++ b/core/jni/android_os_VintfObject.cpp
@@ -46,6 +46,7 @@
using vintf::Version;
using vintf::VintfObject;
using vintf::Vndk;
+using vintf::CheckFlags::ENABLE_ALL_CHECKS;
template<typename V>
static inline jobjectArray toJavaStringArray(JNIEnv* env, const V& v) {
@@ -93,12 +94,13 @@
return toJavaStringArray(env, cStrings);
}
-static jint android_os_VintfObject_verifyWithoutAvb(JNIEnv* env, jclass) {
+static jint android_os_VintfObject_verifyBuildAtBoot(JNIEnv* env, jclass) {
std::string error;
- int32_t status = VintfObject::GetInstance()->checkCompatibility(&error,
- ::android::vintf::CheckFlags::DISABLE_AVB_CHECK);
+ int32_t status =
+ VintfObject::GetInstance()
+ ->checkCompatibility(&error, ENABLE_ALL_CHECKS.disableAvb().disableKernel());
if (status)
- LOG(WARNING) << "VintfObject.verifyWithoutAvb() returns " << status << ": " << error;
+ LOG(WARNING) << "VintfObject.verifyBuildAtBoot() returns " << status << ": " << error;
return status;
}
@@ -170,7 +172,7 @@
static const JNINativeMethod gVintfObjectMethods[] = {
{"report", "()[Ljava/lang/String;", (void*)android_os_VintfObject_report},
- {"verifyWithoutAvb", "()I", (void*)android_os_VintfObject_verifyWithoutAvb},
+ {"verifyBuildAtBoot", "()I", (void*)android_os_VintfObject_verifyBuildAtBoot},
{"getHalNamesAndVersions", "()[Ljava/lang/String;",
(void*)android_os_VintfObject_getHalNamesAndVersions},
{"getSepolicyVersion", "()Ljava/lang/String;",
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 5b68e8e..f7d8152 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -82,6 +82,7 @@
status_t initialize();
void dispose();
status_t finishInputEvent(uint32_t seq, bool handled);
+ bool probablyHasInput();
status_t reportTimeline(int32_t inputEventId, nsecs_t gpuCompletedTime, nsecs_t presentTime);
status_t consumeEvents(JNIEnv* env, bool consumeBatches, nsecs_t frameTime,
bool* outConsumedBatch);
@@ -165,6 +166,10 @@
return processOutboundEvents();
}
+bool NativeInputEventReceiver::probablyHasInput() {
+ return mInputConsumer.probablyHasInput();
+}
+
status_t NativeInputEventReceiver::reportTimeline(int32_t inputEventId, nsecs_t gpuCompletedTime,
nsecs_t presentTime) {
if (kDebugDispatchCycle) {
@@ -547,6 +552,12 @@
}
}
+static bool nativeProbablyHasInput(JNIEnv* env, jclass clazz, jlong receiverPtr) {
+ sp<NativeInputEventReceiver> receiver =
+ reinterpret_cast<NativeInputEventReceiver*>(receiverPtr);
+ return receiver->probablyHasInput();
+}
+
static void nativeReportTimeline(JNIEnv* env, jclass clazz, jlong receiverPtr, jint inputEventId,
jlong gpuCompletedTime, jlong presentTime) {
if (IdGenerator::getSource(inputEventId) != IdGenerator::Source::INPUT_READER) {
@@ -597,6 +608,7 @@
(void*)nativeInit},
{"nativeDispose", "(J)V", (void*)nativeDispose},
{"nativeFinishInputEvent", "(JIZ)V", (void*)nativeFinishInputEvent},
+ {"nativeProbablyHasInput", "(J)Z", (void*)nativeProbablyHasInput},
{"nativeReportTimeline", "(JIJJ)V", (void*)nativeReportTimeline},
{"nativeConsumeBatchedInputEvents", "(JJ)Z", (void*)nativeConsumeBatchedInputEvents},
{"nativeDump", "(JLjava/lang/String;)Ljava/lang/String;", (void*)nativeDump},
diff --git a/core/jni/hwbinder/EphemeralStorage.cpp b/core/jni/hwbinder/EphemeralStorage.cpp
index 95bb42e..ef0750c 100644
--- a/core/jni/hwbinder/EphemeralStorage.cpp
+++ b/core/jni/hwbinder/EphemeralStorage.cpp
@@ -164,7 +164,7 @@
}
default:
- CHECK(!"Should not be here");
+ CHECK(!"Should not be here") << "Item type: " << item.mType;
}
}
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 2a0feee..52e0124 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -395,6 +395,8 @@
optional bool should_refresh_activity_for_camera_compat = 40;
optional bool should_refresh_activity_via_pause_for_camera_compat = 41;
optional bool should_override_min_aspect_ratio = 42;
+ optional bool should_ignore_orientation_request_loop = 43;
+ optional bool should_override_force_resize_app = 44;
}
/* represents WindowToken */
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e65bfab..ef6caef 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1709,6 +1709,7 @@
<!-- @SystemApi Allows camera access by Headless System User 0 when device is running in
HSUM Mode.
+ @FlaggedApi("com.android.internal.camera.flags.camera_hsum_permission")
@hide -->
<permission android:name="android.permission.CAMERA_HEADLESS_SYSTEM_USER"
android:permissionGroup="android.permission-group.UNDEFINED"
@@ -7146,6 +7147,16 @@
android:label="@string/permlab_foregroundServiceFileManagement"
android:protectionLevel="normal|instant" />
+ <!-- @FlaggedApi("android.content.pm.introduce_media_processing_type")
+ Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "mediaProcessing".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROCESSING"
+ android:description="@string/permdesc_foregroundServiceMediaProcessing"
+ android:label="@string/permlab_foregroundServiceMediaProcessing"
+ android:protectionLevel="normal|instant" />
+
<!-- Allows a regular application to use {@link android.app.Service#startForeground
Service.startForeground} with the type "specialUse".
<p>Protection level: normal|appop|instant
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 70f0c93..0cc49a5 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -2316,7 +2316,7 @@
<string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7089318886628390827">"Ø¥Ø²Ø§ÙØ© Ø§ÙØØžØ±"</string>
<string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Ø§ÙØ®ØµÙØµÙØ© ÙÙ Ø¬ÙØ§Ø² Ø§ÙØ§Ø³ØªØŽØ¹Ø§Ø±"</string>
<string name="splash_screen_view_icon_description" msgid="180638751260598187">"رÙ
ز Ø§ÙØªØ·ØšÙÙ"</string>
- <string name="splash_screen_view_branding_description" msgid="7911129347402728216">"Ø§ÙØµÙرة Ø§ÙØ°ÙÙÙØ© ÙÙØ¹ÙاÙ
Ø© Ø§ÙØªØ¬Ø§Ø±ÙØ© ÙÙØªØ·ØšÙÙ"</string>
+ <string name="splash_screen_view_branding_description" msgid="7911129347402728216">"ÙÙÙØ© Ø§ÙØ¹ÙاÙ
Ø© Ø§ÙØªØ¬Ø§Ø±ÙØ© ÙÙØªØ·ØšÙÙ"</string>
<string name="view_and_control_notification_title" msgid="4300765399209912240">"Ø§ÙØªØÙÙÙ Ù
٠إعدادات اÙÙØµÙÙ"</string>
<string name="view_and_control_notification_content" msgid="8003766498562604034">"ÙÙ
ÙÙ ÙØ®Ø¯Ù
Ø© <xliff:g id="SERVICE_NAME">%s</xliff:g> Ø§ÙØ§Ø·ÙÙØ§Ø¹ عÙÙ ØŽØ§ØŽØªÙ ÙØ§ÙتØÙÙÙ
ÙÙÙØ§. اÙÙØ± ÙÙ
راجعة Ø§ÙØ¥Ø¹Ø¯Ø§Ø¯Ø§Øª."</string>
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> (Ù
ÙØªØ±Ø¬ÙÙ
)."</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index a4b01e8..ec14677 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -2340,9 +2340,9 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permet que una aplicació complementària iniciï serveis en primer pla des d\'un segon pla."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"El micròfon està disponible"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"El micròfon està bloquejat"</string>
- <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"No es pot projectar a la pantalla"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"No es pot duplicar a la pantalla"</string>
<string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Utilitza un altre cable i torna-ho a provar"</string>
- <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"El dispositiu està massa calent i no pot projectar a la pantalla fins que es refredi"</string>
+ <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"El dispositiu està massa calent i no pot duplicar a la pantalla fins que es refredi"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Pantalla dual"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"La pantalla dual està activada"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> està utilitzant les dues pantalles per mostrar contingut"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 7119131..d3f8550 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -2050,7 +2050,7 @@
<string name="autofill_save_type_password" msgid="5624528786144539944">"adgangskode"</string>
<string name="autofill_save_type_address" msgid="3111006395818252885">"adresse"</string>
<string name="autofill_save_type_credit_card" msgid="3583795235862046693">"kreditkort"</string>
- <string name="autofill_save_type_debit_card" msgid="3169397504133097468">"betalingskort"</string>
+ <string name="autofill_save_type_debit_card" msgid="3169397504133097468">"debetkort"</string>
<string name="autofill_save_type_payment_card" msgid="6555012156728690856">"betalingskort"</string>
<string name="autofill_save_type_generic_card" msgid="1019367283921448608">"kort"</string>
<string name="autofill_save_type_username" msgid="1018816929884640882">"brugernavn"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index c0e12c3..514d695 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -355,7 +355,7 @@
<string name="permlab_fullScreenIntent" msgid="4310888199502509104">"Benachrichtigungen auf einem gesperrten Gerät als Vollbildaktivitäten anzeigen"</string>
<string name="permdesc_fullScreenIntent" msgid="1100721419406643997">"Ermöglicht der App, Benachrichtigungen auf einem gesperrten Gerät als Vollbildaktivitäten anzuzeigen"</string>
<string name="permlab_install_shortcut" msgid="7451554307502256221">"Verknüpfungen installieren"</string>
- <string name="permdesc_install_shortcut" msgid="4476328467240212503">"ohne Zutun des Nutzers Verknüpfungen zum Startbildschirm hinzufügen."</string>
+ <string name="permdesc_install_shortcut" msgid="4476328467240212503">"Ermöglicht einer App, dem Startbildschirm ohne Zutun des Nutzers Verknüpfungen hinzuzufügen."</string>
<string name="permlab_uninstall_shortcut" msgid="295263654781900390">"Verknüpfungen deinstallieren"</string>
<string name="permdesc_uninstall_shortcut" msgid="1924735350988629188">"Ermöglicht einer App das Entfernen von Verknüpfungen vom Startbildschirm ohne Eingriff des Nutzers"</string>
<string name="permlab_processOutgoingCalls" msgid="4075056020714266558">"Ausgehende Anrufe umleiten"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index cf18eb9..2b9c555 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -2331,7 +2331,7 @@
<string name="default_card_name" msgid="9198284935962911468">"<xliff:g id="CARDNUMBER">%d</xliff:g> TXARTELA"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Aplikazio osagarrien erloju-profilaren baimena erlojuak kudeatzeko"</string>
<string name="permdesc_companionProfileWatch" msgid="5655698581110449397">"Erlojuak kudeatzeko baimena ematen die aplikazio osagarriei."</string>
- <string name="permlab_observeCompanionDevicePresence" msgid="9008994909653990465">"Begiratu gailu osagarrien presentzia"</string>
+ <string name="permlab_observeCompanionDevicePresence" msgid="9008994909653990465">"Begiratu gailu osagarrien presentziari"</string>
<string name="permdesc_observeCompanionDevicePresence" msgid="3011699826788697852">"Gailu osagarrien presentzia begiratzeko baimena ematen die aplikazio osagarriei gailuak inguruan edo urrun daudenean."</string>
<string name="permlab_deliverCompanionMessages" msgid="3931552294842980887">"Entregatu aplikazio osagarrien mezuak"</string>
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Beste gailuetan mezuak entregatzeko baimena ematen die aplikazio osagarriei."</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index d435c62..3b24230 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -1724,7 +1724,7 @@
<string name="color_inversion_feature_name" msgid="2672824491933264951">"Inversion des couleurs"</string>
<string name="color_correction_feature_name" msgid="7975133554160979214">"Correction des couleurs"</string>
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Mode une main"</string>
- <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Encore moins lumineux"</string>
+ <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Luminosité ultra-réduite"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Appareils auditifs"</string>
<string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"Touches de volume appuyées de manière prolongée. Service <xliff:g id="SERVICE_NAME">%1$s</xliff:g> activé."</string>
<string name="accessibility_shortcut_disabling_service" msgid="8675244165062700619">"Touches de volume appuyées de manière prolongée. Service <xliff:g id="SERVICE_NAME">%1$s</xliff:g> désactivé."</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 2ff302d..9a063fb 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -575,7 +575,7 @@
<string name="permdesc_changeWimaxState" product="tablet" msgid="4011097664859480108">"Inaruhusu programu kuunganisha kompyuta kibao, na kukata kompyuta kibao kutoka mitandao ya WiMAX."</string>
<string name="permdesc_changeWimaxState" product="tv" msgid="5373274458799425276">"Huruhusu programu iunganishe na kutenganisha kifaa chako cha Android TV na mitandao ya WiMAX."</string>
<string name="permdesc_changeWimaxState" product="default" msgid="1551666203780202101">"Inaruhusu programu kuunganisha simu kwenye, na kukata simu kutoka mitandao ya WiMAX."</string>
- <string name="permlab_bluetooth" msgid="586333280736937209">"oanisha na vifaa vya Bluetooth"</string>
+ <string name="permlab_bluetooth" msgid="586333280736937209">"unganisha na vifaa vya Bluetooth"</string>
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Huruhusu programu kuona usanidi wa Bluetooth kwenye kompyuta kibao, na kutuma na kukubali miunganisho kwa vifaa vilivyooanishwa."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Huruhusu programu iangalie mipangilio iliyowekwa ya Bluetooth kwenye kifaa chako cha Android TV na kufanya na kukubali miunganisho na vifaa vilivyooanishwa."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Huruhusu programu kuona usanidi wa Bluetooth kwenye simu, na kutuma na kukubali miunganisho kwa vifaa vilivyooanishwa."</string>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index d1143c4..8fae6db 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -301,9 +301,7 @@
granted to the system companion device manager service -->
<flag name="companion" value="0x800000" />
<!-- Additional flag from base permission type: this permission will be granted to the
- retail demo app, as defined by the OEM.
- This flag has been replaced by the retail demo role and is a no-op since Android V.
- -->
+ retail demo app, as defined by the OEM. -->
<flag name="retailDemo" value="0x1000000" />
<!-- Additional flag from base permission type: this permission will be granted to the
recents app. -->
@@ -1746,6 +1744,12 @@
TODO: b/258855262 mark this field as {@code hide} once this bug is fixed.
<flag name="fileManagement" value="0x1000" />
-->
+ <!-- Media processing use cases such as video or photo editing and processing.
+ <p>Requires the app to hold the permission
+ {@link android.Manifest.permission#FOREGROUND_SERVICE_MEDIA_PROCESSING} in order to use
+ this type.
+ -->
+ <flag name="mediaProcessing" value="0x2000" />
<!-- Use cases that can't be categorized into any other foreground service types, but also
can't use @link android.app.job.JobInfo.Builder} APIs.
See {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE} for the
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index eddd81e..53a6270 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -568,10 +568,6 @@
<color name="side_fps_button_color">#00677E</color>
<!-- Color for system bars -->
- <color name="status_bar_compatible">@android:color/black</color>
- <!-- This uses non-regular transparent intentionally. It is used to tell if the transparent
- color is set by the framework or not. -->
- <color name="status_bar_default">#00808080</color>
<color name="navigation_bar_compatible">@android:color/black</color>
<!-- This uses non-regular transparent intentionally. It is used to tell if the transparent
color is set by the framework or not. -->
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 8d80af4..5346454 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -212,6 +212,36 @@
<bool name="config_send_satellite_datagram_to_modem_in_demo_mode">false</bool>
<java-symbol type="bool" name="config_send_satellite_datagram_to_modem_in_demo_mode" />
+ <!-- List of country codes where oem-enabled satellite services are either allowed or disallowed
+ by the device. Each country code is a lowercase 2 character ISO-3166-1 alpha-2.
+ -->
+ <string-array name="config_oem_enabled_satellite_country_codes">
+ </string-array>
+ <java-symbol type="array" name="config_oem_enabled_satellite_country_codes" />
+
+ <!-- The file storing S2-cell-based satellite access restriction of the countries defined by
+ config_oem_enabled_satellite_countries. -->
+ <string name="config_oem_enabled_satellite_s2cell_file"></string>
+ <java-symbol type="string" name="config_oem_enabled_satellite_s2cell_file" />
+
+ <!-- Whether to treat the countries defined by the config_oem_enabled_satellite_countries
+ as satellite-allowed areas. The default true value means the countries defined by
+ config_oem_enabled_satellite_countries will be treated as satellite-allowed areas.
+ -->
+ <bool name="config_oem_enabled_satellite_access_allow">true</bool>
+ <java-symbol type="bool" name="config_oem_enabled_satellite_access_allow" />
+
+ <!-- The time duration in seconds which is used to decide whether the Location returned from
+ LocationManager#getLastKnownLocation is fresh.
+
+ The Location is considered fresh if the duration from the Location's elapsed real time to
+ the current elapsed real time is less than this config. If the Location is considered
+ fresh, it will be used as the current location by Telephony to decide whether satellite
+ services should be allowed.
+ -->
+ <integer name="config_oem_enabled_satellite_location_fresh_duration">600</integer>
+ <java-symbol type="integer" name="config_oem_enabled_satellite_location_fresh_duration" />
+
<!-- Whether enhanced IWLAN handover check is enabled. If enabled, telephony frameworks
will not perform handover if the target transport is out of service, or VoPS not
supported. The network will be torn down on the source transport, and will be
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 540967d..17bb86a 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -128,7 +128,7 @@
</staging-public-group>
<staging-public-group type="string" first-id="0x01ba0000">
- <!-- @hide @SystemApi -->
+ <!-- @hide @SystemApi @FlaggedApi("android.permission.flags.retail_demo_role_enabled") -->
<public name="config_defaultRetailDemo" />
</staging-public-group>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d2fb9e1..542e9d6 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1248,6 +1248,11 @@
<string name="permdesc_foregroundServiceFileManagement">Allows the app to make use of foreground services with the type \"fileManagement\"</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_foregroundServiceMediaProcessing">run foreground service with the type \"mediaProcessing\"</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_foregroundServiceMediaProcessing">Allows the app to make use of foreground services with the type \"mediaProcessing\"</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_foregroundServiceSpecialUse">run foreground service with the type \"specialUse\"</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_foregroundServiceSpecialUse">Allows the app to make use of foreground services with the type \"specialUse\"</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b0a4c16..d12ef2b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3095,8 +3095,6 @@
<java-symbol type="bool" name="config_navBarDefaultTransparent" />
<java-symbol type="color" name="navigation_bar_default"/>
<java-symbol type="color" name="navigation_bar_compatible"/>
- <java-symbol type="color" name="status_bar_default"/>
- <java-symbol type="color" name="status_bar_compatible"/>
<!-- EditText suggestion popup. -->
<java-symbol type="id" name="suggestionWindowContainer" />
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index d5d67ab..bdbf96b 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -190,7 +190,7 @@
<item name="windowTranslucentStatus">false</item>
<item name="windowTranslucentNavigation">false</item>
<item name="windowDrawsSystemBarBackgrounds">false</item>
- <item name="statusBarColor">@color/status_bar_default</item>
+ <item name="statusBarColor">@color/black</item>
<item name="navigationBarColor">@color/navigation_bar_default</item>
<item name="windowActionBarFullscreenDecorLayout">@layout/screen_action_bar</item>
<item name="windowContentTransitions">false</item>
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index 723c081..a796a0f 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -171,7 +171,8 @@
@Test
public void testRecycleStartActivityItem() {
- testRecycle(() -> StartActivityItem.obtain(mActivityToken, ActivityOptions.makeBasic()));
+ testRecycle(() -> StartActivityItem.obtain(mActivityToken,
+ new ActivityOptions.SceneTransitionInfo()));
}
@Test
diff --git a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
index c0e2a49..3823033 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
@@ -255,9 +255,9 @@
return LaunchActivityItem.obtain(mActivityToken, mIntent, mIdent, mInfo,
mCurConfig, mOverrideConfig, mDeviceId, mReferrer, mVoiceInteractor,
mProcState, mState, mPersistentState, mPendingResults, mPendingNewIntents,
- mActivityOptions, mIsForward, mProfilerInfo, mAssistToken,
- null /* activityClientController */, mShareableActivityToken,
- mLaunchedFromBubble, mTaskFragmentToken);
+ mActivityOptions != null ? mActivityOptions.getSceneTransitionInfo() : null,
+ mIsForward, mProfilerInfo, mAssistToken, null /* activityClientController */,
+ mShareableActivityToken, mLaunchedFromBubble, mTaskFragmentToken);
}
}
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 07921bf..952cdd9 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -262,7 +262,7 @@
public void testStart() {
// Write to parcel
StartActivityItem item = StartActivityItem.obtain(mActivityToken,
- ActivityOptions.makeBasic());
+ new ActivityOptions.SceneTransitionInfo());
writeAndPrepareForReading(item);
// Read from parcel and assert
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index b9efe65..a1ea2b8 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -134,6 +134,7 @@
<permission name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
<permission name="android.permission.DUMP"/>
<permission name="android.permission.INTERACT_ACROSS_USERS"/>
+ <permission name="android.permission.LOCATION_BYPASS"/>
<permission name="android.permission.LOCAL_MAC_ADDRESS"/>
<permission name="android.permission.MANAGE_USERS"/>
<permission name="android.permission.MANAGE_SUBSCRIPTION_PLANS" />
@@ -149,6 +150,7 @@
<permission name="android.permission.REGISTER_CALL_PROVIDER"/>
<permission name="android.permission.REGISTER_SIM_SUBSCRIPTION"/>
<permission name="android.permission.REGISTER_STATS_PULL_ATOM"/>
+ <permission name="android.permission.SATELLITE_COMMUNICATION"/>
<permission name="android.permission.SEND_RESPOND_VIA_MESSAGE"/>
<permission name="android.permission.SHUTDOWN"/>
<permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/>
diff --git a/data/keyboards/Android.bp b/data/keyboards/Android.bp
new file mode 100644
index 0000000..f15c153
--- /dev/null
+++ b/data/keyboards/Android.bp
@@ -0,0 +1,29 @@
+// Copyright 2010 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.
+
+genrule {
+ name: "validate_framework_keymaps",
+ srcs: [
+ "*.kl",
+ "*.kcm",
+ "*.idc",
+ ],
+ tools: ["validatekeymaps"],
+ out: ["stamp"],
+ cmd: "$(location validatekeymaps) -q $(in) " +
+ "&& touch $(out)",
+ dist: {
+ targets: ["droidcore"],
+ },
+}
diff --git a/data/keyboards/Android.mk b/data/keyboards/Android.mk
deleted file mode 100644
index 6ae8800..0000000
--- a/data/keyboards/Android.mk
+++ /dev/null
@@ -1,44 +0,0 @@
-# Copyright (C) 2010 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.
-
-# This makefile performs build time validation of framework keymap files.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(LOCAL_PATH)/common.mk
-
-# Validate all key maps.
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := validate_framework_keymaps
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE
-intermediates := $(call intermediates-dir-for,ETC,$(LOCAL_MODULE),,COMMON)
-LOCAL_BUILT_MODULE := $(intermediates)/stamp
-
-validatekeymaps := $(HOST_OUT_EXECUTABLES)/validatekeymaps$(HOST_EXECUTABLE_SUFFIX)
-$(LOCAL_BUILT_MODULE): PRIVATE_VALIDATEKEYMAPS := $(validatekeymaps)
-$(LOCAL_BUILT_MODULE) : $(framework_keylayouts) $(framework_keycharmaps) $(framework_keyconfigs) | $(validatekeymaps)
- $(hide) $(PRIVATE_VALIDATEKEYMAPS) -q $^
- $(hide) mkdir -p $(dir $@) && touch $@
-
-# Run validatekeymaps uncondionally for platform build.
-droidcore : $(LOCAL_BUILT_MODULE)
-
-# Reset temp vars.
-validatekeymaps :=
-framework_keylayouts :=
-framework_keycharmaps :=
-framework_keyconfigs :=
diff --git a/data/keyboards/Vendor_0957_Product_0031.kl b/data/keyboards/Vendor_0957_Product_0031.kl
new file mode 100644
index 0000000..b47ee58
--- /dev/null
+++ b/data/keyboards/Vendor_0957_Product_0031.kl
@@ -0,0 +1,82 @@
+# Copyright 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.
+#
+# Key Layout file for Google Reference RCU Remote with customizable button.
+#
+
+key 116 TV_POWER WAKE
+key 217 ASSIST WAKE
+key 423 MACRO_1 WAKE
+
+key 103 DPAD_UP
+key 108 DPAD_DOWN
+key 105 DPAD_LEFT
+key 106 DPAD_RIGHT
+key 353 DPAD_CENTER
+
+key 158 BACK
+key 172 HOME WAKE
+
+key 113 VOLUME_MUTE
+key 114 VOLUME_DOWN
+key 115 VOLUME_UP
+
+key 2 1
+key 3 2
+key 4 3
+key 5 4
+key 6 5
+key 7 6
+key 8 7
+key 9 8
+key 10 9
+key 11 0
+
+# custom keys
+key usage 0x000c01BB TV_INPUT
+
+key usage 0x000c0185 TV_TELETEXT
+key usage 0x000c0061 CAPTIONS
+
+key usage 0x000c01BD INFO
+key usage 0x000c0037 PERIOD
+
+key usage 0x000c0069 PROG_RED
+key usage 0x000c006A PROG_GREEN
+key usage 0x000c006C PROG_YELLOW
+key usage 0x000c006B PROG_BLUE
+key usage 0x000c00B4 MEDIA_SKIP_BACKWARD
+key usage 0x000c00CD MEDIA_PLAY_PAUSE
+key usage 0x000c00B2 MEDIA_RECORD
+key usage 0x000c00B3 MEDIA_SKIP_FORWARD
+
+key usage 0x000c022A BOOKMARK
+key usage 0x000c01A2 ALL_APPS
+key usage 0x000c019C PROFILE_SWITCH
+
+key usage 0x000c0096 SETTINGS
+key usage 0x000c009F NOTIFICATION
+
+key usage 0x000c008D GUIDE
+key usage 0x000c0089 TV
+
+key usage 0x000c0187 FEATURED_APP_1 WAKE #FreeTv
+
+key usage 0x000c009C CHANNEL_UP
+key usage 0x000c009D CHANNEL_DOWN
+
+key usage 0x000c0077 BUTTON_3 WAKE #YouTube
+key usage 0x000c0078 BUTTON_4 WAKE #Netflix
+key usage 0x000c0079 BUTTON_6 WAKE
+key usage 0x000c007A BUTTON_7 WAKE
\ No newline at end of file
diff --git a/data/keyboards/common.mk b/data/keyboards/common.mk
deleted file mode 100644
index d75b691..0000000
--- a/data/keyboards/common.mk
+++ /dev/null
@@ -1,22 +0,0 @@
-# Copyright (C) 2010 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.
-
-# This is the list of framework provided keylayouts and key character maps to include.
-# Used by Android.mk and keyboards.mk.
-
-framework_keylayouts := $(wildcard $(LOCAL_PATH)/*.kl)
-
-framework_keycharmaps := $(wildcard $(LOCAL_PATH)/*.kcm)
-
-framework_keyconfigs := $(wildcard $(LOCAL_PATH)/*.idc)
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index 592f9a5..80afb16d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -382,9 +382,13 @@
if (splitAttributes == null) {
return TaskFragmentAnimationParams.DEFAULT;
}
- return new TaskFragmentAnimationParams.Builder()
- // TODO(b/263047900): Update extensions API.
- // .setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor())
- .build();
+ final AnimationBackground animationBackground = splitAttributes.getAnimationBackground();
+ if (animationBackground instanceof AnimationBackground.ColorBackground colorBackground) {
+ return new TaskFragmentAnimationParams.Builder()
+ .setAnimationBackgroundColor(colorBackground.getColor())
+ .build();
+ } else {
+ return TaskFragmentAnimationParams.DEFAULT;
+ }
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 65597de..066f38b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -819,14 +819,20 @@
Log.e(TAG, "onTaskFragmentParentInfoChanged on empty Task id=" + taskId);
return;
}
+ // Checks if container should be updated before apply new parentInfo.
+ final boolean shouldUpdateContainer = taskContainer.shouldUpdateContainer(parentInfo);
taskContainer.updateTaskFragmentParentInfo(parentInfo);
if (!taskContainer.isVisible()) {
// Don't update containers if the task is not visible. We only update containers when
// parentInfo#isVisibleRequested is true.
return;
}
- if (isInPictureInPicture(parentInfo.getConfiguration())) {
- // No need to update presentation in PIP until the Task exit PIP.
+
+ // If the last direct activity of the host task is dismissed and the overlay container is
+ // the only taskFragment, the overlay container should also be dismissed.
+ dismissOverlayContainerIfNeeded(wct, taskContainer);
+
+ if (!shouldUpdateContainer) {
return;
}
updateContainersInTask(wct, taskContainer);
@@ -1947,11 +1953,8 @@
void updateOverlayContainer(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container) {
final TaskContainer taskContainer = container.getTaskContainer();
- // Dismiss the overlay container if it's the only container in the task and there's no
- // direct activity in the parent task.
- if (taskContainer.getTaskFragmentContainers().size() == 1
- && !taskContainer.hasDirectActivity()) {
- container.finish(false /* shouldFinishDependent */, mPresenter, wct, this);
+
+ if (dismissOverlayContainerIfNeeded(wct, taskContainer)) {
return;
}
@@ -1968,6 +1971,24 @@
}
}
+ /** Dismisses the overlay container in the {@code taskContainer} if needed. */
+ @GuardedBy("mLock")
+ private boolean dismissOverlayContainerIfNeeded(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskContainer taskContainer) {
+ final TaskFragmentContainer overlayContainer = taskContainer.getOverlayContainer();
+ if (overlayContainer == null) {
+ return false;
+ }
+ // Dismiss the overlay container if it's the only container in the task and there's no
+ // direct activity in the parent task.
+ if (taskContainer.getTaskFragmentContainers().size() == 1
+ && !taskContainer.hasDirectActivity()) {
+ mPresenter.cleanupContainer(wct, overlayContainer, false /* shouldFinishDependant */);
+ return true;
+ }
+ return false;
+ }
+
/**
* Updates {@link SplitContainer} with the given {@link SplitAttributes} if the
* {@link SplitContainer} is the top most and not finished. If passed {@link SplitAttributes}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 6f356fa..8b7fd10 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -893,8 +893,7 @@
return new SplitAttributes.Builder()
.setSplitType(splitTypeToUpdate)
.setLayoutDirection(splitAttributes.getLayoutDirection())
- // TODO(b/263047900): Update extensions API.
- // .setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor())
+ .setAnimationBackground(splitAttributes.getAnimationBackground())
.build();
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 64ad4fa..71195b6 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -138,6 +138,21 @@
}
/**
+ * Returns {@code true} if the container should be updated with {@code info}.
+ */
+ boolean shouldUpdateContainer(@NonNull TaskFragmentParentInfo info) {
+ final Configuration configuration = info.getConfiguration();
+
+ return info.isVisible()
+ // No need to update presentation in PIP until the Task exit PIP.
+ && !isInPictureInPicture(configuration)
+ // If the task properties equals regardless of starting position, don't need to
+ // update the container.
+ && (mConfiguration.diffPublicOnly(configuration) != 0
+ || mDisplayId != info.getDisplayId());
+ }
+
+ /**
* Returns the windowing mode for the TaskFragments below this Task, which should be split with
* other TaskFragments.
*
@@ -161,7 +176,11 @@
}
boolean isInPictureInPicture() {
- return getWindowingMode() == WINDOWING_MODE_PINNED;
+ return isInPictureInPicture(mConfiguration);
+ }
+
+ private static boolean isInPictureInPicture(@NonNull Configuration configuration) {
+ return configuration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_PINNED;
}
boolean isInMultiWindow() {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index b52971a..6fe8e50 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -616,6 +616,9 @@
* Removes all activities that belong to this process and finishes other containers/activities
* configured to finish together.
*/
+ // Suppress GuardedBy warning because lint ask to mark this method as
+ // @GuardedBy(container.mController.mLock), which is mLock itself
+ @SuppressWarnings("GuardedBy")
@GuardedBy("mController.mLock")
void finish(boolean shouldFinishDependent, @NonNull SplitPresenter presenter,
@NonNull WindowContainerTransaction wct, @NonNull SplitController controller) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java
index 396956e..6624c70 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java
@@ -77,9 +77,11 @@
@NonNull
TransactionRecord startNewTransaction(@Nullable IBinder taskFragmentTransactionToken) {
if (mCurrentTransaction != null) {
+ final TransactionRecord lastTransaction = mCurrentTransaction;
mCurrentTransaction = null;
throw new IllegalStateException(
- "The previous transaction has not been applied or aborted,");
+ "The previous transaction:" + lastTransaction + " has not been applied or "
+ + "aborted.");
}
mCurrentTransaction = new TransactionRecord(taskFragmentTransactionToken);
return mCurrentTransaction;
@@ -199,5 +201,15 @@
? mOriginType
: TASK_FRAGMENT_TRANSIT_CHANGE;
}
+
+ @Override
+ @NonNull
+ public String toString() {
+ return TransactionRecord.class.getSimpleName() + "{"
+ + "token=" + mTaskFragmentTransactionToken
+ + ", type=" + getTransactionTransitionType()
+ + ", transaction=" + mTransaction
+ + "}";
+ }
}
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
index 60beb0b..f471af0 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
@@ -25,6 +25,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import androidx.window.extensions.embedding.AnimationBackground;
import androidx.window.extensions.embedding.SplitAttributes;
import org.junit.Before;
@@ -70,7 +71,7 @@
.isEqualTo(SplitAttributes.LayoutDirection.LOCALE);
assertThat(splitAttributes.getSplitType())
.isEqualTo(new SplitAttributes.SplitType.RatioSplitType(0.5f));
- // TODO(b/263047900): Update extensions API.
- // assertThat(splitAttributes.getAnimationBackgroundColor()).isEqualTo(0);
+ assertThat(splitAttributes.getAnimationBackground())
+ .isEqualTo(AnimationBackground.ANIMATION_BACKGROUND_DEFAULT);
}
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
index 5ef6a52..bc92101 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -472,6 +472,29 @@
verify(mSplitPresenter).applyActivityStackAttributes(any(), eq(container), eq(attrs));
}
+ @Test
+ public void testOnTaskFragmentParentInfoChanged_positionOnlyChange_earlyReturn() {
+ final TaskFragmentContainer overlayContainer = createTestOverlayContainer(TASK_ID, "test");
+
+ final TaskContainer taskContainer = overlayContainer.getTaskContainer();
+ spyOn(taskContainer);
+ final TaskContainer.TaskProperties taskProperties = taskContainer.getTaskProperties();
+ final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo(
+ new Configuration(taskProperties.getConfiguration()), taskProperties.getDisplayId(),
+ true /* visible */, false /* hasDirectActivity */, null /* decorSurface */);
+ parentInfo.getConfiguration().windowConfiguration.getBounds().offset(10, 10);
+
+ mSplitController.onTaskFragmentParentInfoChanged(mTransaction, TASK_ID, parentInfo);
+
+ // The parent info must be applied to the task container
+ verify(taskContainer).updateTaskFragmentParentInfo(parentInfo);
+ verify(mSplitController, never()).updateContainer(any(), any());
+
+ assertWithMessage("The overlay container must still be dismissed even if "
+ + "#updateContainer is not called")
+ .that(taskContainer.getOverlayContainer()).isNull();
+ }
+
/**
* A simplified version of {@link SplitController.ActivityStartMonitor
* #createOrUpdateOverlayTaskFragmentIfNeeded}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index ac75c73..06210ff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -20,6 +20,7 @@
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
+import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationSpec.createShowSnapshotForClosingAnimation;
import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition;
@@ -330,6 +331,9 @@
if (!animation.hasExtension()) {
continue;
}
+ if (adapter.mChange.hasFlags(FLAG_TRANSLUCENT)) {
+ continue;
+ }
final TransitionInfo.Change change = adapter.mChange;
if (TransitionUtil.isOpeningType(adapter.mChange.getMode())) {
// Need to screenshot after startTransaction is applied otherwise activity
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
index 6213f62..8f04f12 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
@@ -63,6 +63,10 @@
if ((touchX < mStartThresholdX && mSwipeEdge == BackEvent.EDGE_LEFT)
|| (touchX > mStartThresholdX && mSwipeEdge == BackEvent.EDGE_RIGHT)) {
mStartThresholdX = touchX;
+ if ((mSwipeEdge == BackEvent.EDGE_LEFT && mStartThresholdX < mInitTouchX)
+ || (mSwipeEdge == BackEvent.EDGE_RIGHT && mStartThresholdX > mInitTouchX)) {
+ mInitTouchX = mStartThresholdX;
+ }
}
mLatestTouchX = touchX;
mLatestTouchY = touchY;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
index 0693543..662f325 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
@@ -16,7 +16,7 @@
package com.android.wm.shell.common.split;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
@@ -24,19 +24,27 @@
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.PendingIntent;
-import android.content.Context;
+import android.content.ComponentName;
import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
+import android.os.UserHandle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.internal.util.ArrayUtils;
import com.android.wm.shell.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
+import java.util.Arrays;
+import java.util.List;
+
/** Helper utility class for split screen components to use. */
public class SplitScreenUtils {
/** Reverse the split position. */
@@ -135,4 +143,28 @@
return isLandscape;
}
}
+
+ /** Returns the component from a PendingIntent */
+ @Nullable
+ public static ComponentName getComponent(@Nullable PendingIntent pendingIntent) {
+ if (pendingIntent == null || pendingIntent.getIntent() == null) {
+ return null;
+ }
+ return pendingIntent.getIntent().getComponent();
+ }
+
+ /** Returns the component from a shortcut */
+ @Nullable
+ public static ComponentName getShortcutComponent(@NonNull String packageName, String shortcutId,
+ @NonNull UserHandle user, @NonNull LauncherApps launcherApps) {
+ LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery();
+ query.setPackage(packageName);
+ query.setShortcutIds(Arrays.asList(shortcutId));
+ query.setQueryFlags(FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED);
+ List<ShortcutInfo> shortcuts = launcherApps.getShortcuts(query, user);
+ ShortcutInfo info = shortcuts != null && shortcuts.size() > 0
+ ? shortcuts.get(0)
+ : null;
+ return info != null ? info.getActivity() : null;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 0ef047f..36f06e8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -210,7 +210,6 @@
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
- RecentsTransitionHandler recentsTransitionHandler,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
if (DesktopModeStatus.isEnabled()) {
return new DesktopModeWindowDecorViewModel(
@@ -226,7 +225,6 @@
syncQueue,
transitions,
desktopTasksController,
- recentsTransitionHandler,
rootTaskDisplayAreaOrganizer);
}
return new CaptionWindowDecorViewModel(
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 4a1bcaa..b1c43c1 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
@@ -320,9 +320,8 @@
}
/** Move a task with given `taskId` to fullscreen */
- fun moveToFullscreen(taskId: Int, windowDecor: DesktopModeWindowDecoration) {
+ fun moveToFullscreen(taskId: Int) {
shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task ->
- windowDecor.incrementRelayoutBlock()
moveToFullscreenWithAnimation(task, task.positionInParent)
}
}
@@ -906,20 +905,17 @@
* @param position position of surface when drag ends.
* @param inputCoordinate the coordinates of the motion event
* @param taskBounds the updated bounds of the task being dragged.
- * @param windowDecor the window decoration for the task being dragged
*/
fun onDragPositioningEnd(
taskInfo: RunningTaskInfo,
position: Point,
inputCoordinate: PointF,
- taskBounds: Rect,
- windowDecor: DesktopModeWindowDecoration
+ taskBounds: Rect
) {
if (taskInfo.configuration.windowConfiguration.windowingMode != WINDOWING_MODE_FREEFORM) {
return
}
if (taskBounds.top <= transitionAreaHeight) {
- windowDecor.incrementRelayoutBlock()
moveToFullscreenWithAnimation(taskInfo, position)
}
if (inputCoordinate.x <= transitionAreaWidth) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
index 9debb25..0218493 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
@@ -54,8 +54,6 @@
taskId: Int,
windowDecoration: DesktopModeWindowDecoration
) {
- // Pause relayout until the transition animation finishes.
- windowDecoration.incrementRelayoutBlock()
transitions.startTransition(TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE, wct, this)
taskToDecorationMap.put(taskId, windowDecoration)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
index 6b6a7bc..ffcc526 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
@@ -112,7 +112,6 @@
onChangeTransitionReady(change, startT, finishT);
break;
}
- mWindowDecorViewModel.onTransitionReady(transition, info, change);
}
mTransitionToTaskInfo.put(transition, taskInfoList);
}
@@ -153,8 +152,6 @@
@Override
public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
- mWindowDecorViewModel.onTransitionMerged(merged, playing);
-
final List<ActivityManager.RunningTaskInfo> infoOfMerged =
mTransitionToTaskInfo.get(merged);
if (infoOfMerged == null) {
@@ -178,7 +175,6 @@
final List<ActivityManager.RunningTaskInfo> taskInfo =
mTransitionToTaskInfo.getOrDefault(transition, Collections.emptyList());
mTransitionToTaskInfo.remove(transition);
- mWindowDecorViewModel.onTransitionFinished(transition);
for (int i = 0; i < taskInfo.size(); ++i) {
mWindowDecorViewModel.destroyWindowDecoration(taskInfo.get(i));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
index 3906599..8b3de62 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
@@ -52,9 +52,10 @@
* @param componentName ComponentName represents the Activity
* @param destinationBounds the destination bounds the PiP window lands into
* @param overlay an optional overlay to fade out after entering PiP
+ * @param appBounds the bounds used to set the buffer size of the optional content overlay
*/
oneway void stopSwipePipToHome(int taskId, in ComponentName componentName,
- in Rect destinationBounds, in SurfaceControl overlay) = 2;
+ in Rect destinationBounds, in SurfaceControl overlay, in Rect appBounds) = 2;
/**
* Notifies the swiping Activity to PiP onto home transition is aborted
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 3635165..a9a3f78 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -334,6 +334,16 @@
@Nullable
SurfaceControl mPipOverlay;
+ /**
+ * The app bounds used for the buffer size of the
+ * {@link com.android.wm.shell.pip.PipContentOverlay.PipAppIconOverlay}.
+ *
+ * Note that this is empty if the overlay is removed or if it's some other type of overlay
+ * defined in {@link PipContentOverlay}.
+ */
+ @NonNull
+ final Rect mAppBounds = new Rect();
+
public PipTaskOrganizer(Context context,
@NonNull SyncTransactionQueue syncTransactionQueue,
@NonNull PipTransitionState pipTransitionState,
@@ -464,15 +474,15 @@
* Expect {@link #onTaskAppeared(ActivityManager.RunningTaskInfo, SurfaceControl)} afterwards.
*/
public void stopSwipePipToHome(int taskId, ComponentName componentName, Rect destinationBounds,
- SurfaceControl overlay) {
+ SurfaceControl overlay, Rect appBounds) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "stopSwipePipToHome: %s, state=%s", componentName, mPipTransitionState);
+ "stopSwipePipToHome: %s, stat=%s", componentName, mPipTransitionState);
// do nothing if there is no startSwipePipToHome being called before
if (!mPipTransitionState.getInSwipePipToHomeTransition()) {
return;
}
mPipBoundsState.setBounds(destinationBounds);
- mPipOverlay = overlay;
+ setContentOverlay(overlay, appBounds);
if (ENABLE_SHELL_TRANSITIONS && overlay != null) {
// With Shell transition, the overlay was attached to the remote transition leash, which
// will be removed when the current transition is finished, so we need to reparent it
@@ -1888,7 +1898,7 @@
"%s: trying to remove overlay (%s) which is not local reference (%s)",
TAG, surface, mPipOverlay);
}
- mPipOverlay = null;
+ clearContentOverlay();
}
if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) {
// Avoid double removal, which is fatal.
@@ -1905,6 +1915,20 @@
if (callback != null) callback.run();
}
+ void clearContentOverlay() {
+ mPipOverlay = null;
+ mAppBounds.setEmpty();
+ }
+
+ void setContentOverlay(@Nullable SurfaceControl leash, @NonNull Rect appBounds) {
+ mPipOverlay = leash;
+ if (mPipOverlay != null) {
+ mAppBounds.set(appBounds);
+ } else {
+ mAppBounds.setEmpty();
+ }
+ }
+
private void resetShadowRadius() {
if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) {
// mLeash is undefined when in PipTransitionState.UNDEFINED
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index f5f15d8..89dcc4c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -141,8 +141,6 @@
/** Whether the PIP window has fade out for fixed rotation. */
private boolean mHasFadeOut;
- private Rect mInitBounds = new Rect();
-
/** Used for setting transform to a transaction from animator. */
private final PipAnimationController.PipTransactionHandler mTransactionConsumer =
new PipAnimationController.PipTransactionHandler() {
@@ -465,12 +463,13 @@
mSurfaceTransactionHelper.crop(tx, leash, destinationBounds)
.resetScale(tx, leash, destinationBounds)
.round(tx, leash, true /* applyCornerRadius */);
- if (mPipOrganizer.mPipOverlay != null && !mInitBounds.isEmpty()) {
+ final Rect appBounds = mPipOrganizer.mAppBounds;
+ if (mPipOrganizer.mPipOverlay != null && !appBounds.isEmpty()) {
// Resetting the scale for pinned task while re-adjusting its crop,
// also scales the overlay. So we need to update the overlay leash too.
Rect overlayBounds = new Rect(destinationBounds);
final int overlaySize = PipContentOverlay.PipAppIconOverlay
- .getOverlaySize(mInitBounds, destinationBounds);
+ .getOverlaySize(appBounds, destinationBounds);
overlayBounds.offsetTo(
(destinationBounds.width() - overlaySize) / 2,
@@ -479,7 +478,6 @@
mPipOrganizer.mPipOverlay, overlayBounds);
}
}
- mInitBounds.setEmpty();
wct.setBoundsChangeTransaction(taskInfo.token, tx);
}
final int displayRotation = taskInfo.getConfiguration().windowConfiguration
@@ -617,7 +615,7 @@
// if overlay is present remove it immediately, as exit transition came before it faded out
if (mPipOrganizer.mPipOverlay != null) {
startTransaction.remove(mPipOrganizer.mPipOverlay);
- clearPipOverlay();
+ mPipOrganizer.clearContentOverlay();
}
if (pipChange == null) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -951,9 +949,6 @@
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
final Rect currentBounds = pipChange.getStartAbsBounds();
- // Cache the start bounds for overlay manipulations as a part of finishCallback.
- mInitBounds.set(currentBounds);
-
int rotationDelta = deltaRotation(startRotation, endRotation);
Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
taskInfo.pictureInPictureParams, currentBounds, destinationBounds);
@@ -1022,7 +1017,7 @@
} else {
throw new RuntimeException("Unrecognized animation type: " + enterAnimationType);
}
- mPipOrganizer.mPipOverlay = animator.getContentOverlayLeash();
+ mPipOrganizer.setContentOverlay(animator.getContentOverlayLeash(), currentBounds);
animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
.setPipAnimationCallback(mPipAnimationCallback)
.setDuration(mEnterExitAnimationDuration);
@@ -1073,10 +1068,6 @@
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"%s: SwipePipToHome should not use fixed rotation %d", TAG, mEndFixedRotation);
}
- Rect appBounds = pipTaskInfo.configuration.windowConfiguration.getAppBounds();
- if (mFixedRotationState == FIXED_ROTATION_CALLBACK && appBounds != null) {
- mInitBounds.set(appBounds);
- }
final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mPipOverlay;
if (swipePipToHomeOverlay != null) {
// Launcher fade in the overlay on top of the fullscreen Task. It is possible we
@@ -1106,7 +1097,7 @@
sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
if (swipePipToHomeOverlay != null) {
mPipOrganizer.fadeOutAndRemoveOverlay(swipePipToHomeOverlay,
- this::clearPipOverlay /* callback */, false /* withStartDelay */);
+ null /* callback */, false /* withStartDelay */);
}
mPipTransitionState.setInSwipePipToHomeTransition(false);
}
@@ -1250,10 +1241,6 @@
mPipMenuController.updateMenuBounds(destinationBounds);
}
- private void clearPipOverlay() {
- mPipOrganizer.mPipOverlay = null;
- }
-
@Override
public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 63f20fd..238e6b5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -982,8 +982,9 @@
}
private void stopSwipePipToHome(int taskId, ComponentName componentName, Rect destinationBounds,
- SurfaceControl overlay) {
- mPipTaskOrganizer.stopSwipePipToHome(taskId, componentName, destinationBounds, overlay);
+ SurfaceControl overlay, Rect appBounds) {
+ mPipTaskOrganizer.stopSwipePipToHome(taskId, componentName, destinationBounds, overlay,
+ appBounds);
}
private void abortSwipePipToHome(int taskId, ComponentName componentName) {
@@ -1280,13 +1281,13 @@
@Override
public void stopSwipePipToHome(int taskId, ComponentName componentName,
- Rect destinationBounds, SurfaceControl overlay) {
+ Rect destinationBounds, SurfaceControl overlay, Rect appBounds) {
if (overlay != null) {
overlay.setUnreleasedWarningCallSite("PipController.stopSwipePipToHome");
}
executeRemoteCallWithTaskPermission(mController, "stopSwipePipToHome",
(controller) -> controller.stopSwipePipToHome(
- taskId, componentName, destinationBounds, overlay));
+ taskId, componentName, destinationBounds, overlay, appBounds));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 7b57097..880d952 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -23,12 +23,15 @@
import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.common.split.SplitScreenConstants.KEY_EXTRA_WIDGET_INTENT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.common.split.SplitScreenUtils.getComponent;
+import static com.android.wm.shell.common.split.SplitScreenUtils.getShortcutComponent;
import static com.android.wm.shell.common.split.SplitScreenUtils.isValidToSplit;
import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
import static com.android.wm.shell.common.split.SplitScreenUtils.samePackage;
@@ -47,6 +50,8 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.graphics.Rect;
import android.os.Bundle;
@@ -171,6 +176,8 @@
private final ShellTaskOrganizer mTaskOrganizer;
private final SyncTransactionQueue mSyncQueue;
private final Context mContext;
+ private final PackageManager mPackageManager;
+ private final LauncherApps mLauncherApps;
private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
private final ShellExecutor mMainExecutor;
private final SplitScreenImpl mImpl = new SplitScreenImpl();
@@ -186,7 +193,8 @@
private final Optional<WindowDecorViewModel> mWindowDecorViewModel;
private final Optional<DesktopTasksController> mDesktopTasksController;
private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler;
- private final String[] mAppsSupportMultiInstances;
+ // A static allow list of apps which support multi-instance
+ private final String[] mAppsSupportingMultiInstance;
@VisibleForTesting
StageCoordinator mStageCoordinator;
@@ -220,6 +228,8 @@
mTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
mContext = context;
+ mPackageManager = context.getPackageManager();
+ mLauncherApps = context.getSystemService(LauncherApps.class);
mRootTDAOrganizer = rootTDAOrganizer;
mMainExecutor = mainExecutor;
mDisplayController = displayController;
@@ -242,7 +252,7 @@
// TODO(255224696): Remove the config once having a way for client apps to opt-in
// multi-instances split.
- mAppsSupportMultiInstances = mContext.getResources()
+ mAppsSupportingMultiInstance = mContext.getResources()
.getStringArray(R.array.config_appsSupportMultiInstancesSplit);
}
@@ -266,12 +276,15 @@
WindowDecorViewModel windowDecorViewModel,
DesktopTasksController desktopTasksController,
ShellExecutor mainExecutor,
- StageCoordinator stageCoordinator) {
+ StageCoordinator stageCoordinator,
+ String[] appsSupportingMultiInstance) {
mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
mTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
mContext = context;
+ mPackageManager = context.getPackageManager();
+ mLauncherApps = context.getSystemService(LauncherApps.class);
mRootTDAOrganizer = rootTDAOrganizer;
mMainExecutor = mainExecutor;
mDisplayController = displayController;
@@ -288,8 +301,7 @@
mStageCoordinator = stageCoordinator;
mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
shellInit.addInitCallback(this::onInit, this);
- mAppsSupportMultiInstances = mContext.getResources()
- .getStringArray(R.array.config_appsSupportMultiInstancesSplit);
+ mAppsSupportingMultiInstance = appsSupportingMultiInstance;
}
public SplitScreen asSplitScreen() {
@@ -588,7 +600,8 @@
if (samePackage(packageName, getPackageName(reverseSplitPosition(position)),
user.getIdentifier(), getUserId(reverseSplitPosition(position)))) {
- if (supportMultiInstancesSplit(packageName)) {
+ if (supportsMultiInstanceSplit(getShortcutComponent(packageName, shortcutId, user,
+ mLauncherApps))) {
activityOptions.setApplyMultipleTaskFlagForShortcut(true);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
} else if (isSplitScreenVisible()) {
@@ -609,7 +622,7 @@
activityOptions.toBundle(), user);
}
- void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
+ void startShortcutAndTaskWithLegacyTransition(@NonNull ShortcutInfo shortcutInfo,
@Nullable Bundle options1, int taskId, @Nullable Bundle options2,
@SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
RemoteAnimationAdapter adapter, InstanceId instanceId) {
@@ -621,7 +634,7 @@
final int userId1 = shortcutInfo.getUserId();
final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (supportMultiInstancesSplit(shortcutInfo.getPackage())) {
+ if (supportsMultiInstanceSplit(shortcutInfo.getActivity())) {
activityOptions.setApplyMultipleTaskFlagForShortcut(true);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
} else {
@@ -640,7 +653,7 @@
instanceId);
}
- void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1,
+ void startShortcutAndTask(@NonNull ShortcutInfo shortcutInfo, @Nullable Bundle options1,
int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
@PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition,
InstanceId instanceId) {
@@ -653,7 +666,7 @@
final int userId1 = shortcutInfo.getUserId();
final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (supportMultiInstancesSplit(packageName1)) {
+ if (supportsMultiInstanceSplit(shortcutInfo.getActivity())) {
activityOptions.setApplyMultipleTaskFlagForShortcut(true);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
} else {
@@ -692,7 +705,7 @@
final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (supportMultiInstancesSplit(packageName1)) {
+ if (supportsMultiInstanceSplit(getComponent(pendingIntent))) {
fillInIntent = new Intent();
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
@@ -722,7 +735,7 @@
final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
boolean setSecondIntentMultipleTask = false;
if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (supportMultiInstancesSplit(packageName1)) {
+ if (supportsMultiInstanceSplit(getComponent(pendingIntent))) {
setSecondIntentMultipleTask = true;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
} else {
@@ -757,7 +770,7 @@
final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1);
final String packageName2 = SplitScreenUtils.getPackageName(pendingIntent2);
if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (supportMultiInstancesSplit(packageName1)) {
+ if (supportsMultiInstanceSplit(getComponent(pendingIntent1))) {
fillInIntent1 = new Intent();
fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
fillInIntent2 = new Intent();
@@ -794,7 +807,7 @@
? ActivityOptions.fromBundle(options2) : ActivityOptions.makeBasic();
boolean setSecondIntentMultipleTask = false;
if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (supportMultiInstancesSplit(packageName1)) {
+ if (supportsMultiInstanceSplit(getComponent(pendingIntent1))) {
fillInIntent1 = new Intent();
fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
setSecondIntentMultipleTask = true;
@@ -856,7 +869,7 @@
return;
}
if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (supportMultiInstancesSplit(packageName1)) {
+ if (supportsMultiInstanceSplit(getComponent(intent))) {
// Flag with MULTIPLE_TASK if this is launching the same activity into both sides of
// the split and there is no reusable background task.
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
@@ -915,16 +928,63 @@
return taskInfo != null ? taskInfo.userId : -1;
}
+ /**
+ * Returns whether a specific component desires to be launched in multiple instances for
+ * split screen.
+ */
@VisibleForTesting
- boolean supportMultiInstancesSplit(String packageName) {
- if (packageName != null) {
- for (int i = 0; i < mAppsSupportMultiInstances.length; i++) {
- if (mAppsSupportMultiInstances[i].equals(packageName)) {
- return true;
- }
+ boolean supportsMultiInstanceSplit(@Nullable ComponentName componentName) {
+ if (componentName == null || componentName.getPackageName() == null) {
+ // TODO(b/262864589): Handle empty component case
+ return false;
+ }
+
+ // Check the pre-defined allow list
+ final String packageName = componentName.getPackageName();
+ for (int i = 0; i < mAppsSupportingMultiInstance.length; i++) {
+ if (mAppsSupportingMultiInstance[i].equals(packageName)) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "application=%s in allowlist supports multi-instance", packageName);
+ return true;
}
}
+ // Check the activity property first
+ try {
+ final PackageManager.Property activityProp = mPackageManager.getProperty(
+ PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, componentName);
+ // If the above call doesn't throw a NameNotFoundException, then the activity property
+ // should override the application property value
+ if (activityProp.isBoolean()) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "activity=%s supports multi-instance", componentName);
+ return activityProp.getBoolean();
+ } else {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Warning: property=%s for activity=%s has non-bool type=%d",
+ PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName,
+ activityProp.getType());
+ }
+ } catch (PackageManager.NameNotFoundException nnfe) {
+ // Not specified in the activity, fall through
+ }
+
+ // Check the application property otherwise
+ try {
+ final PackageManager.Property appProp = mPackageManager.getProperty(
+ PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName);
+ if (appProp.isBoolean()) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "application=%s supports multi-instance", packageName);
+ return appProp.getBoolean();
+ } else {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Warning: property=%s for application=%s has non-bool type=%d",
+ PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName, appProp.getType());
+ }
+ } catch (PackageManager.NameNotFoundException nnfe) {
+ // Not specified in either application or activity
+ }
return false;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index f58aeac..a666e20 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -73,6 +73,7 @@
import com.android.internal.graphics.palette.Palette;
import com.android.internal.graphics.palette.Quantizer;
import com.android.internal.graphics.palette.VariationalKMeansQuantizer;
+import com.android.internal.policy.PhoneWindow;
import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.IconProvider;
@@ -245,16 +246,19 @@
} else {
windowFlags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
}
- params.layoutInDisplayCutoutMode = a.getInt(
- R.styleable.Window_windowLayoutInDisplayCutoutMode,
- WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS);
- params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0);
- a.recycle();
final ActivityManager.RunningTaskInfo taskInfo = windowInfo.taskInfo;
final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
? windowInfo.targetActivityInfo
: taskInfo.topActivityInfo;
+ params.layoutInDisplayCutoutMode = a.getInt(
+ R.styleable.Window_windowLayoutInDisplayCutoutMode,
+ PhoneWindow.isEdgeToEdgeEnforced(activityInfo.applicationInfo, false /* local */)
+ ? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+ : params.layoutInDisplayCutoutMode);
+ params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0);
+ a.recycle();
+
final int displayId = taskInfo.displayId;
// Assumes it's safe to show starting windows of launched apps while
// the keyguard is being hidden. This is okay because starting windows never show
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index 34c015f..84f21f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -330,7 +330,7 @@
continue;
}
if (isHide) {
- if (pending.mType == TRANSIT_TO_BACK) {
+ if (pending != null && pending.mType == TRANSIT_TO_BACK) {
// TO_BACK is only used when setting the task view visibility immediately,
// so in that case we can also hide the surface immediately
startTransaction.hide(chg.getLeash());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 9f20f49..db84513 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -21,9 +21,9 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
@@ -84,7 +84,7 @@
private UnfoldTransitionHandler mUnfoldHandler;
private ActivityEmbeddingController mActivityEmbeddingController;
- private class MixedTransition {
+ private static class MixedTransition {
static final int TYPE_ENTER_PIP_FROM_SPLIT = 1;
/** Both the display and split-state (enter/exit) is changing */
@@ -175,7 +175,6 @@
joinFinishArgs(wct);
if (mInFlightSubAnimations == 0) {
- mActiveTransitions.remove(MixedTransition.this);
mFinishCB.onTransitionFinished(mFinishWCT);
}
}
@@ -401,8 +400,12 @@
final MixedTransition keyguardMixed =
new MixedTransition(MixedTransition.TYPE_KEYGUARD, transition);
mActiveTransitions.add(keyguardMixed);
- final boolean hasAnimateKeyguard = animateKeyguard(keyguardMixed, info,
- startTransaction, finishTransaction, finishCallback);
+ Transitions.TransitionFinishCallback callback = wct -> {
+ mActiveTransitions.remove(keyguardMixed);
+ finishCallback.onTransitionFinished(wct);
+ };
+ final boolean hasAnimateKeyguard = animateKeyguard(
+ keyguardMixed, info, startTransaction, finishTransaction, callback);
if (hasAnimateKeyguard) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"Converting mixed transition into a keyguard transition");
@@ -420,27 +423,34 @@
if (mixed == null) return false;
- if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
- return animateEnterPipFromSplit(mixed, info, startTransaction, finishTransaction,
- finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
- return animateEnterPipFromActivityEmbedding(mixed, info, startTransaction,
- finishTransaction, finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) {
+ final MixedTransition chosenTransition = mixed;
+ Transitions.TransitionFinishCallback callback = wct -> {
+ mActiveTransitions.remove(chosenTransition);
+ finishCallback.onTransitionFinished(wct);
+ };
+
+ if (chosenTransition.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
+ return animateEnterPipFromSplit(
+ chosenTransition, info, startTransaction, finishTransaction, callback);
+ } else if (chosenTransition.mType
+ == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
+ return animateEnterPipFromActivityEmbedding(
+ chosenTransition, info, startTransaction, finishTransaction, callback);
+ } else if (chosenTransition.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) {
return false;
- } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
- final boolean handledToPip = animateOpenIntentWithRemoteAndPip(mixed, info,
- startTransaction, finishTransaction, finishCallback);
+ } else if (chosenTransition.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
+ final boolean handledToPip = animateOpenIntentWithRemoteAndPip(
+ chosenTransition, info, startTransaction, finishTransaction, callback);
// Consume the transition on remote handler if the leftover handler already handle this
// transition. And if it cannot, the transition will be handled by remote handler, so
// don't consume here.
// Need to check leftOverHandler as it may change in #animateOpenIntentWithRemoteAndPip
- if (handledToPip && mixed.mHasRequestToRemote
- && mixed.mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) {
+ if (handledToPip && chosenTransition.mHasRequestToRemote
+ && chosenTransition.mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) {
mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, false, null);
}
return handledToPip;
- } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
+ } else if (chosenTransition.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
// Pip auto-entering info might be appended to recent transition like pressing
@@ -449,28 +459,29 @@
if (mPipHandler.isEnteringPip(change, info.getType())
&& mSplitHandler.getSplitItemPosition(change.getLastParent())
!= SPLIT_POSITION_UNDEFINED) {
- return animateEnterPipFromSplit(mixed, info, startTransaction,
- finishTransaction, finishCallback);
+ return animateEnterPipFromSplit(
+ chosenTransition, info, startTransaction, finishTransaction, callback);
}
}
- return animateRecentsDuringSplit(mixed, info, startTransaction, finishTransaction,
- finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
- return animateKeyguard(mixed, info, startTransaction, finishTransaction,
- finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_KEYGUARD) {
- return animateRecentsDuringKeyguard(mixed, info, startTransaction, finishTransaction,
- finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
- return animateRecentsDuringDesktop(mixed, info, startTransaction, finishTransaction,
- finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_UNFOLD) {
- return animateUnfold(mixed, info, startTransaction, finishTransaction, finishCallback);
+ return animateRecentsDuringSplit(
+ chosenTransition, info, startTransaction, finishTransaction, callback);
+ } else if (chosenTransition.mType == MixedTransition.TYPE_KEYGUARD) {
+ return animateKeyguard(
+ chosenTransition, info, startTransaction, finishTransaction, callback);
+ } else if (chosenTransition.mType == MixedTransition.TYPE_RECENTS_DURING_KEYGUARD) {
+ return animateRecentsDuringKeyguard(
+ chosenTransition, info, startTransaction, finishTransaction, callback);
+ } else if (chosenTransition.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
+ return animateRecentsDuringDesktop(
+ chosenTransition, info, startTransaction, finishTransaction, callback);
+ } else if (chosenTransition.mType == MixedTransition.TYPE_UNFOLD) {
+ return animateUnfold(
+ chosenTransition, info, startTransaction, finishTransaction, callback);
} else {
- mActiveTransitions.remove(mixed);
+ mActiveTransitions.remove(chosenTransition);
throw new IllegalStateException("Starting mixed animation without a known mixed type? "
- + mixed.mType);
+ + chosenTransition.mType);
}
}
@@ -727,7 +738,11 @@
final MixedTransition mixed = new MixedTransition(
MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT, transition);
mActiveTransitions.add(mixed);
- return animateEnterPipFromSplit(mixed, info, startT, finishT, finishCallback);
+ Transitions.TransitionFinishCallback callback = wct -> {
+ mActiveTransitions.remove(mixed);
+ finishCallback.onTransitionFinished(wct);
+ };
+ return animateEnterPipFromSplit(mixed, info, startT, finishT, callback);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index cebc400..1a793a1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -23,13 +23,11 @@
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.os.Handler;
-import android.os.IBinder;
import android.util.SparseArray;
import android.view.Choreographer;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
-import android.window.TransitionInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -80,16 +78,6 @@
}
@Override
- public void onTransitionReady(IBinder transition, TransitionInfo info,
- TransitionInfo.Change change) {}
-
- @Override
- public void onTransitionMerged(IBinder merged, IBinder playing) {}
-
- @Override
- public void onTransitionFinished(IBinder transition) {}
-
- @Override
public void setFreeformTaskTransitionStarter(FreeformTaskTransitionStarter transitionStarter) {
mTaskOperations = new TaskOperations(transitionStarter, mContext, mSyncQueue);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 61a8e9b..d07c646 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -22,7 +22,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.WindowInsets.Type.statusBars;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
@@ -43,7 +42,6 @@
import android.graphics.Region;
import android.hardware.input.InputManager;
import android.os.Handler;
-import android.os.IBinder;
import android.os.Looper;
import android.util.SparseArray;
import android.view.Choreographer;
@@ -59,8 +57,6 @@
import android.view.SurfaceControl.Transaction;
import android.view.View;
import android.view.ViewConfiguration;
-import android.view.WindowManager;
-import android.window.TransitionInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -80,8 +76,6 @@
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
-import com.android.wm.shell.recents.RecentsTransitionHandler;
-import com.android.wm.shell.recents.RecentsTransitionStateListener;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -115,7 +109,6 @@
private final DisplayController mDisplayController;
private final SyncTransactionQueue mSyncQueue;
private final Optional<DesktopTasksController> mDesktopTasksController;
- private final RecentsTransitionHandler mRecentsTransitionHandler;
private boolean mTransitionDragActive;
private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
@@ -154,7 +147,6 @@
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
- RecentsTransitionHandler recentsTransitionHandler,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer
) {
this(
@@ -170,7 +162,6 @@
syncQueue,
transitions,
desktopTasksController,
- recentsTransitionHandler,
new DesktopModeWindowDecoration.Factory(),
new InputMonitorFactory(),
SurfaceControl.Transaction::new,
@@ -191,7 +182,6 @@
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
- RecentsTransitionHandler recentsTransitionHandler,
DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
InputMonitorFactory inputMonitorFactory,
Supplier<SurfaceControl.Transaction> transactionFactory,
@@ -207,7 +197,6 @@
mSyncQueue = syncQueue;
mTransitions = transitions;
mDesktopTasksController = desktopTasksController;
- mRecentsTransitionHandler = recentsTransitionHandler;
mShellCommandHandler = shellCommandHandler;
mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory;
mInputMonitorFactory = inputMonitorFactory;
@@ -219,12 +208,6 @@
private void onInit() {
mShellController.addKeyguardChangeListener(mDesktopModeKeyguardChangeListener);
- mRecentsTransitionHandler.addTransitionStateListener(new RecentsTransitionStateListener() {
- @Override
- public void onTransitionStarted(IBinder transition) {
- blockRelayoutOnTransitionStarted(transition);
- }
- });
mShellCommandHandler.addDumpCallback(this::dump, this);
mDisplayInsetsController.addInsetsChangedListener(mContext.getDisplayId(),
new DesktopModeOnInsetsChangedListener());
@@ -264,48 +247,6 @@
}
@Override
- public void onTransitionReady(
- @NonNull IBinder transition,
- @NonNull TransitionInfo info,
- @NonNull TransitionInfo.Change change) {
- if (change.getMode() == WindowManager.TRANSIT_CHANGE
- && (info.getType() == Transitions.TRANSIT_EXIT_DESKTOP_MODE
- || info.getType() == Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE
- || info.getType() == Transitions.TRANSIT_MOVE_TO_DESKTOP)) {
- mWindowDecorByTaskId.get(change.getTaskInfo().taskId)
- .addTransitionPausingRelayout(transition);
- } else if (change.getMode() == WindowManager.TRANSIT_TO_BACK
- && info.getType() == Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
- && change.getTaskInfo() != null) {
- final DesktopModeWindowDecoration decor =
- mWindowDecorByTaskId.get(change.getTaskInfo().taskId);
- if (decor != null) {
- decor.addTransitionPausingRelayout(transition);
- }
- } else if (change.getMode() == WindowManager.TRANSIT_TO_FRONT
- && ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0)
- && change.getTaskInfo() != null) {
- blockRelayoutOnTransitionStarted(transition);
- }
- }
-
- @Override
- public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
- for (int i = 0; i < mWindowDecorByTaskId.size(); i++) {
- final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
- decor.mergeTransitionPausingRelayout(merged, playing);
- }
- }
-
- @Override
- public void onTransitionFinished(@NonNull IBinder transition) {
- for (int i = 0; i < mWindowDecorByTaskId.size(); i++) {
- final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
- decor.removeTransitionPausingRelayout(transition);
- }
- }
-
- @Override
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
if (decoration == null) return;
@@ -365,16 +306,6 @@
}
}
- private void blockRelayoutOnTransitionStarted(IBinder transition) {
- // Block relayout on window decorations originating from #onTaskInfoChanges until the
- // animation completes to avoid interfering with the transition animation.
- for (int i = 0; i < mWindowDecorByTaskId.size(); i++) {
- final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
- decor.incrementRelayoutBlock();
- decor.addTransitionPausingRelayout(transition);
- }
- }
-
private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener,
DragDetector.MotionEventHandler {
@@ -425,7 +356,6 @@
// App sometimes draws before the insets from WindowDecoration#relayout have
// been added, so they must be added here
mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct);
- decoration.incrementRelayoutBlock();
mDesktopTasksController.get().moveToDesktop(decoration, mTaskId, wct);
closeOtherSplitTask(mTaskId);
}
@@ -436,7 +366,7 @@
mSplitScreenController.moveTaskToFullscreen(mTaskId);
} else {
mDesktopTasksController.ifPresent(c ->
- c.moveToFullscreen(mTaskId, mWindowDecorByTaskId.get(mTaskId)));
+ c.moveToFullscreen(mTaskId));
}
} else if (id == R.id.split_screen_button) {
decoration.closeHandleMenu();
@@ -604,7 +534,7 @@
mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo,
position,
new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)),
- newTaskBounds, mWindowDecorByTaskId.get(mTaskId)));
+ newTaskBounds));
mIsDragging = false;
return true;
}
@@ -812,20 +742,8 @@
mContext, mDragToDesktopAnimationStartBounds,
relevantDecor.mTaskInfo, relevantDecor.mTaskSurface);
mDesktopTasksController.ifPresent(
- c -> {
- final int taskId = relevantDecor.mTaskInfo.taskId;
- relevantDecor.incrementRelayoutBlock();
- if (isTaskInSplitScreen(taskId)) {
- final DesktopModeWindowDecoration otherDecor =
- mWindowDecorByTaskId.get(
- getOtherSplitTask(taskId).taskId);
- if (otherDecor != null) {
- otherDecor.incrementRelayoutBlock();
- }
- }
- c.startDragToDesktop(relevantDecor.mTaskInfo,
- mMoveToDesktopAnimator, relevantDecor);
- });
+ c -> c.startDragToDesktop(relevantDecor.mTaskInfo,
+ mMoveToDesktopAnimator, relevantDecor));
}
}
if (mMoveToDesktopAnimator != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index d08b655..5f77192 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -36,7 +36,6 @@
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.os.Handler;
-import android.os.IBinder;
import android.util.Log;
import android.view.Choreographer;
import android.view.MotionEvent;
@@ -62,8 +61,6 @@
import com.android.wm.shell.windowdecor.viewholder.DesktopModeFocusedWindowDecorationViewHolder;
import com.android.wm.shell.windowdecor.viewholder.DesktopModeWindowDecorationViewHolder;
-import java.util.HashSet;
-import java.util.Set;
import java.util.function.Supplier;
/**
@@ -104,8 +101,6 @@
private ExclusionRegionListener mExclusionRegionListener;
- private final Set<IBinder> mTransitionsPausingRelayout = new HashSet<>();
- private int mRelayoutBlock;
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
DesktopModeWindowDecoration(
@@ -179,13 +174,6 @@
@Override
void relayout(ActivityManager.RunningTaskInfo taskInfo) {
- // TaskListener callbacks and shell transitions aren't synchronized, so starting a shell
- // transition can trigger an onTaskInfoChanged call that updates the task's SurfaceControl
- // and interferes with the transition animation that is playing at the same time.
- if (mRelayoutBlock > 0) {
- return;
- }
-
final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
// The crop and position of the task should only be set when a task is fluid resizing. In
// all other cases, it is expected that the transition handler positions and crops the task
@@ -737,16 +725,6 @@
return exclusionRegion;
}
- /**
- * If transition exists in mTransitionsPausingRelayout, remove the transition and decrement
- * mRelayoutBlock
- */
- void removeTransitionPausingRelayout(IBinder transition) {
- if (mTransitionsPausingRelayout.remove(transition)) {
- mRelayoutBlock--;
- }
- }
-
@Override
int getCaptionHeightId(@WindowingMode int windowingMode) {
return getCaptionHeightIdStatic(windowingMode);
@@ -767,35 +745,10 @@
return R.id.desktop_mode_caption;
}
- /**
- * Add transition to mTransitionsPausingRelayout
- */
- void addTransitionPausingRelayout(IBinder transition) {
- mTransitionsPausingRelayout.add(transition);
- }
-
- /**
- * If two transitions merge and the merged transition is in mTransitionsPausingRelayout,
- * remove the merged transition from the set and add the transition it was merged into.
- */
- public void mergeTransitionPausingRelayout(IBinder merged, IBinder playing) {
- if (mTransitionsPausingRelayout.remove(merged)) {
- mTransitionsPausingRelayout.add(playing);
- }
- }
-
- /**
- * Increase mRelayoutBlock, stopping relayout if mRelayoutBlock is now greater than 0.
- */
- public void incrementRelayoutBlock() {
- mRelayoutBlock++;
- }
-
@Override
public String toString() {
return "{"
+ "mPositionInParent=" + mPositionInParent + ", "
- + "mRelayoutBlock=" + mRelayoutBlock + ", "
+ "taskId=" + mTaskInfo.taskId + ", "
+ "windowingMode=" + windowingModeToString(mTaskInfo.getWindowingMode()) + ", "
+ "isFocused=" + isFocused()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
index ae1a3d9..01a6012 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
@@ -17,9 +17,7 @@
package com.android.wm.shell.windowdecor;
import android.app.ActivityManager;
-import android.os.IBinder;
import android.view.SurfaceControl;
-import android.window.TransitionInfo;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -103,34 +101,4 @@
* @param taskInfo the info of the task
*/
void destroyWindowDecoration(ActivityManager.RunningTaskInfo taskInfo);
-
- /**
- * Notifies that a shell transition is about to start. If the transition is of type
- * TRANSIT_ENTER_DESKTOP, it will save that transition to unpause relayout for the transitioning
- * task after the transition has ended.
- *
- * @param transition the ready transaction
- * @param info of Transition to check if relayout needs to be paused for a task
- * @param change a change in the given transition
- */
- default void onTransitionReady(IBinder transition, TransitionInfo info,
- TransitionInfo.Change change) {}
-
- /**
- * Notifies that a shell transition is about to merge with another to give the window
- * decoration a chance to prepare for this merge.
- *
- * @param merged the transaction being merged
- * @param playing the transaction being merged into
- */
- default void onTransitionMerged(IBinder merged, IBinder playing) {}
-
- /**
- * Notifies that a shell transition is about to finish to give the window decoration a chance
- * to clean up.
- *
- * @param transaction
- */
- default void onTransitionFinished(IBinder transaction) {}
-
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.kt
index bf07dcc..6dbb1e2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.kt
@@ -170,6 +170,71 @@
nonLinearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / nonLinearTarget)
}
+ @Test
+ fun restartingGesture_resetsInitialTouchX_leftEdge() {
+ val linearTracker = linearTouchTracker()
+ linearTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0f, BackEvent.EDGE_LEFT)
+ var touchX = 100f
+ val velocityX = 0f
+ val velocityY = 0f
+
+ // assert that progress is increased when increasing touchX
+ linearTracker.update(touchX, 0f, velocityX, velocityY)
+ linearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / MAX_DISTANCE)
+
+ // assert that progress is reset to 0 when start location is updated
+ linearTracker.updateStartLocation()
+ linearTracker.assertProgress(0f)
+
+ // assert that progress remains 0 when touchX is decreased
+ touchX -= 50
+ linearTracker.update(touchX, 0f, velocityX, velocityY)
+ linearTracker.assertProgress(0f)
+
+ // assert that progress uses new minimal touchX for progress calculation
+ val newInitialTouchX = touchX
+ touchX += 100
+ linearTracker.update(touchX, 0f, velocityX, velocityY)
+ linearTracker.assertProgress((touchX - newInitialTouchX) / MAX_DISTANCE)
+
+ // assert the same for triggerBack==true
+ linearTracker.triggerBack = true
+ linearTracker.assertProgress((touchX - newInitialTouchX) / MAX_DISTANCE)
+ }
+
+ @Test
+ fun restartingGesture_resetsInitialTouchX_rightEdge() {
+ val linearTracker = linearTouchTracker()
+ linearTracker.setGestureStartLocation(INITIAL_X_RIGHT_EDGE, 0f, BackEvent.EDGE_RIGHT)
+
+ var touchX = INITIAL_X_RIGHT_EDGE - 100f
+ val velocityX = 0f
+ val velocityY = 0f
+
+ // assert that progress is increased when decreasing touchX
+ linearTracker.update(touchX, 0f, velocityX, velocityY)
+ linearTracker.assertProgress((INITIAL_X_RIGHT_EDGE - touchX) / MAX_DISTANCE)
+
+ // assert that progress is reset to 0 when start location is updated
+ linearTracker.updateStartLocation()
+ linearTracker.assertProgress(0f)
+
+ // assert that progress remains 0 when touchX is increased
+ touchX += 50
+ linearTracker.update(touchX, 0f, velocityX, velocityY)
+ linearTracker.assertProgress(0f)
+
+ // assert that progress uses new maximal touchX for progress calculation
+ val newInitialTouchX = touchX
+ touchX -= 100
+ linearTracker.update(touchX, 0f, velocityX, velocityY)
+ linearTracker.assertProgress((newInitialTouchX - touchX) / MAX_DISTANCE)
+
+ // assert the same for triggerBack==true
+ linearTracker.triggerBack = true
+ linearTracker.assertProgress((newInitialTouchX - touchX) / MAX_DISTANCE)
+ }
+
companion object {
private const val MAX_DISTANCE = 500f
private const val LINEAR_DISTANCE = 400f
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitScreenUtilsTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitScreenUtilsTests.kt
new file mode 100644
index 0000000..955660c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitScreenUtilsTests.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.wm.shell.common.split
+
+import android.content.ComponentName
+import android.content.pm.LauncherApps
+import android.content.pm.ShortcutInfo
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.wm.shell.ShellTestCase
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when`
+
+@RunWith(AndroidJUnit4::class)
+class SplitScreenUtilsTests : ShellTestCase() {
+
+ @Test
+ fun getShortcutComponent_nullShortcuts() {
+ val launcherApps = mock(LauncherApps::class.java).also {
+ `when`(it.getShortcuts(any(), any())).thenReturn(null)
+ }
+ assertEquals(null, SplitScreenUtils.getShortcutComponent(TEST_PACKAGE,
+ TEST_SHORTCUT_ID, UserHandle.CURRENT, launcherApps))
+ }
+
+ @Test
+ fun getShortcutComponent_noShortcuts() {
+ val launcherApps = mock(LauncherApps::class.java).also {
+ `when`(it.getShortcuts(any(), any())).thenReturn(ArrayList<ShortcutInfo>())
+ }
+ assertEquals(null, SplitScreenUtils.getShortcutComponent(TEST_PACKAGE,
+ TEST_SHORTCUT_ID, UserHandle.CURRENT, launcherApps))
+ }
+
+ @Test
+ fun getShortcutComponent_validShortcut() {
+ val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY)
+ val shortcutInfo = ShortcutInfo.Builder(context, "id").setActivity(component).build()
+ val launcherApps = mock(LauncherApps::class.java).also {
+ `when`(it.getShortcuts(any(), any())).thenReturn(arrayListOf(shortcutInfo))
+ }
+ assertEquals(component, SplitScreenUtils.getShortcutComponent(TEST_PACKAGE,
+ TEST_SHORTCUT_ID, UserHandle.CURRENT, launcherApps))
+ }
+
+ companion object {
+ val TEST_PACKAGE = "com.android.wm.shell.common.split"
+ val TEST_ACTIVITY = "TestActivity";
+ val TEST_SHORTCUT_ID = "test_shortcut_1"
+ }
+}
\ No newline at end of file
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 94c862b..9249b0a 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
@@ -393,7 +393,7 @@
fun moveToFullscreen_displayFullscreen_windowingModeSetToUndefined() {
val task = setUpFreeformTask()
task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN
- controller.moveToFullscreen(task.taskId, desktopModeWindowDecoration)
+ controller.moveToFullscreen(task.taskId)
val wct = getLatestExitDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_UNDEFINED)
@@ -403,7 +403,7 @@
fun moveToFullscreen_displayFreeform_windowingModeSetToFullscreen() {
val task = setUpFreeformTask()
task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM
- controller.moveToFullscreen(task.taskId, desktopModeWindowDecoration)
+ controller.moveToFullscreen(task.taskId)
val wct = getLatestExitDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_FULLSCREEN)
@@ -411,7 +411,7 @@
@Test
fun moveToFullscreen_nonExistentTask_doesNothing() {
- controller.moveToFullscreen(999, desktopModeWindowDecoration)
+ controller.moveToFullscreen(999)
verifyWCTNotExecuted()
}
@@ -420,7 +420,7 @@
val taskDefaultDisplay = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val taskSecondDisplay = setUpFreeformTask(displayId = SECOND_DISPLAY)
- controller.moveToFullscreen(taskDefaultDisplay.taskId, desktopModeWindowDecoration)
+ controller.moveToFullscreen(taskDefaultDisplay.taskId)
with(getLatestExitDesktopWct()) {
assertThat(changes.keys).contains(taskDefaultDisplay.token.asBinder())
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 855b7ee..12a5594 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -22,6 +22,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
+import static android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
@@ -36,6 +37,8 @@
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -46,8 +49,10 @@
import android.app.ActivityTaskManager;
import android.app.PendingIntent;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
import android.os.Bundle;
import androidx.test.annotation.UiThreadTest;
@@ -55,6 +60,7 @@
import androidx.test.filters.SmallTest;
import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
@@ -91,6 +97,10 @@
@RunWith(AndroidJUnit4.class)
public class SplitScreenControllerTests extends ShellTestCase {
+ private static final String TEST_PACKAGE = "com.android.wm.shell.splitscreen";
+ private static final String TEST_NOT_ALLOWED_PACKAGE = "com.android.wm.shell.splitscreen.fake";
+ private static final String TEST_ACTIVITY = "TestActivity";
+
@Mock ShellInit mShellInit;
@Mock ShellCommandHandler mShellCommandHandler;
@Mock ShellTaskOrganizer mTaskOrganizer;
@@ -118,6 +128,8 @@
public void setup() {
assumeTrue(ActivityTaskManager.supportsSplitScreenMultiWindow(mContext));
MockitoAnnotations.initMocks(this);
+ String[] appsSupportingMultiInstance = mContext.getResources()
+ .getStringArray(R.array.config_appsSupportMultiInstancesSplit);
mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler,
mMainExecutor));
mSplitScreenController = spy(new SplitScreenController(mContext, mShellInit,
@@ -125,7 +137,8 @@
mRootTDAOrganizer, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
mIconProvider, mRecentTasks, mLaunchAdjacentController, mWindowDecorViewModel,
- mDesktopTasksController, mMainExecutor, mStageCoordinator));
+ mDesktopTasksController, mMainExecutor, mStageCoordinator,
+ appsSupportingMultiInstance));
}
@Test
@@ -200,7 +213,7 @@
@Test
public void startIntent_multiInstancesSupported_appendsMultipleTaskFag() {
- doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
+ doReturn(true).when(mSplitScreenController).supportsMultiInstanceSplit(any());
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
@@ -237,12 +250,13 @@
verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
isNull());
- verify(mSplitScreenController, never()).supportMultiInstancesSplit(any());
+ verify(mSplitScreenController, never()).supportsMultiInstanceSplit(any());
verify(mStageCoordinator, never()).switchSplitPosition(any());
}
@Test
public void startIntent_multiInstancesSupported_startTaskInBackgroundAfterSplitActivated() {
+ doReturn(true).when(mSplitScreenController).supportsMultiInstanceSplit(any());
doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
@@ -259,14 +273,14 @@
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
SPLIT_POSITION_TOP_OR_LEFT, null);
- verify(mSplitScreenController, never()).supportMultiInstancesSplit(any());
+ verify(mSplitScreenController, never()).supportsMultiInstanceSplit(any());
verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
isNull());
}
@Test
public void startIntent_multiInstancesNotSupported_switchesPositionAfterSplitActivated() {
- doReturn(false).when(mSplitScreenController).supportMultiInstancesSplit(any());
+ doReturn(false).when(mSplitScreenController).supportsMultiInstanceSplit(any());
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
@@ -283,6 +297,130 @@
verify(mStageCoordinator).switchSplitPosition(anyString());
}
+ @Test
+ public void supportsMultiInstanceSplit_inStaticAllowList() {
+ String[] allowList = { TEST_PACKAGE };
+ SplitScreenController controller = new SplitScreenController(mContext, mShellInit,
+ mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
+ mRootTDAOrganizer, mDisplayController, mDisplayImeController,
+ mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
+ mIconProvider, mRecentTasks, mLaunchAdjacentController, mWindowDecorViewModel,
+ mDesktopTasksController, mMainExecutor, mStageCoordinator,
+ allowList);
+ ComponentName component = new ComponentName(TEST_PACKAGE, TEST_ACTIVITY);
+ assertEquals(true, controller.supportsMultiInstanceSplit(component));
+ }
+
+ @Test
+ public void supportsMultiInstanceSplit_notInStaticAllowList() {
+ String[] allowList = { TEST_PACKAGE };
+ SplitScreenController controller = new SplitScreenController(mContext, mShellInit,
+ mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
+ mRootTDAOrganizer, mDisplayController, mDisplayImeController,
+ mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
+ mIconProvider, mRecentTasks, mLaunchAdjacentController, mWindowDecorViewModel,
+ mDesktopTasksController, mMainExecutor, mStageCoordinator,
+ allowList);
+ ComponentName component = new ComponentName(TEST_NOT_ALLOWED_PACKAGE, TEST_ACTIVITY);
+ assertEquals(false, controller.supportsMultiInstanceSplit(component));
+ }
+
+ @Test
+ public void supportsMultiInstanceSplit_activityPropertyTrue()
+ throws PackageManager.NameNotFoundException {
+ Context context = spy(mContext);
+ ComponentName component = new ComponentName(TEST_PACKAGE, TEST_ACTIVITY);
+ PackageManager pm = mock(PackageManager.class);
+ doReturn(pm).when(context).getPackageManager();
+ PackageManager.Property activityProp = new PackageManager.Property("", true, "", "");
+ doReturn(activityProp).when(pm).getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component));
+ PackageManager.Property appProp = new PackageManager.Property("", false, "", "");
+ doReturn(appProp).when(pm).getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component.getPackageName()));
+
+ SplitScreenController controller = new SplitScreenController(context, mShellInit,
+ mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
+ mRootTDAOrganizer, mDisplayController, mDisplayImeController,
+ mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
+ mIconProvider, mRecentTasks, mLaunchAdjacentController, mWindowDecorViewModel,
+ mDesktopTasksController, mMainExecutor, mStageCoordinator,
+ new String[0]);
+ // Expect activity property to override application property
+ assertEquals(true, controller.supportsMultiInstanceSplit(component));
+ }
+
+ @Test
+ public void supportsMultiInstanceSplit_activityPropertyFalseApplicationPropertyTrue()
+ throws PackageManager.NameNotFoundException {
+ Context context = spy(mContext);
+ ComponentName component = new ComponentName(TEST_PACKAGE, TEST_ACTIVITY);
+ PackageManager pm = mock(PackageManager.class);
+ doReturn(pm).when(context).getPackageManager();
+ PackageManager.Property activityProp = new PackageManager.Property("", false, "", "");
+ doReturn(activityProp).when(pm).getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component));
+ PackageManager.Property appProp = new PackageManager.Property("", true, "", "");
+ doReturn(appProp).when(pm).getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component.getPackageName()));
+
+ SplitScreenController controller = new SplitScreenController(context, mShellInit,
+ mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
+ mRootTDAOrganizer, mDisplayController, mDisplayImeController,
+ mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
+ mIconProvider, mRecentTasks, mLaunchAdjacentController, mWindowDecorViewModel,
+ mDesktopTasksController, mMainExecutor, mStageCoordinator,
+ new String[0]);
+ // Expect activity property to override application property
+ assertEquals(false, controller.supportsMultiInstanceSplit(component));
+ }
+
+ @Test
+ public void supportsMultiInstanceSplit_noActivityPropertyApplicationPropertyTrue()
+ throws PackageManager.NameNotFoundException {
+ Context context = spy(mContext);
+ ComponentName component = new ComponentName(TEST_PACKAGE, TEST_ACTIVITY);
+ PackageManager pm = mock(PackageManager.class);
+ doReturn(pm).when(context).getPackageManager();
+ doThrow(PackageManager.NameNotFoundException.class).when(pm).getProperty(
+ eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), eq(component));
+ PackageManager.Property appProp = new PackageManager.Property("", true, "", "");
+ doReturn(appProp).when(pm).getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component.getPackageName()));
+
+ SplitScreenController controller = new SplitScreenController(context, mShellInit,
+ mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
+ mRootTDAOrganizer, mDisplayController, mDisplayImeController,
+ mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
+ mIconProvider, mRecentTasks, mLaunchAdjacentController, mWindowDecorViewModel,
+ mDesktopTasksController, mMainExecutor, mStageCoordinator,
+ new String[0]);
+ // Expect fall through to app property
+ assertEquals(true, controller.supportsMultiInstanceSplit(component));
+ }
+
+ @Test
+ public void supportsMultiInstanceSplit_noActivityOrAppProperty()
+ throws PackageManager.NameNotFoundException {
+ Context context = spy(mContext);
+ ComponentName component = new ComponentName(TEST_PACKAGE, TEST_ACTIVITY);
+ PackageManager pm = mock(PackageManager.class);
+ doReturn(pm).when(context).getPackageManager();
+ doThrow(PackageManager.NameNotFoundException.class).when(pm).getProperty(
+ eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), eq(component));
+ doThrow(PackageManager.NameNotFoundException.class).when(pm).getProperty(
+ eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), eq(component.getPackageName()));
+
+ SplitScreenController controller = new SplitScreenController(context, mShellInit,
+ mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
+ mRootTDAOrganizer, mDisplayController, mDisplayImeController,
+ mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
+ mIconProvider, mRecentTasks, mLaunchAdjacentController, mWindowDecorViewModel,
+ mDesktopTasksController, mMainExecutor, mStageCoordinator,
+ new String[0]);
+ assertEquals(false, controller.supportsMultiInstanceSplit(component));
+ }
+
private Intent createStartIntent(String activityName) {
Intent intent = new Intent();
intent.setComponent(new ComponentName(mContext, activityName));
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 883c24e..f84685a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -27,7 +27,6 @@
import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay
import android.os.Handler
-import android.os.IBinder
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.Choreographer
@@ -40,8 +39,6 @@
import android.view.SurfaceView
import android.view.WindowInsets.Type.navigationBars
import android.view.WindowInsets.Type.statusBars
-import android.view.WindowManager
-import android.window.TransitionInfo
import androidx.test.filters.SmallTest
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
@@ -53,8 +50,6 @@
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopTasksController
-import com.android.wm.shell.recents.RecentsTransitionHandler
-import com.android.wm.shell.recents.RecentsTransitionStateListener
import com.android.wm.shell.sysui.KeyguardChangeListener
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
@@ -100,7 +95,6 @@
@Mock private lateinit var mockShellController: ShellController
@Mock private lateinit var mockShellExecutor: ShellExecutor
@Mock private lateinit var mockRootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
- @Mock private lateinit var mockRecentsTransitionHandler: RecentsTransitionHandler
@Mock private lateinit var mockShellCommandHandler: ShellCommandHandler
private val transactionFactory = Supplier<SurfaceControl.Transaction> {
@@ -127,7 +121,6 @@
mockSyncQueue,
mockTransitions,
Optional.of(mockDesktopTasksController),
- mockRecentsTransitionHandler,
mockDesktopModeWindowDecorFactory,
mockInputMonitorFactory,
transactionFactory,
@@ -275,48 +268,6 @@
}
@Test
- fun testRelayoutBlockedDuringRecentsTransition() {
- val recentsCaptor = argumentCaptor<RecentsTransitionStateListener>()
- verify(mockRecentsTransitionHandler).addTransitionStateListener(recentsCaptor.capture())
-
- val transition = mock(IBinder::class.java)
- val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
- val decoration = setUpMockDecorationForTask(task)
-
- // Make sure a window decorations exists first by launching a freeform task.
- onTaskOpening(task)
- // Now call back when a Recents transition starts.
- recentsCaptor.firstValue.onTransitionStarted(transition)
-
- verify(decoration).incrementRelayoutBlock()
- verify(decoration).addTransitionPausingRelayout(transition)
- }
-
- @Test
- fun testRelayoutBlockedDuringKeyguardTransition() {
- val transition = mock(IBinder::class.java)
- val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
- val decoration = setUpMockDecorationForTask(task)
- val transitionInfo = mock(TransitionInfo::class.java)
- val transitionChange = mock(TransitionInfo.Change::class.java)
- val taskInfo = mock(RunningTaskInfo()::class.java)
-
- // Replicate a keyguard going away transition for a task
- whenever(transitionInfo.getFlags())
- .thenReturn(WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY)
- whenever(transitionChange.getMode()).thenReturn(WindowManager.TRANSIT_TO_FRONT)
- whenever(transitionChange.getTaskInfo()).thenReturn(taskInfo)
-
- // Make sure a window decorations exists first by launching a freeform task.
- onTaskOpening(task)
- // OnTransition ready is called when a keyguard going away transition happens
- desktopModeWindowDecorViewModel
- .onTransitionReady(transition, transitionInfo, transitionChange)
-
- verify(decoration).incrementRelayoutBlock()
- verify(decoration).addTransitionPausingRelayout(transition)
- }
- @Test
fun testRelayoutRunsWhenStatusBarsInsetsSourceVisibilityChanges() {
val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true)
val decoration = setUpMockDecorationForTask(task)
diff --git a/location/api/current.txt b/location/api/current.txt
index 0c23d8c..c55676b 100644
--- a/location/api/current.txt
+++ b/location/api/current.txt
@@ -414,7 +414,7 @@
field public static final int TYPE_GPS_L5CNAV = 259; // 0x103
field @FlaggedApi(Flags.FLAG_GNSS_API_NAVIC_L1) public static final int TYPE_IRN_L1 = 1795; // 0x703
field @FlaggedApi(Flags.FLAG_GNSS_API_NAVIC_L1) public static final int TYPE_IRN_L5 = 1794; // 0x702
- field @Deprecated public static final int TYPE_IRN_L5CA = 1793; // 0x701
+ field public static final int TYPE_IRN_L5CA = 1793; // 0x701
field public static final int TYPE_QZS_L1CA = 1025; // 0x401
field public static final int TYPE_SBS = 513; // 0x201
field public static final int TYPE_UNKNOWN = 0; // 0x0
diff --git a/location/java/android/location/GnssNavigationMessage.java b/location/java/android/location/GnssNavigationMessage.java
index 5e3f803..7a667ae 100644
--- a/location/java/android/location/GnssNavigationMessage.java
+++ b/location/java/android/location/GnssNavigationMessage.java
@@ -78,9 +78,7 @@
public static final int TYPE_GAL_F = 0x0602;
/**
* NavIC L5 C/A message contained in the structure.
- * @deprecated deprecated.
*/
- @Deprecated
public static final int TYPE_IRN_L5CA = 0x0701;
/** NavIC L5 message contained in the structure. */
@FlaggedApi(Flags.FLAG_GNSS_API_NAVIC_L1)
diff --git a/location/java/android/location/flags/gnss.aconfig b/location/java/android/location/flags/gnss.aconfig
index 794a555..5f1279e 100644
--- a/location/java/android/location/flags/gnss.aconfig
+++ b/location/java/android/location/flags/gnss.aconfig
@@ -41,3 +41,10 @@
description: "Flag for GNSS configuration from resource"
bug: "317734846"
}
+
+flag {
+ name: "replace_future_elapsed_realtime_jni"
+ namespace: "location"
+ description: "Flag for replacing future elapsedRealtime in JNI"
+ bug: "314328533"
+}
diff --git a/media/java/android/media/LoudnessCodecConfigurator.java b/media/java/android/media/LoudnessCodecConfigurator.java
index de9d87c0..aadd783 100644
--- a/media/java/android/media/LoudnessCodecConfigurator.java
+++ b/media/java/android/media/LoudnessCodecConfigurator.java
@@ -234,19 +234,21 @@
* @param mediaCodec the codec to start receiving asynchronous loudness
* updates. The codec has to be in a configured or started
* state in order to add it for loudness updates.
- * @throws IllegalArgumentException if the {@code mediaCodec} was not configured,
- * does not contain loudness metadata or if it
- * was already added before
+ * @throws IllegalArgumentException if the same {@code mediaCodec} was already
+ * added before.
+ * @return {@code false} if the {@code mediaCodec} was not configured or does
+ * not contain loudness metadata, {@code true} otherwise.
*/
@FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
- public void addMediaCodec(@NonNull MediaCodec mediaCodec) {
+ public boolean addMediaCodec(@NonNull MediaCodec mediaCodec) {
final MediaCodec mc = Objects.requireNonNull(mediaCodec,
"MediaCodec for addMediaCodec cannot be null");
int piid = PLAYER_PIID_INVALID;
final LoudnessCodecInfo mcInfo = getCodecInfo(mc);
if (mcInfo == null) {
- throw new IllegalArgumentException("Could not extract codec loudness information");
+ Log.v(TAG, "Could not extract codec loudness information");
+ return false;
}
synchronized (mConfiguratorLock) {
final AtomicBoolean containsCodec = new AtomicBoolean(false);
@@ -271,6 +273,8 @@
if (piid != PLAYER_PIID_INVALID) {
mLcDispatcher.addLoudnessCodecInfo(piid, mediaCodec.hashCode(), mcInfo);
}
+
+ return true;
}
/**
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index b7c97208..470a8ac 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -3401,13 +3401,15 @@
}
/**
- * Set a harware graphic buffer to this queue request. Exactly one buffer must
+ * Set a hardware graphic buffer to this queue request. Exactly one buffer must
* be set for a queue request before calling {@link #queue}.
* <p>
* Note: buffers should have format {@link HardwareBuffer#YCBCR_420_888},
* a single layer, and an appropriate usage ({@link HardwareBuffer#USAGE_CPU_READ_OFTEN}
* for software codecs and {@link HardwareBuffer#USAGE_VIDEO_ENCODE} for hardware)
- * for codecs to recognize. Codecs may throw exception if the buffer is not recognizable.
+ * for codecs to recognize. Format {@link ImageFormat#PRIVATE} together with
+ * usage {@link HardwareBuffer#USAGE_VIDEO_ENCODE} will also work for hardware codecs.
+ * Codecs may throw exception if the buffer is not recognizable.
*
* @param buffer The hardware graphic buffer object
* @return this object
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 60497fe..47637b8 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -15,7 +15,10 @@
*/
package android.media.session;
+import static com.android.media.flags.Flags.FLAG_ENABLE_NOTIFYING_ACTIVITY_MANAGER_WITH_MEDIA_SESSION_STATUS_CHANGE;
+
import android.annotation.DrawableRes;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.LongDef;
import android.annotation.Nullable;
@@ -189,7 +192,8 @@
*/
@IntDef({STATE_NONE, STATE_STOPPED, STATE_PAUSED, STATE_PLAYING, STATE_FAST_FORWARDING,
STATE_REWINDING, STATE_BUFFERING, STATE_ERROR, STATE_CONNECTING,
- STATE_SKIPPING_TO_PREVIOUS, STATE_SKIPPING_TO_NEXT, STATE_SKIPPING_TO_QUEUE_ITEM})
+ STATE_SKIPPING_TO_PREVIOUS, STATE_SKIPPING_TO_NEXT, STATE_SKIPPING_TO_QUEUE_ITEM,
+ STATE_PLAYBACK_SUPPRESSED})
@Retention(RetentionPolicy.SOURCE)
public @interface State {}
@@ -286,6 +290,19 @@
public static final int STATE_SKIPPING_TO_QUEUE_ITEM = 11;
/**
+ * State indicating that playback is paused due to an external transient interruption, like a
+ * phone call.
+ *
+ * <p>This state is different from {@link #STATE_PAUSED} in that it is deemed transitory,
+ * possibly allowing the service associated to the session in this state to run in the
+ * foreground.
+ *
+ * @see Builder#setState
+ */
+ @FlaggedApi(FLAG_ENABLE_NOTIFYING_ACTIVITY_MANAGER_WITH_MEDIA_SESSION_STATUS_CHANGE)
+ public static final int STATE_PLAYBACK_SUPPRESSED = 12;
+
+ /**
* Use this value for the position to indicate the position is not known.
*/
public static final long PLAYBACK_POSITION_UNKNOWN = -1;
@@ -384,6 +401,7 @@
* <li> {@link PlaybackState#STATE_SKIPPING_TO_PREVIOUS}</li>
* <li> {@link PlaybackState#STATE_SKIPPING_TO_NEXT}</li>
* <li> {@link PlaybackState#STATE_SKIPPING_TO_QUEUE_ITEM}</li>
+ * <li> {@link PlaybackState#STATE_PLAYBACK_SUPPRESSED}</li>
* </ul>
*/
@State
@@ -507,6 +525,7 @@
* <li>{@link #STATE_SKIPPING_TO_NEXT}</li>
* <li>{@link #STATE_SKIPPING_TO_PREVIOUS}</li>
* <li>{@link #STATE_SKIPPING_TO_QUEUE_ITEM}</li>
+ * <li>{@link #STATE_PLAYBACK_SUPPRESSED}</li>
* </ul>
*/
public boolean isActive() {
@@ -519,33 +538,12 @@
case PlaybackState.STATE_BUFFERING:
case PlaybackState.STATE_CONNECTING:
case PlaybackState.STATE_PLAYING:
+ case PlaybackState.STATE_PLAYBACK_SUPPRESSED:
return true;
}
return false;
}
- /**
- * Returns whether the service holding the media session should run in the foreground when the
- * media session has this playback state or not.
- *
- * @hide
- */
- public boolean shouldAllowServiceToRunInForeground() {
- switch (mState) {
- case PlaybackState.STATE_PLAYING:
- case PlaybackState.STATE_FAST_FORWARDING:
- case PlaybackState.STATE_REWINDING:
- case PlaybackState.STATE_BUFFERING:
- case PlaybackState.STATE_CONNECTING:
- case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
- case PlaybackState.STATE_SKIPPING_TO_NEXT:
- case PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM:
- return true;
- default:
- return false;
- }
- }
-
public static final @android.annotation.NonNull Parcelable.Creator<PlaybackState> CREATOR =
new Parcelable.Creator<PlaybackState>() {
@Override
@@ -586,6 +584,8 @@
return "SKIPPING_TO_NEXT";
case STATE_SKIPPING_TO_QUEUE_ITEM:
return "SKIPPING_TO_QUEUE_ITEM";
+ case STATE_PLAYBACK_SUPPRESSED:
+ return "STATE_PLAYBACK_SUPPRESSED";
default:
return "UNKNOWN";
}
@@ -823,6 +823,7 @@
* <li> {@link PlaybackState#STATE_SKIPPING_TO_PREVIOUS}</li>
* <li> {@link PlaybackState#STATE_SKIPPING_TO_NEXT}</li>
* <li> {@link PlaybackState#STATE_SKIPPING_TO_QUEUE_ITEM}</li>
+ * <li> {@link PlaybackState#STATE_PLAYBACK_SUPPRESSED}</li>
* </ul>
*
* @param state The current state of playback.
@@ -867,6 +868,7 @@
* <li> {@link PlaybackState#STATE_SKIPPING_TO_PREVIOUS}</li>
* <li> {@link PlaybackState#STATE_SKIPPING_TO_NEXT}</li>
* <li> {@link PlaybackState#STATE_SKIPPING_TO_QUEUE_ITEM}</li>
+ * <li> {@link PlaybackState#STATE_PLAYBACK_SUPPRESSED}</li>
* </ul>
*
* @param state The current state of playback.
diff --git a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java
index ce1004c..74e5612 100644
--- a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java
+++ b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java
@@ -19,6 +19,7 @@
import static android.media.audio.Flags.FLAG_LOUDNESS_CONFIGURATOR_API;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -191,6 +192,18 @@
@Test
@RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
+ public void addUnconfiguredMediaCodec_returnsFalse() throws Exception {
+ final MediaCodec mediaCodec = MediaCodec.createDecoderByType("audio/mpeg");
+
+ try {
+ assertFalse(mLcc.addMediaCodec(mediaCodec));
+ } finally {
+ mediaCodec.release();
+ }
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
public void setClearTrack_removeAllAudioServicePiidCodecs() throws Exception {
final ArgumentCaptor<List> argument = ArgumentCaptor.forClass(List.class);
final AudioTrack track = createAudioTrack();
diff --git a/packages/CompanionDeviceManager/res/values-ar/strings.xml b/packages/CompanionDeviceManager/res/values-ar/strings.xml
index 5b893b0..6ddd5d3 100644
--- a/packages/CompanionDeviceManager/res/values-ar/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ar/strings.xml
@@ -56,28 +56,18 @@
<string name="permission_nearby_devices" msgid="7530973297737123481">"Ø§ÙØ£Ø¬Ùزة اÙÙ
Ø¬Ø§ÙØ±Ø©"</string>
<string name="permission_media_routing_control" msgid="5498639511586715253">"تغÙÙØ± Ø¬ÙØ§Ø² إخراج اÙÙØ³Ø§ØŠØ·"</string>
<string name="permission_storage" msgid="6831099350839392343">"Ø§ÙØµÙر ÙØ§ÙÙØ³Ø§ØŠØ·"</string>
- <!-- no translation found for permission_notifications (4099418516590632909) -->
- <skip />
+ <string name="permission_notifications" msgid="4099418516590632909">"Ø§ÙØ¥ØŽØ¹Ø§Ø±Ø§Øª"</string>
<string name="permission_app_streaming" msgid="6009695219091526422">"Ø§ÙØªØ·ØšÙÙØ§Øª"</string>
<string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Ø§ÙØšØ«Ù"</string>
- <!-- no translation found for permission_phone_summary (8246321093970051702) -->
- <skip />
- <!-- no translation found for permission_call_logs_summary (7545243592757693321) -->
- <skip />
- <!-- no translation found for permission_sms_summary (8499509535410068616) -->
- <skip />
- <!-- no translation found for permission_contacts_summary (2840800622763086808) -->
- <skip />
- <!-- no translation found for permission_calendar_summary (8430353935747336165) -->
- <skip />
- <!-- no translation found for permission_microphone_summary (4862628553869973259) -->
- <skip />
- <!-- no translation found for permission_nearby_devices_summary (1306752848196464817) -->
- <skip />
- <!-- no translation found for permission_notification_listener_access_summary (7856071768185367749) -->
- <skip />
- <!-- no translation found for permission_notifications_summary (2272810466047367030) -->
- <skip />
+ <string name="permission_phone_summary" msgid="8246321093970051702">"إجراء اÙÙ
ÙØ§ÙÙ
ات اÙÙØ§ØªÙÙØ© ÙØ¥Ø¯Ø§Ø±ØªÙا"</string>
+ <string name="permission_call_logs_summary" msgid="7545243592757693321">"ÙØ±Ø§Ø¡Ø© سجÙ٠اÙÙ
ÙØ§ÙÙ
ات اÙÙØ§ØªÙÙØ© ÙØ§ÙÙØªØ§ØšØ© Ø¥ÙÙÙ"</string>
+ <string name="permission_sms_summary" msgid="8499509535410068616">"Ø¥Ø±Ø³Ø§Ù Ø§ÙØ±Ø³Ø§ØŠÙ اÙÙØµÙرة ÙØ¹Ø±Ø¶Ùا"</string>
+ <string name="permission_contacts_summary" msgid="2840800622763086808">"اÙÙØµÙ٠إÙÙ Ø¬ÙØ§Øª اتصاÙÙ"</string>
+ <string name="permission_calendar_summary" msgid="8430353935747336165">"اÙÙØµÙ٠إÙ٠تÙÙÙÙ
Ù"</string>
+ <string name="permission_microphone_summary" msgid="4862628553869973259">"تسجÙÙ Ø§ÙØµÙت"</string>
+ <string name="permission_nearby_devices_summary" msgid="1306752848196464817">"ÙÙ
ÙÙ Ø§ÙØ¹Ø«Ùر عÙ٠اÙÙ
ÙØ¶Ø¹ اÙÙØ³ØšÙ ÙÙØ£Ø¬Ùزة اÙÙ
Ø¬Ø§ÙØ±Ø© ÙØ§Ùرؚط ØšÙØ§ ÙØªØØ¯ÙØ¯ÙØ§."</string>
+ <string name="permission_notification_listener_access_summary" msgid="7856071768185367749">"ÙÙ
ÙÙ ÙÙØ°Ø§ اÙÙ
ÙÙ Ø§ÙØŽØ®ØµÙ ÙØ±Ø§Ø¡Ø© جÙ
ÙØ¹ Ø§ÙØ¥ØŽØ¹Ø§Ø±Ø§ØªØ ØšÙ
ا Ù٠ذÙ٠اÙÙ
عÙÙÙ
Ø§ØªØ Ù
Ø«Ù Ø¬ÙØ§Øª Ø§ÙØ§ØªØµØ§Ù ÙØ§ÙØ±Ø³Ø§ØŠÙ ÙØ§ÙØµÙØ±."</string>
+ <string name="permission_notifications_summary" msgid="2272810466047367030">"• ÙØ±Ø§Ø¡Ø© ÙÙ Ø§ÙØ¥ØŽØ¹Ø§Ø±Ø§Øª ØšÙ
ا ÙÙÙØ§ اÙÙ
عÙÙÙ
Ø§ØªØ Ù
Ø«Ù Ø¬ÙØ§Øª Ø§ÙØ§ØªØµØ§Ù ÙØ§ÙØ±Ø³Ø§ØŠÙ ÙØ§ÙØµÙØ±<br/>• Ø¥Ø±Ø³Ø§Ù Ø§ÙØ¥ØŽØ¹Ø§Ø±Ø§Øª<br/><br/>ÙÙ
ÙÙ٠إدارة Ø§ÙØ¥Ø°Ù اÙÙ
Ù
ÙÙØ ÙÙØ°Ø§ Ø§ÙØªØ·ØšÙÙ ØšÙØ±Ø§Ø¡Ø© Ø§ÙØ¥ØŽØ¹Ø§Ø±Ø§Øª ÙØ¥Ø±Ø³Ø§ÙÙØ§ Ù٠أ٠ÙÙØª Ù
Ù Ø®ÙØ§Ù Ø§ÙØ¥Ø¹Ø¯Ø§Ø¯Ø§Øª > Ø§ÙØ¥ØŽØ¹Ø§Ø±Ø§Øª."</string>
<string name="permission_app_streaming_summary" msgid="606923325679670624">"ؚث تطؚÙÙØ§Øª ÙØ§ØªÙÙ"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
<string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"ØšØ«Ù Ø§ÙØªØ·ØšÙÙØ§Øª ÙÙ
ÙØ²Ø§Øª اÙÙØžØ§Ù
Ø§ÙØ£Ø®Ø±Ù Ù
Ù ÙØ§ØªÙÙ"</string>
diff --git a/packages/CredentialManager/res/drawable/more_horiz_24px.xml b/packages/CredentialManager/res/drawable/more_horiz_24px.xml
new file mode 100644
index 0000000..7b235f8
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/more_horiz_24px.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M240,560Q207,560 183.5,536.5Q160,513 160,480Q160,447 183.5,423.5Q207,400 240,400Q273,400 296.5,423.5Q320,447 320,480Q320,513 296.5,536.5Q273,560 240,560ZM480,560Q447,560 423.5,536.5Q400,513 400,480Q400,447 423.5,423.5Q447,400 480,400Q513,400 536.5,423.5Q560,447 560,480Q560,513 536.5,536.5Q513,560 480,560ZM720,560Q687,560 663.5,536.5Q640,513 640,480Q640,447 663.5,423.5Q687,400 720,400Q753,400 776.5,423.5Q800,447 800,480Q800,513 776.5,536.5Q753,560 720,560Z"/>
+</vector>
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index dfa5735..8ac364e7 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -305,6 +305,14 @@
var i = 0
var datasetAdded = false
+ val duplicateDisplayNames: MutableMap<String, Boolean> = mutableMapOf()
+ providerDisplayInfo.sortedUserNameToCredentialEntryList.forEach {
+ val credentialEntry = it.sortedCredentialEntryList.first()
+ credentialEntry.displayName?.let {displayName ->
+ val duplicateEntry = duplicateDisplayNames.contains(displayName)
+ duplicateDisplayNames[displayName] = duplicateEntry
+ }
+ }
providerDisplayInfo.sortedUserNameToCredentialEntryList.forEach usernameLoop@{
val primaryEntry = it.sortedCredentialEntryList.first()
val pendingIntent = primaryEntry.pendingIntent
@@ -339,10 +347,14 @@
} else {
spec = inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
}
+ val displayName : String = primaryEntry.displayName ?: primaryEntry.userName
val sliceBuilder = InlineSuggestionUi
.newContentBuilder(pendingIntent)
- .setTitle(primaryEntry.userName)
+ .setTitle(displayName)
sliceBuilder.setStartIcon(icon)
+ if (duplicateDisplayNames[displayName] == true) {
+ sliceBuilder.setSubtitle(primaryEntry.userName)
+ }
inlinePresentation = InlinePresentation(
sliceBuilder.build().slice, spec, /* pinned= */ false)
}
@@ -398,7 +410,7 @@
val sliceBuilder = InlineSuggestionUi
.newContentBuilder(bottomSheetPendingIntent)
.setStartIcon(Icon.createWithResource(this,
- com.android.credentialmanager.R.drawable.ic_other_sign_in_24))
+ com.android.credentialmanager.R.drawable.more_horiz_24px))
val presentationBuilder = Presentations.Builder()
.setInlinePresentation(InlinePresentation(
sliceBuilder.build().slice, spec, /* pinned= */ true))
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 2a7e9e1..59e6142 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -21,11 +21,14 @@
import androidx.lifecycle.viewModelScope
import com.android.credentialmanager.model.Request
import com.android.credentialmanager.client.CredentialManagerClient
+import com.android.credentialmanager.model.get.ActionEntryInfo
+import com.android.credentialmanager.model.get.CredentialEntryInfo
import com.android.credentialmanager.ui.mappers.toGet
import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import javax.inject.Inject
@@ -33,15 +36,15 @@
class CredentialSelectorViewModel @Inject constructor(
private val credentialManagerClient: CredentialManagerClient,
) : ViewModel() {
-
+ private val isPrimaryScreen = MutableStateFlow(false)
val uiState: StateFlow<CredentialSelectorUiState> = credentialManagerClient.requests
- .map { request ->
+ .combine(isPrimaryScreen) { request, isPrimary ->
when (request) {
null -> CredentialSelectorUiState.Idle
is Request.Cancel -> CredentialSelectorUiState.Cancel(request.appName)
is Request.Close -> CredentialSelectorUiState.Close
is Request.Create -> CredentialSelectorUiState.Create
- is Request.Get -> request.toGet()
+ is Request.Get -> request.toGet(isPrimary)
}
}
.stateIn(
@@ -57,9 +60,18 @@
sealed class CredentialSelectorUiState {
data object Idle : CredentialSelectorUiState()
- sealed class Get : CredentialSelectorUiState() {
- data object SingleProviderSinglePasskey : Get()
- data object SingleProviderSinglePassword : Get()
+ sealed class Get() : CredentialSelectorUiState() {
+ data class SingleEntry(val entry: CredentialEntryInfo) : Get()
+ data class SingleEntryPerAccount(val sortedEntries: List<CredentialEntryInfo>) : Get()
+ data class MultipleEntry(
+ val accounts: List<PerUserNameEntries>,
+ val actionEntryList: List<ActionEntryInfo>,
+ ) : Get() {
+ data class PerUserNameEntries(
+ val userName: String,
+ val sortedCredentialEntryList: List<CredentialEntryInfo>,
+ )
+ }
// TODO: b/301206470 add the remaining states
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
index 7e0ea30..790f5b1 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
@@ -26,6 +26,7 @@
import androidx.wear.compose.navigation.rememberSwipeDismissableNavController
import androidx.wear.compose.navigation.rememberSwipeDismissableNavHostState
import com.android.credentialmanager.CredentialSelectorUiState
+import com.android.credentialmanager.CredentialSelectorUiState.Get.SingleEntry
import com.android.credentialmanager.CredentialSelectorViewModel
import com.android.credentialmanager.ui.screens.LoadingScreen
import com.android.credentialmanager.ui.screens.single.password.SinglePasswordScreen
@@ -57,6 +58,7 @@
scrollable(Screen.SinglePasswordScreen.route) {
SinglePasswordScreen(
+ state = viewModel.uiState.value as SingleEntry,
columnState = it.columnState,
onCloseApp = onCloseApp,
)
@@ -100,7 +102,7 @@
onCloseApp: () -> Unit,
) {
when (state) {
- is CredentialSelectorUiState.Get.SingleProviderSinglePassword -> {
+ is SingleEntry -> {
navController.navigateToSinglePasswordScreen()
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
index 14b992a..44a838d 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
@@ -18,18 +18,45 @@
import com.android.credentialmanager.model.Request
import com.android.credentialmanager.CredentialSelectorUiState
+import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry.PerUserNameEntries
+import com.android.credentialmanager.model.CredentialType
+import com.android.credentialmanager.model.get.CredentialEntryInfo
-fun Request.Get.toGet(): CredentialSelectorUiState.Get {
+fun Request.Get.toGet(isPrimary: Boolean): CredentialSelectorUiState.Get {
// TODO: b/301206470 returning a hard coded state for MVP
- if (true) return CredentialSelectorUiState.Get.SingleProviderSinglePassword
-
- return if (providerInfos.size == 1) {
- if (providerInfos.first().credentialEntryList.size == 1) {
- CredentialSelectorUiState.Get.SingleProviderSinglePassword
+ if (true) return CredentialSelectorUiState.Get.SingleEntry(
+ providerInfos
+ .flatMap { it.credentialEntryList }
+ .first { it.credentialType == CredentialType.PASSWORD }
+ )
+ val accounts = providerInfos
+ .flatMap { it.credentialEntryList }
+ .groupBy { it.userName}
+ .entries
+ .toList()
+ return if (isPrimary) {
+ if (accounts.size == 1) {
+ CredentialSelectorUiState.Get.SingleEntry(
+ accounts[0].value.minWith(comparator)
+ )
} else {
- TODO() // b/301206470 - Implement other get flows
+ CredentialSelectorUiState.Get.SingleEntryPerAccount(
+ accounts.map { it.value.minWith(comparator) }.sortedWith(comparator)
+ )
}
} else {
- TODO() // b/301206470 - Implement other get flows
+ CredentialSelectorUiState.Get.MultipleEntry(
+ accounts = accounts.map { PerUserNameEntries(
+ it.key,
+ it.value.sortedWith(comparator)
+ )
+ },
+ actionEntryList = providerInfos.flatMap { it.actionEntryList },
+ )
}
}
+val comparator = compareBy<CredentialEntryInfo> { entryInfo ->
+ // Passkey type always go first
+ entryInfo.credentialType.let{ if (it == CredentialType.PASSKEY) 0 else 1 }
+}
+ .thenByDescending{ it.lastUsedTimeMillis }
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
index b64f581..9f971ae 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
@@ -29,6 +29,7 @@
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.credentialmanager.CredentialSelectorUiState.Get.SingleEntry
import com.android.credentialmanager.R
import com.android.credentialmanager.TAG
import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
@@ -44,12 +45,13 @@
@Composable
fun SinglePasswordScreen(
+ state: SingleEntry,
columnState: ScalingLazyColumnState,
onCloseApp: () -> Unit,
modifier: Modifier = Modifier,
viewModel: SinglePasswordScreenViewModel = hiltViewModel(),
) {
- viewModel.initialize()
+ viewModel.initialize(state.entry)
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
index 26bee1f..4f9fc46 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
@@ -19,12 +19,9 @@
import android.content.Intent
import android.credentials.ui.ProviderPendingIntentResponse
import android.credentials.ui.UserSelectionDialogResult
-import android.util.Log
import androidx.activity.result.IntentSenderRequest
import androidx.annotation.MainThread
import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import com.android.credentialmanager.TAG
import com.android.credentialmanager.ktx.getIntentSenderRequest
import com.android.credentialmanager.model.Request
import com.android.credentialmanager.client.CredentialManagerClient
@@ -33,7 +30,6 @@
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
@@ -51,32 +47,14 @@
val uiState: StateFlow<SinglePasswordScreenUiState> = _uiState
@MainThread
- fun initialize() {
+ fun initialize(entryInfo: CredentialEntryInfo) {
if (initializeCalled) return
initializeCalled = true
-
- viewModelScope.launch {
- val request = credentialManagerClient.requests.value
- Log.d(TAG, "request: $request, client instance: $credentialManagerClient")
-
- if (request !is Request.Get) {
- _uiState.value = SinglePasswordScreenUiState.Error
- } else {
- requestGet = request
-
- if (requestGet.providerInfos.all { it.credentialEntryList.isEmpty() }) {
- Log.d(TAG, "Empty passwordEntries")
- _uiState.value = SinglePasswordScreenUiState.Error
- } else {
- entryInfo = requestGet.providerInfos.first().credentialEntryList.first()
- _uiState.value = SinglePasswordScreenUiState.Loaded(
- PasswordUiModel(
- email = entryInfo.userName,
- )
- )
- }
- }
- }
+ _uiState.value = SinglePasswordScreenUiState.Loaded(
+ PasswordUiModel(
+ email = entryInfo.userName,
+ )
+ )
}
fun onCancelClick() {
diff --git a/packages/InputDevices/res/values-uk/strings.xml b/packages/InputDevices/res/values-uk/strings.xml
index 5368f2c..71b3496 100644
--- a/packages/InputDevices/res/values-uk/strings.xml
+++ b/packages/InputDevices/res/values-uk/strings.xml
@@ -3,49 +3,49 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="8016145283189546017">"ÐÑОÑÑÑÐŸÑ Ð²Ð²ÐŸÐŽÑ"</string>
<string name="keyboard_layouts_label" msgid="6688773268302087545">"ÐлавÑаÑÑÑа Android"</string>
- <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"аМглÑйÑÑка (ÐелОка ÐÑОÑаМÑÑ)"</string>
- <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"аМглÑйÑÑка (СКÐ)"</string>
- <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"аМглÑйÑÑка (СКÐ), ÐŒÑжМаÑПЎМа"</string>
- <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"аМглÑйÑÑка (СКÐ), ÑПзклаЎка Colemak"</string>
- <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"аМглÑйÑÑка (СКÐ), ÑПзклаЎка ÐвПÑака"</string>
- <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"аМглÑйÑÑка (СКÐ), ÑПзклаЎка Workman"</string>
- <string name="keyboard_layout_german_label" msgid="8451565865467909999">"МÑЌеÑÑка"</string>
- <string name="keyboard_layout_french_label" msgid="813450119589383723">"ÑÑаМÑÑзÑка"</string>
- <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ÑÑаМÑÑзÑка (ÐаМаЎа)"</string>
- <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"ÑПÑÑйÑÑка"</string>
- <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"ÑПÑÑйÑÑка, ÑПзклаЎка Mac"</string>
- <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"ÑÑпаМÑÑка"</string>
- <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"ÑÑаМÑÑзÑка (КвейÑаÑÑÑ)"</string>
- <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"МÑЌеÑÑка (КвейÑаÑÑÑ)"</string>
- <string name="keyboard_layout_belgian" msgid="2011984572838651558">"белÑгÑйÑÑка"</string>
- <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"бПлгаÑÑÑка"</string>
+ <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"ÐМглÑйÑÑка (ÐелОка ÐÑОÑаМÑÑ)"</string>
+ <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"ÐМглÑйÑÑка (СКÐ)"</string>
+ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ÐМглÑйÑÑка (СКÐ), ÐŒÑжМаÑПЎМа"</string>
+ <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ÐМглÑйÑÑка (СКÐ), ÑПзклаЎка Colemak"</string>
+ <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ÐМглÑйÑÑка (СКÐ), ÑПзклаЎка ÐвПÑака"</string>
+ <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"ÐМглÑйÑÑка (СКÐ), ÑПзклаЎка Workman"</string>
+ <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ÐÑЌеÑÑка"</string>
+ <string name="keyboard_layout_french_label" msgid="813450119589383723">"ЀÑаМÑÑзÑка"</string>
+ <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ЀÑаМÑÑзÑка (ÐаМаЎа)"</string>
+ <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"РПÑÑйÑÑка"</string>
+ <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"РПÑÑйÑÑка, ÑПзклаЎка Mac"</string>
+ <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"ÐÑпаМÑÑка"</string>
+ <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"ЀÑаМÑÑзÑка (КвейÑаÑÑÑ)"</string>
+ <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"ÐÑЌеÑÑка (КвейÑаÑÑÑ)"</string>
+ <string name="keyboard_layout_belgian" msgid="2011984572838651558">"ÐелÑгÑйÑÑка"</string>
+ <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"ÐПлгаÑÑÑка"</string>
<string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"ÐПлгаÑÑÑка (ÑПМеÑОÑМа)"</string>
- <string name="keyboard_layout_italian" msgid="6497079660449781213">"ÑÑалÑйÑÑка"</string>
- <string name="keyboard_layout_danish" msgid="8036432066627127851">"ЎаМÑÑка"</string>
- <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"МПÑвезÑка"</string>
- <string name="keyboard_layout_swedish" msgid="732959109088479351">"ÑвеЎÑÑка"</string>
- <string name="keyboard_layout_finnish" msgid="5585659438924315466">"ÑÑМÑÑка"</string>
- <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Ñ
ПÑваÑÑÑка"</string>
- <string name="keyboard_layout_czech" msgid="1349256901452975343">"ÑеÑÑка"</string>
+ <string name="keyboard_layout_italian" msgid="6497079660449781213">"ÐÑалÑйÑÑка"</string>
+ <string name="keyboard_layout_danish" msgid="8036432066627127851">"ÐаМÑÑка"</string>
+ <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"ÐПÑвезÑка"</string>
+ <string name="keyboard_layout_swedish" msgid="732959109088479351">"КвеЎÑÑка"</string>
+ <string name="keyboard_layout_finnish" msgid="5585659438924315466">"ЀÑМÑÑка"</string>
+ <string name="keyboard_layout_croatian" msgid="4172229471079281138">"ХПÑваÑÑÑка"</string>
+ <string name="keyboard_layout_czech" msgid="1349256901452975343">"ЧеÑÑка"</string>
<string name="keyboard_layout_czech_qwerty" msgid="3331402534128515501">"ЧеÑÑка (QWERTY)"</string>
- <string name="keyboard_layout_estonian" msgid="8775830985185665274">"еÑÑПМÑÑка"</string>
- <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"ÑгПÑÑÑка"</string>
- <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"ÑÑлаМЎÑÑка"</string>
- <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"бÑазОлÑÑÑка"</string>
- <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"пПÑÑÑгалÑÑÑка"</string>
- <string name="keyboard_layout_slovak" msgid="2469379934672837296">"ÑлПваÑÑка"</string>
- <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"ÑлПвеМÑÑка"</string>
- <string name="keyboard_layout_turkish" msgid="7736163250907964898">"ÑÑÑеÑÑка"</string>
+ <string name="keyboard_layout_estonian" msgid="8775830985185665274">"ÐÑÑПМÑÑка"</string>
+ <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"УгПÑÑÑка"</string>
+ <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"ÐÑлаМЎÑÑка"</string>
+ <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"ÐÑазОлÑÑÑка"</string>
+ <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"ÐПÑÑÑгалÑÑÑка"</string>
+ <string name="keyboard_layout_slovak" msgid="2469379934672837296">"СлПваÑÑка"</string>
+ <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"СлПвеМÑÑка"</string>
+ <string name="keyboard_layout_turkish" msgid="7736163250907964898">"ТÑÑеÑÑка"</string>
<string name="keyboard_layout_turkish_f" msgid="9130320856010776018">"ТÑÑеÑÑка-F"</string>
- <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"ÑкÑаÑМÑÑка"</string>
- <string name="keyboard_layout_arabic" msgid="5671970465174968712">"аÑабÑÑка"</string>
+ <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"УкÑаÑМÑÑка"</string>
+ <string name="keyboard_layout_arabic" msgid="5671970465174968712">"ÐÑабÑÑка"</string>
<string name="keyboard_layout_greek" msgid="7289253560162386040">"ÐÑеÑÑка"</string>
<string name="keyboard_layout_hebrew" msgid="7241473985890173812">"ÐвÑОÑ"</string>
<string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"ÐОÑПвÑÑка"</string>
<string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"ÐÑпаМÑÑка (лаÑОМОÑÑ)"</string>
<string name="keyboard_layout_latvian" msgid="4405417142306250595">"ÐаÑвÑйÑÑка"</string>
<string name="keyboard_layout_persian" msgid="3920643161015888527">"ÐеÑÑÑка"</string>
- <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"азеÑбайЎжаМÑÑка"</string>
+ <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"ÐзеÑбайЎжаМÑÑка"</string>
<string name="keyboard_layout_polish" msgid="1121588624094925325">"ÐПлÑÑÑка"</string>
<string name="keyboard_layout_belarusian" msgid="7619281752698687588">"ÐÑлПÑÑÑÑка"</string>
<string name="keyboard_layout_mongolian" msgid="7678483495823936626">"ÐПМгПлÑÑÑка"</string>
diff --git a/packages/PackageInstaller/res/values-ar/strings.xml b/packages/PackageInstaller/res/values-ar/strings.xml
index e4da5b7..224c6dc 100644
--- a/packages/PackageInstaller/res/values-ar/strings.xml
+++ b/packages/PackageInstaller/res/values-ar/strings.xml
@@ -44,8 +44,7 @@
<string name="unknown_apps_user_restriction_dlg_text" msgid="151020786933988344">"ÙØªØ¹Ø°Ø± عÙÙ ÙØ°Ø§ اÙÙ
ستخدÙ
ØªØ«ØšÙØª Ø§ÙØªØ·ØšÙÙØ§Øª ØºÙØ± اÙÙ
عرÙÙØ©"</string>
<string name="install_apps_user_restriction_dlg_text" msgid="2154119597001074022">"ØºÙØ± Ù
سÙ
ÙØ ÙÙØ°Ø§ اÙÙ
ستخدÙ
ØšØªØ«ØšÙØª Ø§ÙØªØ·ØšÙÙØ§Øª"</string>
<string name="ok" msgid="7871959885003339302">"ØØ³ÙÙØ§"</string>
- <!-- no translation found for archive (4447791830199354721) -->
- <skip />
+ <string name="archive" msgid="4447791830199354721">"Ø£Ø±ØŽÙØ©"</string>
<string name="update_anyway" msgid="8792432341346261969">"Ø§ÙØªØØ¯ÙØ« عÙÙ Ø£Ù ØØ§Ù"</string>
<string name="manage_applications" msgid="5400164782453975580">"إدارة Ø§ÙØªØ·ØšÙÙØ§Øª"</string>
<string name="out_of_space_dlg_title" msgid="4156690013884649502">"ÙÙØ¯Øª Ù
Ø³Ø§ØØ© Ø§ÙØªØ®Ø²ÙÙ"</string>
@@ -60,16 +59,11 @@
<string name="uninstall_update_title" msgid="824411791011583031">"Ø¥Ø²Ø§ÙØ© Ø§ÙØªØØ¯ÙØ«"</string>
<string name="uninstall_activity_text" msgid="1928194674397770771">"<xliff:g id="ACTIVITY_NAME">%1$s</xliff:g> Ù٠جزء Ù
Ù Ø§ÙØªØ·ØšÙÙ Ø§ÙØªØ§ÙÙ:"</string>
<string name="uninstall_application_text" msgid="3816830743706143980">"ÙÙ ØªØ±ÙØ¯ Ø¥Ø²Ø§ÙØ© ÙØ°Ø§ Ø§ÙØªØ·ØšÙÙØ"</string>
- <!-- no translation found for archive_application_text (8482325710714386348) -->
- <skip />
- <!-- no translation found for archive_application_text_all_users (3151229641681672580) -->
- <skip />
- <!-- no translation found for archive_application_text_current_user_work_profile (1450487362134779752) -->
- <skip />
- <!-- no translation found for archive_application_text_user (2586558895535581451) -->
- <skip />
- <!-- no translation found for archive_application_text_current_user_private_profile (1958423158655599132) -->
- <skip />
+ <string name="archive_application_text" msgid="8482325710714386348">"Ø³ÙØªÙ
ØÙØž ØšÙØ§ÙØ§ØªÙ Ø§ÙØŽØ®ØµÙØ©."</string>
+ <string name="archive_application_text_all_users" msgid="3151229641681672580">"ÙÙ ØªØ±ÙØ¯ Ø£Ø±ØŽÙØ© ÙØ°Ø§ Ø§ÙØªØ·ØšÙÙ ÙØ¬Ù
ÙØ¹ اÙÙ
ستخدÙ
ÙÙØ Ø³ÙØªÙ
ØÙØž ØšÙØ§ÙØ§ØªÙ Ø§ÙØŽØ®ØµÙØ©."</string>
+ <string name="archive_application_text_current_user_work_profile" msgid="1450487362134779752">"ÙÙ ØªØ±ÙØ¯ Ø£Ø±ØŽÙØ© ÙØ°Ø§ Ø§ÙØªØ·ØšÙÙ ÙÙ Ù
ÙÙ Ø§ÙØ¹Ù
ÙØ Ø³ÙØªÙ
ØÙØž ØšÙØ§ÙØ§ØªÙ Ø§ÙØŽØ®ØµÙØ©."</string>
+ <string name="archive_application_text_user" msgid="2586558895535581451">"ÙÙ ØªØ±ÙØ¯ Ø£Ø±ØŽÙØ© ÙØ°Ø§ Ø§ÙØªØ·ØšÙÙ ÙÙ \"<xliff:g id="USERNAME">%1$s</xliff:g>\"Ø Ø³ÙØªÙ
ØÙØž ØšÙØ§ÙØ§ØªÙ Ø§ÙØŽØ®ØµÙØ©."</string>
+ <string name="archive_application_text_current_user_private_profile" msgid="1958423158655599132">"ÙÙ ØªØ±ÙØ¯ Ø£Ø±ØŽÙØ© ÙØ°Ø§ Ø§ÙØªØ·ØšÙ٠اÙÙ
ØÙÙØž Ù٠اÙÙ
Ø³Ø§ØØ© Ø§ÙØ®Ø§ØµÙØ©Ø Ø³ÙØªÙ
ØÙØž ØšÙØ§ÙØ§ØªÙ Ø§ÙØŽØ®ØµÙØ©."</string>
<string name="uninstall_application_text_all_users" msgid="575491774380227119">"ÙÙ ØªØ±ÙØ¯ Ø¥Ø²Ø§ÙØ© ÙØ°Ø§ Ø§ÙØªØ·ØšÙÙ "<b>"ÙÙÙ"</b>" اÙÙ
ستخدÙ
ÙÙØ ستتÙ
Ø¥Ø²Ø§ÙØ© Ø§ÙØªØ·ØšÙÙ ÙØšÙØ§ÙØ§ØªÙ Ù
Ù "<b>"ÙÙ"</b>" اÙÙ
ستخدÙ
Ù٠عÙÙ ÙØ°Ø§ Ø§ÙØ¬Ùاز."</string>
<string name="uninstall_application_text_user" msgid="498072714173920526">"ÙÙ ØªØ±ÙØ¯ Ø¥Ø²Ø§ÙØ© ÙØ°Ø§ Ø§ÙØªØ·ØšÙÙ ÙÙÙ
ستخدÙ
<xliff:g id="USERNAME">%1$s</xliff:g>Ø"</string>
<string name="uninstall_application_text_current_user_work_profile" msgid="8788387739022366193">"ÙÙ ØªØ±ÙØ¯ Ø¥Ø²Ø§ÙØ© ØªØ«ØšÙØª ÙØ°Ø§ Ø§ÙØªØ·ØšÙÙ Ù
Ù Ù
ÙÙÙ Ø§ÙØŽØ®ØµÙ ÙÙØ¹Ù
ÙØ"</string>
@@ -108,8 +102,7 @@
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"ÙØ¹ØªØšØ± Ø§ÙØ¬Ùاز اÙÙÙØÙ ÙØ§ÙØšÙØ§Ùات Ø§ÙØŽØ®ØµÙØ© Ø£ÙØ«Ø± عرضة ÙÙØ¬ÙÙ
Ø§ÙØªØ·ØšÙÙØ§Øª ØºÙØ± اÙÙ
عرÙÙØ©. Ù
Ù Ø®ÙØ§Ù ØªØ«ØšÙØª ÙØ°Ø§ Ø§ÙØªØ·ØšÙÙØ ØªÙØ§Ù٠عÙ٠تØÙ
Ù Ù
س؀ÙÙÙØ© أ٠ضرر ÙØØ¯Ø« ÙÙØ¬Ùاز اÙÙÙØÙ Ø£Ù ÙÙØ¯Ø§Ù Ø§ÙØšÙØ§ÙØ§Øª Ø§ÙØ°Ù ÙØ¯ ÙÙØªØ¬ ع٠استخداÙ
Ù."</string>
<string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"ÙØ¹ØªØšØ± Ø¬ÙØ§Ø² Ø§ÙØªÙÙØ²ÙÙÙ ÙØ§ÙØšÙØ§Ùات Ø§ÙØŽØ®ØµÙØ© Ø£ÙØ«Ø± عرضة ÙÙØ¬ÙÙ
Ø§ÙØªØ·ØšÙÙØ§Øª ØºÙØ± اÙÙ
عرÙÙØ©. Ù
Ù Ø®ÙØ§Ù ØªØ«ØšÙØª ÙØ°Ø§ Ø§ÙØªØ·ØšÙÙØ ØªÙØ§Ù٠عÙ٠تØÙ
Ù Ù
س؀ÙÙÙØ© أ٠ضرر ÙØØ¯Ø« ÙØ¬Ùاز Ø§ÙØªÙÙØ²ÙÙ٠أ٠ÙÙØ¯Ø§Ù Ø§ÙØšÙØ§ÙØ§Øª Ø§ÙØ°Ù ÙØ¯ ÙÙØªØ¬ ع٠استخداÙ
Ù."</string>
<string name="cloned_app_label" msgid="7503612829833756160">"ÙØ³Ø®Ø© Ø·ØšÙ Ø§ÙØ£ØµÙ Ù
Ù \"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>\""</string>
- <!-- no translation found for archiving_app_label (1127085259724124725) -->
- <skip />
+ <string name="archiving_app_label" msgid="1127085259724124725">"ÙÙ ØªØ±ÙØ¯ Ø£Ø±ØŽÙØ© <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>Ø"</string>
<string name="anonymous_source_continue" msgid="4375745439457209366">"Ù
تاؚعة"</string>
<string name="external_sources_settings" msgid="4046964413071713807">"Ø§ÙØ¥Ø¹Ø¯Ø§Ø¯Ø§Øª"</string>
<string name="wear_app_channel" msgid="1960809674709107850">"ØªØ«ØšÙØª / Ø¥ÙØºØ§Ø¡ ØªØ«ØšÙØª تطؚÙÙØ§Øª Android Wear"</string>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java
index 754437e..b5af845 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java
@@ -76,7 +76,7 @@
boolean hasRequestInstallPermission = Arrays.asList(getRequestedPermissions(callingPackage))
.contains(permission.REQUEST_INSTALL_PACKAGES);
boolean hasInstallPermission = getBaseContext().checkPermission(permission.INSTALL_PACKAGES,
- 0 /* random value for pid */, callingUid) != PackageManager.PERMISSION_GRANTED;
+ 0 /* random value for pid */, callingUid) == PackageManager.PERMISSION_GRANTED;
if (!hasRequestInstallPermission && !hasInstallPermission) {
Log.e(TAG, "Uid " + callingUid + " does not have "
+ permission.REQUEST_INSTALL_PACKAGES + " or "
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveFragment.java
index 6ccbc4c..42dd382 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveFragment.java
@@ -28,12 +28,13 @@
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
String appTitle = getArguments().getString(UnarchiveActivity.APP_TITLE);
+ String installerTitle = getArguments().getString(UnarchiveActivity.INSTALLER_TITLE);
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity());
dialogBuilder.setTitle(
String.format(getContext().getString(R.string.unarchive_application_title),
- appTitle));
+ appTitle, installerTitle));
dialogBuilder.setMessage(R.string.unarchive_body_text);
dialogBuilder.setPositiveButton(R.string.restore, this);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
index 326e533..aeabbd5 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
@@ -46,6 +46,7 @@
import com.android.packageinstaller.v2.model.InstallAborted.Companion.ABORT_REASON_DONE
import com.android.packageinstaller.v2.model.InstallAborted.Companion.ABORT_REASON_INTERNAL_ERROR
import com.android.packageinstaller.v2.model.InstallAborted.Companion.ABORT_REASON_POLICY
+import com.android.packageinstaller.v2.model.InstallAborted.Companion.DLG_NONE
import com.android.packageinstaller.v2.model.InstallAborted.Companion.DLG_PACKAGE_ERROR
import com.android.packageinstaller.v2.model.InstallUserActionRequired.Companion.USER_ACTION_REASON_ANONYMOUS_SOURCE
import com.android.packageinstaller.v2.model.InstallUserActionRequired.Companion.USER_ACTION_REASON_INSTALL_CONFIRMATION
@@ -283,14 +284,15 @@
createSessionParams(intent, pfd, uri.toString())
stagedSessionId = packageInstaller.createSession(params)
}
- } catch (e: IOException) {
+ } catch (e: Exception) {
Log.w(LOG_TAG, "Failed to create a staging session", e)
_stagingResult.value = InstallAborted(
ABORT_REASON_INTERNAL_ERROR,
resultIntent = Intent().putExtra(
Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_FAILED_INVALID_APK
),
- activityResultCode = Activity.RESULT_FIRST_USER
+ activityResultCode = Activity.RESULT_FIRST_USER,
+ errorDialogType = if (e is IOException) DLG_PACKAGE_ERROR else DLG_NONE
)
return
}
@@ -313,6 +315,14 @@
)
}
}
+ } else {
+ _stagingResult.value = InstallAborted(
+ ABORT_REASON_INTERNAL_ERROR,
+ resultIntent = Intent().putExtra(
+ Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_FAILED_INVALID_URI
+ ),
+ activityResultCode = Activity.RESULT_FIRST_USER
+ )
}
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt
index be49b39..bbb9bca 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt
@@ -122,13 +122,14 @@
*/
val resultIntent: Intent? = null,
val activityResultCode: Int = Activity.RESULT_CANCELED,
- val errorDialogType: Int? = 0,
+ val errorDialogType: Int? = DLG_NONE,
) : InstallStage(STAGE_ABORTED) {
companion object {
const val ABORT_REASON_INTERNAL_ERROR = 0
const val ABORT_REASON_POLICY = 1
const val ABORT_REASON_DONE = 2
+ const val DLG_NONE = 0
const val DLG_PACKAGE_ERROR = 1
}
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.kt
index c109fc6..1d4d178 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.kt
@@ -34,6 +34,8 @@
*/
fun onNegativeResponse(stageCode: Int)
+ fun onNegativeResponse(resultCode: Int, data: Intent?)
+
/**
* Launch the intent to open the newly installed / updated app.
*/
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt
index 2b610d7..6f8eca3 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt
@@ -36,10 +36,10 @@
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.ViewModelProvider
import com.android.packageinstaller.R
-import com.android.packageinstaller.v2.model.InstallRepository
import com.android.packageinstaller.v2.model.InstallAborted
import com.android.packageinstaller.v2.model.InstallFailed
import com.android.packageinstaller.v2.model.InstallInstalling
+import com.android.packageinstaller.v2.model.InstallRepository
import com.android.packageinstaller.v2.model.InstallStage
import com.android.packageinstaller.v2.model.InstallSuccess
import com.android.packageinstaller.v2.model.InstallUserActionRequired
@@ -50,6 +50,7 @@
import com.android.packageinstaller.v2.ui.fragments.InstallInstallingFragment
import com.android.packageinstaller.v2.ui.fragments.InstallStagingFragment
import com.android.packageinstaller.v2.ui.fragments.InstallSuccessFragment
+import com.android.packageinstaller.v2.ui.fragments.ParseErrorFragment
import com.android.packageinstaller.v2.ui.fragments.SimpleErrorFragment
import com.android.packageinstaller.v2.viewmodel.InstallViewModel
import com.android.packageinstaller.v2.viewmodel.InstallViewModelFactory
@@ -124,8 +125,15 @@
InstallStage.STAGE_ABORTED -> {
val aborted = installStage as InstallAborted
when (aborted.abortReason) {
- InstallAborted.ABORT_REASON_DONE, InstallAborted.ABORT_REASON_INTERNAL_ERROR ->
- setResult(aborted.activityResultCode, aborted.resultIntent, true)
+ InstallAborted.ABORT_REASON_DONE,
+ InstallAborted.ABORT_REASON_INTERNAL_ERROR -> {
+ if (aborted.errorDialogType == InstallAborted.DLG_PACKAGE_ERROR) {
+ val parseErrorDialog = ParseErrorFragment(aborted)
+ showDialogInner(parseErrorDialog)
+ } else {
+ setResult(aborted.activityResultCode, aborted.resultIntent, true)
+ }
+ }
InstallAborted.ABORT_REASON_POLICY -> showPolicyRestrictionDialog(aborted)
else -> setResult(Activity.RESULT_CANCELED, null, true)
@@ -204,7 +212,7 @@
val blockedByPolicyDialog = createDevicePolicyRestrictionDialog(restriction)
// Don't finish the package installer app since the next dialog
// will be shown by this app
- shouldFinish = blockedByPolicyDialog != null
+ shouldFinish = blockedByPolicyDialog == null
showDialogInner(blockedByPolicyDialog)
}
setResult(Activity.RESULT_CANCELED, null, shouldFinish)
@@ -267,6 +275,10 @@
setResult(Activity.RESULT_CANCELED, null, true)
}
+ override fun onNegativeResponse(resultCode: Int, data: Intent?) {
+ setResult(resultCode, data, true)
+ }
+
override fun sendUnknownAppsIntent(sourcePackageName: String) {
val settingsIntent = Intent()
settingsIntent.setAction(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES)
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
index 5ca02ea..dbe32cc 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
@@ -60,20 +60,29 @@
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
View dialogView = getLayoutInflater().inflate(R.layout.install_content_view, null);
+ int positiveBtnTextRes;
+ if (mDialogData.isAppUpdating()) {
+ if (mDialogData.getDialogMessage() != null) {
+ positiveBtnTextRes = R.string.update_anyway;
+ } else {
+ positiveBtnTextRes = R.string.update;
+ }
+ } else {
+ positiveBtnTextRes = R.string.install;
+ }
+
mDialog = new AlertDialog.Builder(requireContext())
.setIcon(mDialogData.getAppIcon())
.setTitle(mDialogData.getAppLabel())
.setView(dialogView)
- .setPositiveButton(mDialogData.isAppUpdating() ? R.string.update : R.string.install,
+ .setPositiveButton(positiveBtnTextRes,
(dialogInt, which) -> mInstallActionListener.onPositiveResponse(
InstallUserActionRequired.USER_ACTION_REASON_INSTALL_CONFIRMATION))
.setNegativeButton(R.string.cancel,
(dialogInt, which) -> mInstallActionListener.onNegativeResponse(
mDialogData.getStageCode()))
-
.create();
- // TODO: Dynamically change positive button text to update anyway
TextView viewToEnable;
if (mDialogData.isAppUpdating()) {
viewToEnable = dialogView.requireViewById(R.id.install_confirm_question_update);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ParseErrorFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ParseErrorFragment.java
new file mode 100644
index 0000000..68d48d6
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ParseErrorFragment.java
@@ -0,0 +1,64 @@
+/*
+ * 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.packageinstaller.v2.ui.fragments;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import androidx.annotation.NonNull;
+import androidx.fragment.app.DialogFragment;
+import com.android.packageinstaller.R;
+import com.android.packageinstaller.v2.model.InstallAborted;
+import com.android.packageinstaller.v2.ui.InstallActionListener;
+
+public class ParseErrorFragment extends DialogFragment {
+
+ private static final String TAG = ParseErrorFragment.class.getSimpleName();
+ private final InstallAborted mDialogData;
+ private InstallActionListener mInstallActionListener;
+
+ public ParseErrorFragment(InstallAborted dialogData) {
+ mDialogData = dialogData;
+ }
+
+ @Override
+ public void onAttach(@NonNull Context context) {
+ super.onAttach(context);
+ mInstallActionListener = (InstallActionListener) context;
+ }
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ return new AlertDialog.Builder(getActivity())
+ .setMessage(R.string.Parse_error_dlg_text)
+ .setPositiveButton(R.string.ok,
+ (dialog, which) ->
+ mInstallActionListener.onNegativeResponse(
+ mDialogData.getActivityResultCode(), mDialogData.getResultIntent()))
+ .create();
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ super.onCancel(dialog);
+ mInstallActionListener.onNegativeResponse(
+ mDialogData.getActivityResultCode(), mDialogData.getResultIntent());
+ }
+}
diff --git a/packages/SettingsLib/LintChecker/Android.bp b/packages/SettingsLib/LintChecker/Android.bp
new file mode 100644
index 0000000..eb489b1
--- /dev/null
+++ b/packages/SettingsLib/LintChecker/Android.bp
@@ -0,0 +1,33 @@
+// 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 {
+ // 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"],
+}
+
+java_library_host {
+ name: "SettingsLibLintChecker",
+ srcs: ["src/**/*.kt"],
+ plugins: ["auto_service_plugin"],
+ libs: [
+ "auto_service_annotations",
+ "lint_api",
+ ],
+ kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/packages/SettingsLib/LintChecker/src/com/android/settingslib/tools/lint/NullabilityAnnotationsDetector.kt b/packages/SettingsLib/LintChecker/src/com/android/settingslib/tools/lint/NullabilityAnnotationsDetector.kt
new file mode 100644
index 0000000..1f06261
--- /dev/null
+++ b/packages/SettingsLib/LintChecker/src/com/android/settingslib/tools/lint/NullabilityAnnotationsDetector.kt
@@ -0,0 +1,146 @@
+/*
+ * 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.settingslib.tools.lint
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.LintFix
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.intellij.psi.PsiModifier
+import com.intellij.psi.PsiPrimitiveType
+import com.intellij.psi.PsiType
+import org.jetbrains.uast.UAnnotated
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UMethod
+
+class NullabilityAnnotationsDetector : Detector(), Detector.UastScanner {
+ override fun getApplicableUastTypes(): List<Class<out UElement>> = listOf(UMethod::class.java)
+
+ override fun createUastHandler(context: JavaContext): UElementHandler? {
+ if (!context.isJavaFile()) return null
+
+ return object : UElementHandler() {
+ override fun visitMethod(node: UMethod) {
+ if (node.isPublic() && node.name != ANONYMOUS_CONSTRUCTOR) {
+ node.verifyMethod()
+ node.verifyMethodParameters()
+ }
+ }
+
+ private fun UMethod.isPublic() = modifierList.hasModifierProperty(PsiModifier.PUBLIC)
+
+ private fun UMethod.verifyMethod() {
+ if (isConstructor) return
+ if (returnType.isPrimitive()) return
+ checkAnnotation(METHOD_MSG)
+ }
+
+ private fun UMethod.verifyMethodParameters() {
+ for (parameter in uastParameters) {
+ if (parameter.type.isPrimitive()) continue
+ parameter.checkAnnotation(PARAMETER_MSG)
+ }
+ }
+
+ private fun PsiType?.isPrimitive() = this is PsiPrimitiveType
+
+ private fun UAnnotated.checkAnnotation(message: String) {
+ val oldAnnotation = findOldNullabilityAnnotation()
+ val oldAnnotationName = oldAnnotation?.qualifiedName?.substringAfterLast('.')
+
+ if (oldAnnotationName != null) {
+ val annotation = "androidx.annotation.$oldAnnotationName"
+ reportIssue(
+ REQUIRE_NULLABILITY_ISSUE,
+ "Prefer $annotation",
+ LintFix.create()
+ .replace()
+ .range(context.getLocation(oldAnnotation))
+ .with("@$annotation")
+ .autoFix()
+ .build()
+ )
+ } else if (!hasNullabilityAnnotation()) {
+ reportIssue(REQUIRE_NULLABILITY_ISSUE, message)
+ }
+ }
+
+ private fun UElement.reportIssue(
+ issue: Issue,
+ message: String,
+ quickfixData: LintFix? = null,
+ ) {
+ context.report(
+ issue = issue,
+ scope = this,
+ location = context.getNameLocation(this),
+ message = message,
+ quickfixData = quickfixData,
+ )
+ }
+
+ private fun UAnnotated.findOldNullabilityAnnotation() =
+ uAnnotations.find { it.qualifiedName in oldAnnotations }
+
+ private fun UAnnotated.hasNullabilityAnnotation() =
+ uAnnotations.any { it.qualifiedName in validAnnotations }
+ }
+ }
+
+ private fun JavaContext.isJavaFile() = psiFile?.fileElementType.toString().startsWith("java")
+
+ companion object {
+ private val validAnnotations = arrayOf("androidx.annotation.NonNull",
+ "androidx.annotation.Nullable")
+
+ private val oldAnnotations = arrayOf("android.annotation.NonNull",
+ "android.annotation.Nullable",
+ )
+
+ private const val ANONYMOUS_CONSTRUCTOR = "<anon-init>"
+
+ private const val METHOD_MSG =
+ "Java public method return with non-primitive type must add androidx annotation. " +
+ "Example: @NonNull | @Nullable Object functionName() {}"
+
+ private const val PARAMETER_MSG =
+ "Java public method parameter with non-primitive type must add androidx " +
+ "annotation. Example: functionName(@NonNull Context context, " +
+ "@Nullable Object obj) {}"
+
+ internal val REQUIRE_NULLABILITY_ISSUE = Issue
+ .create(
+ id = "RequiresNullabilityAnnotation",
+ briefDescription = "Requires nullability annotation for function",
+ explanation = "All public java APIs should specify nullability annotations for " +
+ "methods and parameters.",
+ category = Category.CUSTOM_LINT_CHECKS,
+ priority = 3,
+ severity = Severity.WARNING,
+ androidSpecific = true,
+ implementation = Implementation(
+ NullabilityAnnotationsDetector::class.java,
+ Scope.JAVA_FILE_SCOPE,
+ ),
+ )
+ }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/LintChecker/src/com/android/settingslib/tools/lint/SettingsLintIssueRegistry.kt b/packages/SettingsLib/LintChecker/src/com/android/settingslib/tools/lint/SettingsLintIssueRegistry.kt
new file mode 100644
index 0000000..e0ab24a
--- /dev/null
+++ b/packages/SettingsLib/LintChecker/src/com/android/settingslib/tools/lint/SettingsLintIssueRegistry.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.settingslib.tools.lint
+
+import com.android.tools.lint.client.api.IssueRegistry
+import com.android.tools.lint.detector.api.CURRENT_API
+import com.google.auto.service.AutoService
+
+@AutoService(IssueRegistry::class)
+class SettingsLintIssueRegistry : IssueRegistry() {
+ override val issues = listOf(NullabilityAnnotationsDetector.REQUIRE_NULLABILITY_ISSUE)
+
+ override val api: Int = CURRENT_API
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index f4edb36..460a6f7 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -24,7 +24,8 @@
import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
import com.android.settingslib.spa.gallery.card.CardPageProvider
import com.android.settingslib.spa.gallery.chart.ChartPageProvider
-import com.android.settingslib.spa.gallery.dialog.AlertDialogPageProvider
+import com.android.settingslib.spa.gallery.dialog.DialogMainPageProvider
+import com.android.settingslib.spa.gallery.dialog.NavDialogProvider
import com.android.settingslib.spa.gallery.editor.EditorMainPageProvider
import com.android.settingslib.spa.gallery.editor.SettingsExposedDropdownMenuBoxPageProvider
import com.android.settingslib.spa.gallery.editor.SettingsExposedDropdownMenuCheckBoxProvider
@@ -91,7 +92,8 @@
ProgressBarPageProvider,
LoadingBarPageProvider,
ChartPageProvider,
- AlertDialogPageProvider,
+ DialogMainPageProvider,
+ NavDialogProvider,
ItemListPageProvider,
ItemOperatePageProvider,
OperateListPageProvider,
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/AlertDialogPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/DialogMainPageProvider.kt
similarity index 84%
rename from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/AlertDialogPage.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/DialogMainPageProvider.kt
index 1545a3e..4e3fcee 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/AlertDialogPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/DialogMainPageProvider.kt
@@ -28,10 +28,10 @@
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
-private const val TITLE = "AlertDialogPage"
+private const val TITLE = "Category: Dialog"
-object AlertDialogPageProvider : SettingsPageProvider {
- override val name = "AlertDialogPage"
+object DialogMainPageProvider : SettingsPageProvider {
+ override val name = "DialogMain"
private val owner = createSettingsPage()
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> = listOf(
@@ -47,6 +47,12 @@
override val onClick = alertDialogPresenter::open
})
}.build(),
+ SettingsEntryBuilder.create("NavDialog", owner).setUiLayoutFn {
+ Preference(object : PreferenceModel {
+ override val title = "Navigate to Dialog"
+ override val onClick = navigator(route = NavDialogProvider.name)
+ })
+ }.build(),
)
fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner)
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/NavDialogProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/NavDialogProvider.kt
new file mode 100644
index 0000000..6f79911
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/NavDialogProvider.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.settingslib.spa.gallery.dialog
+
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.widget.dialog.SettingsDialogCard
+
+object NavDialogProvider : SettingsPageProvider {
+ override val name = "NavDialog"
+ override val navType = SettingsPageProvider.NavType.Dialog
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ SettingsDialogCard("Example Nav Dialog") {}
+ }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
index 6a2e598..1f028d5 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
@@ -30,7 +30,7 @@
import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
import com.android.settingslib.spa.gallery.card.CardPageProvider
import com.android.settingslib.spa.gallery.chart.ChartPageProvider
-import com.android.settingslib.spa.gallery.dialog.AlertDialogPageProvider
+import com.android.settingslib.spa.gallery.dialog.DialogMainPageProvider
import com.android.settingslib.spa.gallery.editor.EditorMainPageProvider
import com.android.settingslib.spa.gallery.itemList.OperateListPageProvider
import com.android.settingslib.spa.gallery.page.ArgumentPageModel
@@ -71,7 +71,7 @@
ProgressBarPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
LoadingBarPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
ChartPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- AlertDialogPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ DialogMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
EditorMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
CardPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
CopyablePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index 9f8c868..5605485 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -22,7 +22,6 @@
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.annotation.VisibleForTesting
-import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
@@ -30,15 +29,19 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.core.view.WindowCompat
+import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavGraph.Companion.findStartDestination
+import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
+import androidx.navigation.compose.dialog
import androidx.navigation.compose.rememberNavController
import com.android.settingslib.spa.R
import com.android.settingslib.spa.framework.common.LogCategory
import com.android.settingslib.spa.framework.common.NullPageProvider
import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.SettingsPageProvider.NavType
import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.common.createSettingsPage
@@ -127,27 +130,31 @@
allProvider: Collection<SettingsPageProvider>,
content: @Composable (SettingsPage) -> Unit,
) {
- // TODO(b/298520326): Remove Box after the issue is fixed.
- // Wrap the top level node into a Box to workaround an issue of Compose 1.6.0-alpha03.
- Box {
- NavHost(
- navController = navController,
- startDestination = NullPageProvider.name,
- ) {
- composable(NullPageProvider.name) {}
- for (spp in allProvider) {
- animatedComposable(
- route = spp.name + spp.parameter.navRoute(),
- arguments = spp.parameter,
- ) { navBackStackEntry ->
- val page = remember { spp.createSettingsPage(navBackStackEntry.arguments) }
- content(page)
- }
+ NavHost(
+ navController = navController,
+ startDestination = NullPageProvider.name,
+ ) {
+ composable(NullPageProvider.name) {}
+ for (spp in allProvider) {
+ destination(spp) { navBackStackEntry ->
+ val page = remember { spp.createSettingsPage(navBackStackEntry.arguments) }
+ content(page)
}
}
}
}
+private fun NavGraphBuilder.destination(
+ spp: SettingsPageProvider,
+ content: @Composable (NavBackStackEntry) -> Unit,
+) {
+ val route = spp.name + spp.parameter.navRoute()
+ when (spp.navType) {
+ NavType.Page -> animatedComposable(route, spp.parameter) { content(it) }
+ NavType.Dialog -> dialog(route, spp.parameter) { content(it) }
+ }
+}
+
@Composable
private fun NavControllerWrapperImpl.InitialDestination(
initialIntent: Intent?,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
index 18f964e..81bee5e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
@@ -34,6 +34,14 @@
/** The page provider name, needs to be *unique* and *stable*. */
val name: String
+ enum class NavType {
+ Page,
+ Dialog,
+ }
+
+ val navType: NavType
+ get() = NavType.Page
+
/** The display name of this page provider, for better readability. */
val displayName: String
get() = name
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index c143390..b7f2c1e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -34,6 +34,15 @@
end = itemPaddingEnd,
bottom = itemPaddingVertical,
)
+ val textFieldPadding = PaddingValues(
+ start = itemPaddingStart,
+ end = itemPaddingEnd,
+ )
+ val menuFieldPadding = PaddingValues(
+ start = itemPaddingStart,
+ end = itemPaddingEnd,
+ bottom = itemPaddingVertical,
+ )
val itemPaddingAround = 8.dp
val itemDividerHeight = 32.dp
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsDialog.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsDialog.kt
index 8b172da..f08e740 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsDialog.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsDialog.kt
@@ -19,10 +19,13 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.window.Dialog
+import androidx.navigation.compose.NavHost
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsShape
import com.android.settingslib.spa.widget.ui.SettingsTitle
@@ -34,13 +37,27 @@
content: @Composable () -> Unit,
) {
Dialog(onDismissRequest = onDismissRequest) {
- Card(shape = SettingsShape.CornerExtraLarge) {
- Column(modifier = Modifier.padding(vertical = SettingsDimension.itemPaddingAround)) {
- Box(modifier = Modifier.padding(SettingsDimension.dialogItemPadding)) {
- SettingsTitle(title = title, useMediumWeight = true)
- }
- content()
+ SettingsDialogCard(title, content)
+ }
+}
+
+/**
+ * Card for dialog, suitable for independent dialog in the [NavHost].
+ */
+@Composable
+fun SettingsDialogCard(
+ title: String,
+ content: @Composable () -> Unit,
+) {
+ Card(
+ shape = SettingsShape.CornerExtraLarge,
+ colors = CardDefaults.cardColors(containerColor = AlertDialogDefaults.containerColor),
+ ) {
+ Column(modifier = Modifier.padding(vertical = SettingsDimension.itemPaddingAround)) {
+ Box(modifier = Modifier.padding(SettingsDimension.dialogItemPadding)) {
+ SettingsTitle(title = title, useMediumWeight = true)
}
+ content()
}
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBox.kt
index 0d6c064..f6692a3 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBox.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBox.kt
@@ -51,7 +51,7 @@
onExpandedChange = { expanded = it },
modifier = Modifier
.width(350.dp)
- .padding(SettingsDimension.itemPadding),
+ .padding(SettingsDimension.menuFieldPadding),
) {
OutlinedTextField(
// The `menuAnchor` modifier must be passed to the text field for correctness.
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
index 5d248e1..ba8e354 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
@@ -63,7 +63,7 @@
onExpandedChange = { expanded = it },
modifier = Modifier
.width(350.dp)
- .padding(SettingsDimension.itemPadding)
+ .padding(SettingsDimension.menuFieldPadding)
.onSizeChanged { dropDownWidth = it.width },
) {
OutlinedTextField(
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
index e0dd4e1..2ce3c66 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
@@ -42,7 +42,7 @@
OutlinedTextField(
modifier = Modifier
.fillMaxWidth()
- .padding(SettingsDimension.itemPadding),
+ .padding(SettingsDimension.textFieldPadding),
value = value,
onValueChange = onTextChange,
label = {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPassword.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPassword.kt
index 0757df3..3102a00 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPassword.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPassword.kt
@@ -52,7 +52,7 @@
var visibility by remember { mutableStateOf(false) }
OutlinedTextField(
modifier = Modifier
- .padding(SettingsDimension.itemPadding)
+ .padding(SettingsDimension.menuFieldPadding)
.fillMaxWidth(),
value = value,
onValueChange = onTextChange,
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt
index 92d3411..8cbd964 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt
@@ -18,6 +18,7 @@
import android.content.Context
import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onNodeWithText
@@ -29,12 +30,14 @@
import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.tests.testutils.SppDialog
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
import com.android.settingslib.spa.tests.testutils.SpaLoggerForTest
import com.android.settingslib.spa.tests.testutils.SppDisabled
import com.android.settingslib.spa.tests.testutils.SppHome
import com.android.settingslib.spa.testutils.waitUntil
-import com.google.common.truth.Truth
+import com.android.settingslib.spa.testutils.waitUntilExists
+import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -106,11 +109,31 @@
composeTestRule.onNodeWithText(sppDisabled.getTitle(null)).assertDoesNotExist()
spaLogger.verifyPageEvent(pageDisabled.id, 0, 0)
}
+
+ @Test
+ fun browseContent_dialog() {
+ val spaEnvironment = SpaEnvironmentForTest(
+ context = context,
+ rootPages = listOf(SppHome.createSettingsPage()),
+ logger = spaLogger,
+ )
+ SpaEnvironmentFactory.reset(spaEnvironment)
+ val sppRepository by spaEnvironment.pageProviderRepository
+
+ composeTestRule.setContent {
+ BrowseContent(
+ sppRepository = sppRepository,
+ isPageEnabled = SettingsPage::isEnabled,
+ initialIntent = null,
+ )
+ }
+ composeTestRule.onNodeWithText(SppDialog.name).performClick()
+
+ composeTestRule.waitUntilExists(hasText(SppDialog.CONTENT))
+ }
}
private fun SpaLoggerForTest.verifyPageEvent(id: String, entryCount: Int, leaveCount: Int) {
- Truth.assertThat(getEventCount(id, LogEvent.PAGE_ENTER, LogCategory.FRAMEWORK))
- .isEqualTo(entryCount)
- Truth.assertThat(getEventCount(id, LogEvent.PAGE_LEAVE, LogCategory.FRAMEWORK))
- .isEqualTo(leaveCount)
+ assertThat(getEventCount(id, LogEvent.PAGE_ENTER, LogCategory.FRAMEWORK)).isEqualTo(entryCount)
+ assertThat(getEventCount(id, LogEvent.PAGE_LEAVE, LogCategory.FRAMEWORK)).isEqualTo(leaveCount)
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
index b139f28..0a1c05f 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
@@ -22,6 +22,7 @@
import com.android.settingslib.spa.framework.util.genEntryId
import com.android.settingslib.spa.framework.util.genPageId
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
+import com.android.settingslib.spa.tests.testutils.SppDialog
import com.android.settingslib.spa.tests.testutils.SppHome
import com.android.settingslib.spa.tests.testutils.SppLayer1
import com.android.settingslib.spa.tests.testutils.SppLayer2
@@ -39,26 +40,21 @@
@Test
fun testGetPageWithEntry() {
val pageWithEntry = entryRepository.getAllPageWithEntry()
- assertThat(pageWithEntry.size).isEqualTo(3)
- assertThat(
- entryRepository.getPageWithEntry(genPageId("SppHome"))
- ?.entries?.size
- ).isEqualTo(1)
- assertThat(
- entryRepository.getPageWithEntry(genPageId("SppLayer1"))
- ?.entries?.size
- ).isEqualTo(3)
- assertThat(
- entryRepository.getPageWithEntry(genPageId("SppLayer2"))
- ?.entries?.size
- ).isEqualTo(2)
+
+ assertThat(pageWithEntry).hasSize(4)
+ assertThat(entryRepository.getPageWithEntry(genPageId("SppHome"))?.entries)
+ .hasSize(2)
+ assertThat(entryRepository.getPageWithEntry(genPageId("SppLayer1"))?.entries)
+ .hasSize(3)
+ assertThat(entryRepository.getPageWithEntry(genPageId("SppLayer2"))?.entries)
+ .hasSize(2)
assertThat(entryRepository.getPageWithEntry(genPageId("SppWithParam"))).isNull()
}
@Test
fun testGetEntry() {
val entry = entryRepository.getAllEntries()
- assertThat(entry.size).isEqualTo(7)
+ assertThat(entry).hasSize(8)
assertThat(
entryRepository.getEntry(
genEntryId(
@@ -91,6 +87,16 @@
).isNotNull()
assertThat(
entryRepository.getEntry(
+ genEntryId(
+ "INJECT",
+ SppDialog.createSettingsPage(),
+ SppHome.createSettingsPage(),
+ SppDialog.createSettingsPage(),
+ )
+ )
+ ).isNotNull()
+ assertThat(
+ entryRepository.getEntry(
genEntryId("Layer1Entry1", SppLayer1.createSettingsPage())
)
).isNotNull()
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepositoryTest.kt
index 8576573..169c541 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepositoryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepositoryTest.kt
@@ -25,39 +25,55 @@
@RunWith(AndroidJUnit4::class)
class SettingsPageProviderRepositoryTest {
@Test
- fun getStartPageTest() {
- val sppRepoEmpty = SettingsPageProviderRepository(emptyList(), emptyList())
+ fun rootPages_empty() {
+ val sppRepoEmpty = SettingsPageProviderRepository(emptyList())
+
assertThat(sppRepoEmpty.getDefaultStartPage()).isEqualTo("")
assertThat(sppRepoEmpty.getAllRootPages()).isEmpty()
-
- val nullPage = NullPageProvider.createSettingsPage()
- val sppRepoNull =
- SettingsPageProviderRepository(emptyList(), listOf(nullPage))
- assertThat(sppRepoNull.getDefaultStartPage()).isEqualTo("NULL")
- assertThat(sppRepoNull.getAllRootPages()).contains(nullPage)
-
- val rootPage1 = createSettingsPage(sppName = "Spp1", displayName = "Spp1")
- val rootPage2 = createSettingsPage(sppName = "Spp2", displayName = "Spp2")
- val sppRepo = SettingsPageProviderRepository(emptyList(), listOf(rootPage1, rootPage2))
- val allRoots = sppRepo.getAllRootPages()
- assertThat(sppRepo.getDefaultStartPage()).isEqualTo("Spp1")
- assertThat(allRoots.size).isEqualTo(2)
- assertThat(allRoots).contains(rootPage1)
- assertThat(allRoots).contains(rootPage2)
}
@Test
- fun getProviderTest() {
- val sppRepoEmpty = SettingsPageProviderRepository(emptyList(), emptyList())
+ fun rootPages_single() {
+ val nullPage = NullPageProvider.createSettingsPage()
+
+ val sppRepoNull = SettingsPageProviderRepository(
+ allPageProviders = emptyList(),
+ rootPages = listOf(nullPage),
+ )
+
+ assertThat(sppRepoNull.getDefaultStartPage()).isEqualTo("NULL")
+ assertThat(sppRepoNull.getAllRootPages()).containsExactly(nullPage)
+ }
+
+ @Test
+ fun rootPages_twoPages() {
+ val rootPage1 = createSettingsPage(sppName = "Spp1", displayName = "Spp1")
+ val rootPage2 = createSettingsPage(sppName = "Spp2", displayName = "Spp2")
+
+ val sppRepo = SettingsPageProviderRepository(
+ allPageProviders = emptyList(),
+ rootPages = listOf(rootPage1, rootPage2),
+ )
+
+ assertThat(sppRepo.getDefaultStartPage()).isEqualTo("Spp1")
+ assertThat(sppRepo.getAllRootPages()).containsExactly(rootPage1, rootPage2)
+ }
+
+ @Test
+ fun getProviderOrNull_empty() {
+ val sppRepoEmpty = SettingsPageProviderRepository(emptyList())
assertThat(sppRepoEmpty.getAllProviders()).isEmpty()
assertThat(sppRepoEmpty.getProviderOrNull("Spp")).isNull()
+ }
+ @Test
+ fun getProviderOrNull_single() {
val sppRepo = SettingsPageProviderRepository(listOf(
object : SettingsPageProvider {
override val name = "Spp"
}
- ), emptyList())
- assertThat(sppRepo.getAllProviders().size).isEqualTo(1)
+ ))
+ assertThat(sppRepo.getAllProviders()).hasSize(1)
assertThat(sppRepo.getProviderOrNull("Spp")).isNotNull()
assertThat(sppRepo.getProviderOrNull("SppUnknown")).isNull()
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
index 2755b4e..22a5ca3 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
@@ -21,6 +21,8 @@
import android.content.Context
import android.content.Intent
import android.os.Bundle
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
import androidx.navigation.NavType
import androidx.navigation.navArgument
import com.android.settingslib.spa.framework.BrowseActivity
@@ -88,6 +90,7 @@
val owner = this.createSettingsPage()
return listOf(
SppLayer1.buildInject().setLink(fromPage = owner).build(),
+ SppDialog.buildInject().setLink(fromPage = owner).build(),
)
}
}
@@ -160,6 +163,21 @@
}
}
+object SppDialog : SettingsPageProvider {
+ override val name = "SppDialog"
+ override val navType = SettingsPageProvider.NavType.Dialog
+
+ const val CONTENT = "SppDialog Content"
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ Text(CONTENT)
+ }
+
+ fun buildInject() = SettingsEntryBuilder.createInject(this.createSettingsPage())
+ .setMacro { SimplePreferenceMacro(title = name, clickRoute = name) }
+}
+
object SppForSearch : SettingsPageProvider {
override val name = "SppForSearch"
@@ -223,6 +241,7 @@
navArgument("rt_param") { type = NavType.StringType },
)
},
+ SppDialog,
),
rootPages
)
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index 1cdb69c..a94c313 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -140,8 +140,8 @@
<string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Tumia kwa kuingiza"</string>
<string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Tumia kwa visaidizi vya kusikia"</string>
<string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Tumia kwa ajili ya LE_AUDIO"</string>
- <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Oanisha"</string>
- <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"OANISHA"</string>
+ <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Unganisha"</string>
+ <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"UNGANISHA"</string>
<string name="bluetooth_pairing_decline" msgid="6483118841204885890">"Ghairi"</string>
<string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"Kuoanisha hutoa ruhusa ya kufikiwa kwa unaowasiliana nao na rekodi ya simu zilizopigwa unapounganishwa."</string>
<string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"Haikuwezakulinganisha na <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
@@ -237,10 +237,10 @@
<string name="adb_wireless_error" msgid="721958772149779856">"Hitilafu"</string>
<string name="adb_wireless_settings" msgid="2295017847215680229">"Utatuzi usiotumia waya"</string>
<string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"Ili kuangalia na kutumia vifaa vinavyopatikana, washa utatuzi usiotumia waya"</string>
- <string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"Oanisha kifaa ukitumia msimbo wa QR"</string>
- <string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"Oanisha vifaa vipya ukitumia kichanganuzi cha Msimbo wa QR"</string>
- <string name="adb_pair_method_code_title" msgid="1122590300445142904">"Oanisha kifaa ukitumia msimbo wa kuoanisha"</string>
- <string name="adb_pair_method_code_summary" msgid="6370414511333685185">"Oanisha vifaa vipya ukitumia msimbo wa tarakimu sita"</string>
+ <string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"Unganisha kifaa ukitumia msimbo wa QR"</string>
+ <string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"Unganisha vifaa vipya ukitumia kichanganuzi cha Msimbo wa QR"</string>
+ <string name="adb_pair_method_code_title" msgid="1122590300445142904">"Unganisha kifaa ukitumia msimbo wa kuunganisha"</string>
+ <string name="adb_pair_method_code_summary" msgid="6370414511333685185">"Unganisha vifaa vipya ukitumia msimbo wa tarakimu sita"</string>
<string name="adb_paired_devices_title" msgid="5268997341526217362">"Vifaa vilivyooanishwa"</string>
<string name="adb_wireless_device_connected_summary" msgid="3039660790249148713">"Vilivyounganishwa kwa sasa"</string>
<string name="adb_wireless_device_details_title" msgid="7129369670526565786">"Maelezo ya kifaa"</string>
@@ -248,16 +248,16 @@
<string name="adb_device_fingerprint_title_format" msgid="291504822917843701">"Alama bainifu ya kifaa: <xliff:g id="FINGERPRINT_PARAM">%1$s</xliff:g>"</string>
<string name="adb_wireless_connection_failed_title" msgid="664211177427438438">"Imeshindwa kuunganisha"</string>
<string name="adb_wireless_connection_failed_message" msgid="9213896700171602073">"Hakikisha kuwa <xliff:g id="DEVICE_NAME">%1$s</xliff:g> kimeunganishwa kwenye mtandao sahihi"</string>
- <string name="adb_pairing_device_dialog_title" msgid="7141739231018530210">"Oanisha na kifaa"</string>
+ <string name="adb_pairing_device_dialog_title" msgid="7141739231018530210">"Unganisha na kifaa"</string>
<string name="adb_pairing_device_dialog_pairing_code_label" msgid="3639239786669722731">"Msimbo wa kuoanisha wa Wi-Fi"</string>
<string name="adb_pairing_device_dialog_failed_title" msgid="3426758947882091735">"Imeshindwa kuoanisha"</string>
<string name="adb_pairing_device_dialog_failed_msg" msgid="6611097519661997148">"Hakikisha kuwa kifaa kimeunganishwa kwenye mtandao mmoja."</string>
- <string name="adb_wireless_qrcode_summary" msgid="8051414549011801917">"Oanisha kifaa kupitia Wi-Fi kwa kuchanganua msimbo wa QR"</string>
+ <string name="adb_wireless_qrcode_summary" msgid="8051414549011801917">"Unganisha kifaa kupitia Wi-Fi kwa kuchanganua msimbo wa QR"</string>
<string name="adb_wireless_verifying_qrcode_text" msgid="6123192424916029207">"Inaoanisha kifaa…"</string>
<string name="adb_qrcode_pairing_device_failed_msg" msgid="6936292092592914132">"Imeshindwa kuoanisha kifaa. Huenda msimbo wa QR haukuwa sahihi au kifaa hakijaunganishwa kwenye mtandao mmoja."</string>
<string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"Anwani ya IP na Mlango"</string>
<string name="adb_wireless_qrcode_pairing_title" msgid="1906409667944674707">"Changanua msimbo wa QR"</string>
- <string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"Oanisha kifaa kupitia Wi-Fi kwa kuchanganua msimbo wa QR"</string>
+ <string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"Unganisha kifaa kupitia Wi-Fi kwa kuchanganua msimbo wa QR"</string>
<string name="adb_wireless_no_network_msg" msgid="2365795244718494658">"Tafadhali unganisha kwenye mtandao wa Wi-Fi"</string>
<string name="keywords_adb_wireless" msgid="6507505581882171240">"adb, tatua, dev"</string>
<string name="bugreport_in_power" msgid="8664089072534638709">"Njia ya mkato ya kuripoti hitilafu"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
index 9560b8d..cdb8740 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
@@ -19,6 +19,7 @@
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHearingAid;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
@@ -42,18 +43,16 @@
BluetoothMediaDevice(
Context context,
CachedBluetoothDevice device,
- MediaRoute2Info info,
- String packageName) {
- this(context, device, info, packageName, null);
+ MediaRoute2Info info) {
+ this(context, device, info, null);
}
BluetoothMediaDevice(
Context context,
CachedBluetoothDevice device,
MediaRoute2Info info,
- String packageName,
RouteListingPreference.Item item) {
- super(context, info, packageName, item);
+ super(context, info, item);
mCachedDevice = device;
mAudioManager = context.getSystemService(AudioManager.class);
initDeviceRecord();
@@ -100,7 +99,12 @@
@Override
public String getId() {
- return MediaDeviceUtils.getId(mCachedDevice);
+ if (mCachedDevice.isHearingAidDevice()) {
+ if (mCachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
+ return Long.toString(mCachedDevice.getHiSyncId());
+ }
+ }
+ return mCachedDevice.getAddress();
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/ComplexMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/ComplexMediaDevice.java
index 4e0ebd1..338fb87 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/ComplexMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/ComplexMediaDevice.java
@@ -34,9 +34,8 @@
ComplexMediaDevice(
Context context,
MediaRoute2Info info,
- String packageName,
RouteListingPreference.Item item) {
- super(context, info, packageName, item);
+ super(context, info, item);
}
// MediaRoute2Info.getName was made public on API 34, but exists since API 30.
@@ -63,7 +62,7 @@
@Override
public String getId() {
- return MediaDeviceUtils.getId(mRouteInfo);
+ return mRouteInfo.getId();
}
public boolean isConnected() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
index 012cbc0..1347dd1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
@@ -45,14 +45,13 @@
InfoMediaDevice(
Context context,
MediaRoute2Info info,
- String packageName,
RouteListingPreference.Item item) {
- super(context, info, packageName, item);
+ super(context, info, item);
initDeviceRecord();
}
- InfoMediaDevice(Context context, MediaRoute2Info info, String packageName) {
- this(context, info, packageName, null);
+ InfoMediaDevice(Context context, MediaRoute2Info info) {
+ this(context, info, null);
}
@Override
@@ -118,7 +117,7 @@
@Override
public String getId() {
- return MediaDeviceUtils.getId(mRouteInfo);
+ return mRouteInfo.getId();
}
public boolean isConnected() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index e5fce5b..581c7de 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -383,7 +383,7 @@
for (MediaRoute2Info route : getSelectableRoutes(info)) {
deviceList.add(
new InfoMediaDevice(
- mContext, route, mPackageName, mPreferenceItemMap.get(route.getId())));
+ mContext, route, mPreferenceItemMap.get(route.getId())));
}
return deviceList;
}
@@ -410,7 +410,7 @@
for (MediaRoute2Info route : getDeselectableRoutes(info)) {
deviceList.add(
new InfoMediaDevice(
- mContext, route, mPackageName, mPreferenceItemMap.get(route.getId())));
+ mContext, route, mPreferenceItemMap.get(route.getId())));
Log.d(TAG, route.getName() + " is deselectable for " + mPackageName);
}
return deviceList;
@@ -434,7 +434,7 @@
for (MediaRoute2Info route : getSelectedRoutes(info)) {
deviceList.add(
new InfoMediaDevice(
- mContext, route, mPackageName, mPreferenceItemMap.get(route.getId())));
+ mContext, route, mPreferenceItemMap.get(route.getId())));
}
return deviceList;
}
@@ -633,7 +633,6 @@
new InfoMediaDevice(
mContext,
route,
- mPackageName,
mPreferenceItemMap.get(route.getId()));
break;
case TYPE_BUILTIN_SPEAKER:
@@ -650,7 +649,6 @@
new PhoneMediaDevice(
mContext,
route,
- mPackageName,
mPreferenceItemMap.getOrDefault(route.getId(), null));
break;
case TYPE_HEARING_AID:
@@ -666,7 +664,6 @@
mContext,
cachedDevice,
route,
- mPackageName,
mPreferenceItemMap.getOrDefault(route.getId(), null));
}
break;
@@ -675,7 +672,6 @@
new ComplexMediaDevice(
mContext,
route,
- mPackageName,
mPreferenceItemMap.get(route.getId()));
break;
default:
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index dbc3bf7..ebcca42 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -567,7 +567,7 @@
final CachedBluetoothDevice cachedDevice =
cachedDeviceManager.findDevice(device);
if (isBondedMediaDevice(cachedDevice) && isMutingExpectedDevice(cachedDevice)) {
- return new BluetoothMediaDevice(mContext, cachedDevice, null, mPackageName);
+ return new BluetoothMediaDevice(mContext, cachedDevice, null);
}
}
return null;
@@ -614,7 +614,7 @@
mDisconnectedMediaDevices.clear();
for (CachedBluetoothDevice cachedDevice : cachedBluetoothDeviceList) {
final MediaDevice mediaDevice =
- new BluetoothMediaDevice(mContext, cachedDevice, null, mPackageName);
+ new BluetoothMediaDevice(mContext, cachedDevice, null);
if (!mMediaDevices.contains(mediaDevice)) {
cachedDevice.registerCallback(mDeviceAttributeChangeCallback);
mDisconnectedMediaDevices.add(mediaDevice);
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index c8e4c0c..f2d9d14 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -121,16 +121,13 @@
protected final Context mContext;
protected final MediaRoute2Info mRouteInfo;
protected final RouteListingPreference.Item mItem;
- protected final String mPackageName;
MediaDevice(
Context context,
MediaRoute2Info info,
- String packageName,
RouteListingPreference.Item item) {
mContext = context;
mRouteInfo = info;
- mPackageName = packageName;
mItem = item;
setType(info);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDeviceUtils.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDeviceUtils.java
deleted file mode 100644
index b3a52b9..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDeviceUtils.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2018 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.settingslib.media;
-
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHearingAid;
-import android.media.MediaRoute2Info;
-
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-
-/**
- * MediaDeviceUtils provides utility function for MediaDevice
- */
-public class MediaDeviceUtils {
- /**
- * Use CachedBluetoothDevice address to represent unique id
- *
- * @param cachedDevice the CachedBluetoothDevice
- * @return CachedBluetoothDevice address
- */
- public static String getId(CachedBluetoothDevice cachedDevice) {
- if (cachedDevice.isHearingAidDevice()) {
- if (cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
- return Long.toString(cachedDevice.getHiSyncId());
- }
- }
- return cachedDevice.getAddress();
- }
-
- /**
- * Use BluetoothDevice address to represent unique id
- *
- * @param bluetoothDevice the BluetoothDevice
- * @return BluetoothDevice address
- */
- public static String getId(BluetoothDevice bluetoothDevice) {
- return bluetoothDevice.getAddress();
- }
-
- /**
- * Use MediaRoute2Info id to represent unique id
- *
- * @param route the MediaRoute2Info
- * @return MediaRoute2Info id
- */
- public static String getId(MediaRoute2Info route) {
- return route.getId();
- }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index 0676ce5..d6f1eab 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -117,16 +117,15 @@
return name.toString();
}
- PhoneMediaDevice(Context context, MediaRoute2Info info, String packageName) {
- this(context, info, packageName, null);
+ PhoneMediaDevice(Context context, MediaRoute2Info info) {
+ this(context, info, null);
}
PhoneMediaDevice(
Context context,
MediaRoute2Info info,
- String packageName,
RouteListingPreference.Item item) {
- super(context, info, packageName, item);
+ super(context, info, item);
mDeviceIconUtil = new DeviceIconUtil(mContext);
initDeviceRecord();
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaDeviceTest.java
index f50802a..7061742 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaDeviceTest.java
@@ -39,6 +39,8 @@
@RunWith(RobolectricTestRunner.class)
public class BluetoothMediaDeviceTest {
+ private static final String TEST_ADDRESS = "11:22:33:44:55:66";
+
@Mock
private CachedBluetoothDevice mDevice;
@@ -54,7 +56,7 @@
when(mDevice.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(true);
when(mDevice.isActiveDevice(BluetoothProfile.LE_AUDIO)).thenReturn(true);
- mBluetoothMediaDevice = new BluetoothMediaDevice(mContext, mDevice, null, null, null);
+ mBluetoothMediaDevice = new BluetoothMediaDevice(mContext, mDevice, null, null);
}
@Test
@@ -111,4 +113,10 @@
assertThat(mBluetoothMediaDevice.getIcon() instanceof BitmapDrawable).isFalse();
}
+
+ @Test
+ public void getId_returnsCachedBluetoothDeviceAddress() {
+ when(mDevice.getAddress()).thenReturn(TEST_ADDRESS);
+ assertThat(mBluetoothMediaDevice.getId()).isEqualTo(TEST_ADDRESS);
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java
index a072c17..0665308 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java
@@ -65,7 +65,7 @@
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
- mInfoMediaDevice = new InfoMediaDevice(mContext, mRouteInfo, TEST_PACKAGE_NAME);
+ mInfoMediaDevice = new InfoMediaDevice(mContext, mRouteInfo);
}
@Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index 2252b69..f0330c4 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -547,7 +547,7 @@
@Test
public void connectDeviceWithoutPackageName_noSession_returnFalse() {
final MediaRoute2Info info = mock(MediaRoute2Info.class);
- final MediaDevice device = new InfoMediaDevice(mContext, info, TEST_PACKAGE_NAME);
+ final MediaDevice device = new InfoMediaDevice(mContext, info);
final List<RoutingSessionInfo> infos = new ArrayList<>();
@@ -623,7 +623,7 @@
routingSessionInfos.add(info);
final MediaRoute2Info route2Info = mock(MediaRoute2Info.class);
- final MediaDevice device = new InfoMediaDevice(mContext, route2Info, TEST_PACKAGE_NAME);
+ final MediaDevice device = new InfoMediaDevice(mContext, route2Info);
final List<String> list = new ArrayList<>();
list.add(TEST_ID);
@@ -644,7 +644,7 @@
routingSessionInfos.add(info);
final MediaRoute2Info route2Info = mock(MediaRoute2Info.class);
- final MediaDevice device = new InfoMediaDevice(mContext, route2Info, TEST_PACKAGE_NAME);
+ final MediaDevice device = new InfoMediaDevice(mContext, route2Info);
final List<String> list = new ArrayList<>();
list.add("fake_id");
@@ -674,7 +674,7 @@
routingSessionInfos.add(info);
final MediaRoute2Info route2Info = mock(MediaRoute2Info.class);
- final MediaDevice device = new InfoMediaDevice(mContext, route2Info, TEST_PACKAGE_NAME);
+ final MediaDevice device = new InfoMediaDevice(mContext, route2Info);
final List<String> list = new ArrayList<>();
list.add(TEST_ID);
@@ -695,7 +695,7 @@
routingSessionInfos.add(info);
final MediaRoute2Info route2Info = mock(MediaRoute2Info.class);
- final MediaDevice device = new InfoMediaDevice(mContext, route2Info, TEST_PACKAGE_NAME);
+ final MediaDevice device = new InfoMediaDevice(mContext, route2Info);
final List<String> list = new ArrayList<>();
list.add("fake_id");
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index 926b41a..999e8d5 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -116,8 +116,8 @@
when(mLocalProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHapProfile);
- mInfoMediaDevice1 = spy(new InfoMediaDevice(mContext, mRouteInfo1, TEST_PACKAGE_NAME));
- mInfoMediaDevice2 = new InfoMediaDevice(mContext, mRouteInfo2, TEST_PACKAGE_NAME);
+ mInfoMediaDevice1 = spy(new InfoMediaDevice(mContext, mRouteInfo1));
+ mInfoMediaDevice2 = new InfoMediaDevice(mContext, mRouteInfo2);
mLocalMediaManager = new LocalMediaManager(mContext, mLocalBluetoothManager,
mInfoMediaManager, "com.test.packagename");
mLocalMediaManager.mAudioManager = mAudioManager;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
index 18055d9..098ab16 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
@@ -171,17 +171,17 @@
mBluetoothMediaDevice1 =
new BluetoothMediaDevice(
- mContext, mCachedDevice1, mBluetoothRouteInfo1, TEST_PACKAGE_NAME);
+ mContext, mCachedDevice1, mBluetoothRouteInfo1);
mBluetoothMediaDevice2 =
new BluetoothMediaDevice(
- mContext, mCachedDevice2, mBluetoothRouteInfo2, TEST_PACKAGE_NAME);
+ mContext, mCachedDevice2, mBluetoothRouteInfo2);
mBluetoothMediaDevice3 =
new BluetoothMediaDevice(
- mContext, mCachedDevice3, mBluetoothRouteInfo3, TEST_PACKAGE_NAME);
- mInfoMediaDevice1 = new InfoMediaDevice(mContext, mRouteInfo1, TEST_PACKAGE_NAME);
- mInfoMediaDevice2 = new InfoMediaDevice(mContext, mRouteInfo2, TEST_PACKAGE_NAME);
- mInfoMediaDevice3 = new InfoMediaDevice(mContext, mRouteInfo3, TEST_PACKAGE_NAME);
- mPhoneMediaDevice = new PhoneMediaDevice(mContext, mPhoneRouteInfo, TEST_PACKAGE_NAME);
+ mContext, mCachedDevice3, mBluetoothRouteInfo3);
+ mInfoMediaDevice1 = new InfoMediaDevice(mContext, mRouteInfo1);
+ mInfoMediaDevice2 = new InfoMediaDevice(mContext, mRouteInfo2);
+ mInfoMediaDevice3 = new InfoMediaDevice(mContext, mRouteInfo3);
+ mPhoneMediaDevice = new PhoneMediaDevice(mContext, mPhoneRouteInfo);
}
@Test
@@ -316,7 +316,7 @@
when(phoneRouteInfo.getType()).thenReturn(TYPE_WIRED_HEADPHONES);
final PhoneMediaDevice phoneMediaDevice =
- new PhoneMediaDevice(mContext, phoneRouteInfo, TEST_PACKAGE_NAME);
+ new PhoneMediaDevice(mContext, phoneRouteInfo);
mMediaDevices.add(mBluetoothMediaDevice1);
mMediaDevices.add(phoneMediaDevice);
@@ -332,7 +332,7 @@
when(phoneRouteInfo.getType()).thenReturn(TYPE_WIRED_HEADPHONES);
final PhoneMediaDevice phoneMediaDevice =
- new PhoneMediaDevice(mContext, phoneRouteInfo, TEST_PACKAGE_NAME);
+ new PhoneMediaDevice(mContext, phoneRouteInfo);
mMediaDevices.add(mInfoMediaDevice1);
mMediaDevices.add(phoneMediaDevice);
@@ -483,7 +483,7 @@
public void getFeatures_noRouteInfo_returnEmptyList() {
mBluetoothMediaDevice1 =
new BluetoothMediaDevice(
- mContext, mCachedDevice1, null /* MediaRoute2Info */, TEST_PACKAGE_NAME);
+ mContext, mCachedDevice1, /* MediaRoute2Info */ null);
assertThat(mBluetoothMediaDevice1.getFeatures().size()).isEqualTo(0);
}
@@ -498,10 +498,9 @@
mContext,
mCachedDevice1,
null /* MediaRoute2Info */,
- TEST_PACKAGE_NAME,
mItem);
mPhoneMediaDevice =
- new PhoneMediaDevice(mContext, mPhoneRouteInfo, TEST_PACKAGE_NAME, mItem);
+ new PhoneMediaDevice(mContext, mPhoneRouteInfo, mItem);
assertThat(mBluetoothMediaDevice1.getSelectionBehavior()).isEqualTo(
SELECTION_BEHAVIOR_TRANSFER);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceUtilsTest.java
deleted file mode 100644
index 30a6ad2..0000000
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceUtilsTest.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright 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.settingslib.media;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.when;
-
-import android.bluetooth.BluetoothDevice;
-import android.media.MediaRoute2Info;
-
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-
-@RunWith(RobolectricTestRunner.class)
-public class MediaDeviceUtilsTest {
-
- private static final String TEST_ADDRESS = "11:22:33:44:55:66";
- private static final String TEST_ROUTE_ID = "test_route_id";
-
- @Mock
- private CachedBluetoothDevice mCachedDevice;
- @Mock
- private BluetoothDevice mBluetoothDevice;
- @Mock
- private MediaRoute2Info mRouteInfo;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- }
-
- @Test
- public void getId_returnCachedBluetoothDeviceAddress() {
- when(mCachedDevice.getAddress()).thenReturn(TEST_ADDRESS);
-
- final String id = MediaDeviceUtils.getId(mCachedDevice);
-
- assertThat(id).isEqualTo(TEST_ADDRESS);
- }
-
- @Test
- public void getId_returnBluetoothDeviceAddress() {
- when(mBluetoothDevice.getAddress()).thenReturn(TEST_ADDRESS);
-
- final String id = MediaDeviceUtils.getId(mBluetoothDevice);
-
- assertThat(id).isEqualTo(TEST_ADDRESS);
- }
-
- @Test
- public void getId_returnRouteInfoId() {
- when(mRouteInfo.getId()).thenReturn(TEST_ROUTE_ID);
-
- final String id = MediaDeviceUtils.getId(mRouteInfo);
-
- assertThat(id).isEqualTo(TEST_ROUTE_ID);
- }
-}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index d6e8d26..add3134 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -69,6 +69,7 @@
Settings.Global.PRIVATE_DNS_SPECIFIER,
Settings.Global.SOFT_AP_TIMEOUT_ENABLED,
Settings.Global.ZEN_DURATION,
+ Settings.Global.MUTE_ALARM_STREAM_WITH_RINGER_MODE_USER_PREFERENCE,
Settings.Global.REVERSE_CHARGING_AUTO_ON,
Settings.Global.CHARGING_VIBRATION_ENABLED,
Settings.Global.AWARE_ALLOWED,
@@ -92,6 +93,7 @@
Settings.Global.Wearable.CLOCKWORK_AUTO_TIME,
Settings.Global.Wearable.CLOCKWORK_AUTO_TIME_ZONE,
Settings.Global.Wearable.CLOCKWORK_24HR_TIME,
+ Settings.Global.Wearable.CONSISTENT_NOTIFICATION_BLOCKING_ENABLED,
Settings.Global.Wearable.MUTE_WHEN_OFF_BODY_ENABLED,
Settings.Global.Wearable.AMBIENT_ENABLED,
Settings.Global.Wearable.AMBIENT_TILT_TO_WAKE,
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 59c3cd3..e7d7bb0 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -19,93 +19,105 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.provider.Settings;
+import com.android.server.display.feature.flags.Flags;
+
+import java.util.ArrayList;
+import java.util.List;
+
/** Information about the system settings to back up */
public class SystemSettings {
/**
- * Settings to backup.
+ * Settings to back up.
*
* NOTE: Settings are backed up and restored in the order they appear
* in this array. If you have one setting depending on another,
* make sure that they are ordered appropriately.
*/
@UnsupportedAppUsage
- public static final String[] SETTINGS_TO_BACKUP = {
- Settings.System.STAY_ON_WHILE_PLUGGED_IN, // moved to global
- Settings.System.WIFI_USE_STATIC_IP,
- Settings.System.WIFI_STATIC_IP,
- Settings.System.WIFI_STATIC_GATEWAY,
- Settings.System.WIFI_STATIC_NETMASK,
- Settings.System.WIFI_STATIC_DNS1,
- Settings.System.WIFI_STATIC_DNS2,
- Settings.System.BLUETOOTH_DISCOVERABILITY,
- Settings.System.BLUETOOTH_DISCOVERABILITY_TIMEOUT,
- Settings.System.FONT_SCALE,
- Settings.System.DIM_SCREEN,
- Settings.System.SCREEN_OFF_TIMEOUT,
- Settings.System.SCREEN_BRIGHTNESS_MODE,
- Settings.System.ADAPTIVE_SLEEP, // moved to secure
- Settings.System.APPLY_RAMPING_RINGER,
- Settings.System.VIBRATE_INPUT_DEVICES,
- Settings.System.MODE_RINGER_STREAMS_AFFECTED,
- Settings.System.TEXT_AUTO_REPLACE,
- Settings.System.TEXT_AUTO_CAPS,
- Settings.System.TEXT_AUTO_PUNCTUATE,
- Settings.System.TEXT_SHOW_PASSWORD,
- Settings.System.AUTO_TIME, // moved to global
- Settings.System.AUTO_TIME_ZONE, // moved to global
- Settings.System.TIME_12_24,
- Settings.System.DTMF_TONE_WHEN_DIALING,
- Settings.System.DTMF_TONE_TYPE_WHEN_DIALING,
- Settings.System.HEARING_AID,
- Settings.System.TTY_MODE,
- Settings.System.MASTER_MONO,
- Settings.System.MASTER_BALANCE,
- Settings.System.FOLD_LOCK_BEHAVIOR,
- Settings.System.SOUND_EFFECTS_ENABLED,
- Settings.System.HAPTIC_FEEDBACK_ENABLED,
- Settings.System.POWER_SOUNDS_ENABLED, // moved to global
- Settings.System.DOCK_SOUNDS_ENABLED, // moved to global
- Settings.System.LOCKSCREEN_SOUNDS_ENABLED,
- Settings.System.SHOW_WEB_SUGGESTIONS,
- Settings.System.SIP_CALL_OPTIONS,
- Settings.System.SIP_RECEIVE_CALLS,
- Settings.System.POINTER_SPEED,
- Settings.System.VIBRATE_ON,
- Settings.System.VIBRATE_WHEN_RINGING,
- Settings.System.RINGTONE,
- Settings.System.LOCK_TO_APP_ENABLED,
- Settings.System.NOTIFICATION_SOUND,
- Settings.System.ACCELEROMETER_ROTATION,
- Settings.System.SHOW_BATTERY_PERCENT,
- Settings.System.ALARM_VIBRATION_INTENSITY,
- Settings.System.MEDIA_VIBRATION_INTENSITY,
- Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
- Settings.System.RING_VIBRATION_INTENSITY,
- Settings.System.HAPTIC_FEEDBACK_INTENSITY,
- Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY,
- Settings.System.KEYBOARD_VIBRATION_ENABLED,
- Settings.System.HAPTIC_FEEDBACK_ENABLED,
- Settings.System.DISPLAY_COLOR_MODE_VENDOR_HINT, // must precede DISPLAY_COLOR_MODE
- Settings.System.DISPLAY_COLOR_MODE,
- Settings.System.ALARM_ALERT,
- Settings.System.NOTIFICATION_LIGHT_PULSE,
- Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED,
- Settings.System.CLOCKWORK_BLUETOOTH_SETTINGS_PREF,
- Settings.System.UNREAD_NOTIFICATION_DOT_INDICATOR,
- Settings.System.AUTO_LAUNCH_MEDIA_CONTROLS,
- Settings.System.LOCALE_PREFERENCES,
- Settings.System.TOUCHPAD_POINTER_SPEED,
- Settings.System.TOUCHPAD_NATURAL_SCROLLING,
- Settings.System.TOUCHPAD_TAP_TO_CLICK,
- Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE,
- Settings.System.CAMERA_FLASH_NOTIFICATION,
- Settings.System.SCREEN_FLASH_NOTIFICATION,
- Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR,
- Settings.System.PEAK_REFRESH_RATE,
- Settings.System.MIN_REFRESH_RATE,
- Settings.System.NOTIFICATION_COOLDOWN_ENABLED,
- Settings.System.NOTIFICATION_COOLDOWN_ALL,
- Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
- };
+ public static final String[] SETTINGS_TO_BACKUP = getSettingsToBackUp();
+
+ private static String[] getSettingsToBackUp() {
+ List<String> settings = new ArrayList<>(List.of(
+ Settings.System.STAY_ON_WHILE_PLUGGED_IN, // moved to global
+ Settings.System.WIFI_USE_STATIC_IP,
+ Settings.System.WIFI_STATIC_IP,
+ Settings.System.WIFI_STATIC_GATEWAY,
+ Settings.System.WIFI_STATIC_NETMASK,
+ Settings.System.WIFI_STATIC_DNS1,
+ Settings.System.WIFI_STATIC_DNS2,
+ Settings.System.BLUETOOTH_DISCOVERABILITY,
+ Settings.System.BLUETOOTH_DISCOVERABILITY_TIMEOUT,
+ Settings.System.FONT_SCALE,
+ Settings.System.DIM_SCREEN,
+ Settings.System.SCREEN_OFF_TIMEOUT,
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.ADAPTIVE_SLEEP, // moved to secure
+ Settings.System.APPLY_RAMPING_RINGER,
+ Settings.System.VIBRATE_INPUT_DEVICES,
+ Settings.System.MODE_RINGER_STREAMS_AFFECTED,
+ Settings.System.TEXT_AUTO_REPLACE,
+ Settings.System.TEXT_AUTO_CAPS,
+ Settings.System.TEXT_AUTO_PUNCTUATE,
+ Settings.System.TEXT_SHOW_PASSWORD,
+ Settings.System.AUTO_TIME, // moved to global
+ Settings.System.AUTO_TIME_ZONE, // moved to global
+ Settings.System.TIME_12_24,
+ Settings.System.DTMF_TONE_WHEN_DIALING,
+ Settings.System.DTMF_TONE_TYPE_WHEN_DIALING,
+ Settings.System.HEARING_AID,
+ Settings.System.TTY_MODE,
+ Settings.System.MASTER_MONO,
+ Settings.System.MASTER_BALANCE,
+ Settings.System.FOLD_LOCK_BEHAVIOR,
+ Settings.System.SOUND_EFFECTS_ENABLED,
+ Settings.System.HAPTIC_FEEDBACK_ENABLED,
+ Settings.System.POWER_SOUNDS_ENABLED, // moved to global
+ Settings.System.DOCK_SOUNDS_ENABLED, // moved to global
+ Settings.System.LOCKSCREEN_SOUNDS_ENABLED,
+ Settings.System.SHOW_WEB_SUGGESTIONS,
+ Settings.System.SIP_CALL_OPTIONS,
+ Settings.System.SIP_RECEIVE_CALLS,
+ Settings.System.POINTER_SPEED,
+ Settings.System.VIBRATE_ON,
+ Settings.System.VIBRATE_WHEN_RINGING,
+ Settings.System.RINGTONE,
+ Settings.System.LOCK_TO_APP_ENABLED,
+ Settings.System.NOTIFICATION_SOUND,
+ Settings.System.ACCELEROMETER_ROTATION,
+ Settings.System.SHOW_BATTERY_PERCENT,
+ Settings.System.ALARM_VIBRATION_INTENSITY,
+ Settings.System.MEDIA_VIBRATION_INTENSITY,
+ Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+ Settings.System.RING_VIBRATION_INTENSITY,
+ Settings.System.HAPTIC_FEEDBACK_INTENSITY,
+ Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY,
+ Settings.System.KEYBOARD_VIBRATION_ENABLED,
+ Settings.System.HAPTIC_FEEDBACK_ENABLED,
+ Settings.System.DISPLAY_COLOR_MODE_VENDOR_HINT, // must precede DISPLAY_COLOR_MODE
+ Settings.System.DISPLAY_COLOR_MODE,
+ Settings.System.ALARM_ALERT,
+ Settings.System.NOTIFICATION_LIGHT_PULSE,
+ Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED,
+ Settings.System.CLOCKWORK_BLUETOOTH_SETTINGS_PREF,
+ Settings.System.UNREAD_NOTIFICATION_DOT_INDICATOR,
+ Settings.System.AUTO_LAUNCH_MEDIA_CONTROLS,
+ Settings.System.LOCALE_PREFERENCES,
+ Settings.System.TOUCHPAD_POINTER_SPEED,
+ Settings.System.TOUCHPAD_NATURAL_SCROLLING,
+ Settings.System.TOUCHPAD_TAP_TO_CLICK,
+ Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE,
+ Settings.System.CAMERA_FLASH_NOTIFICATION,
+ Settings.System.SCREEN_FLASH_NOTIFICATION,
+ Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR,
+ Settings.System.NOTIFICATION_COOLDOWN_ENABLED,
+ Settings.System.NOTIFICATION_COOLDOWN_ALL,
+ Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED
+ ));
+ if (Flags.backUpSmoothDisplayAndForcePeakRefreshRate()) {
+ settings.add(Settings.System.PEAK_REFRESH_RATE);
+ settings.add(Settings.System.MIN_REFRESH_RATE);
+ }
+ return settings.toArray(new String[0]);
+ }
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index f8bdcf6..c0a0760 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -210,6 +210,8 @@
VALIDATORS.put(Global.POWER_BUTTON_LONG_PRESS_DURATION_MS, NONE_NEGATIVE_LONG_VALIDATOR);
VALIDATORS.put(Global.STYLUS_EVER_USED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.MUTE_ALARM_STREAM_WITH_RINGER_MODE, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(
+ Global.MUTE_ALARM_STREAM_WITH_RINGER_MODE_USER_PREFERENCE, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.HAS_PAY_TOKENS, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.GMS_CHECKIN_TIMEOUT_MIN, ANY_INTEGER_VALIDATOR);
@@ -448,6 +450,8 @@
VALIDATORS.put(Global.Wearable.WEAR_POWER_ANOMALY_SERVICE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.CONNECTIVITY_KEEP_DATA_ON, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.WRIST_DETECTION_AUTO_LOCKING_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(
+ Global.Wearable.CONSISTENT_NOTIFICATION_BLOCKING_ENABLED, ANY_INTEGER_VALIDATOR);
VALIDATORS.put(Global.FORCE_ENABLE_PSS_PROFILING, BOOLEAN_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java
index bd99a8b..74fd828 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java
@@ -99,7 +99,6 @@
"kiwi",
"latency_tracker",
"launcher",
- "launcher_lily",
"leaked_animator",
"lmkd_native",
"location",
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 6ad10cc..1481d97 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -33,6 +33,8 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.display.feature.flags.Flags;
+
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -53,59 +55,6 @@
public static final String HYBRID_SYSUI_BATTERY_WARNING_FLAGS =
"hybrid_sysui_battery_warning_flags";
- /**
- * The following denylists contain settings that should *not* be backed up and restored to
- * another device. As a general rule, anything that is not user configurable should be
- * denied (and conversely, things that *are* user configurable *should* be backed up)
- */
- private static final Set<String> BACKUP_DENY_LIST_SYSTEM_SETTINGS =
- newHashSet(
- Settings.System.ADVANCED_SETTINGS, // candidate for backup?
- Settings.System.ALARM_ALERT_CACHE, // internal cache
- Settings.System.APPEND_FOR_LAST_AUDIBLE, // suffix deprecated since API 2
- Settings.System.EGG_MODE, // I am the lolrus
- Settings.System.END_BUTTON_BEHAVIOR, // bug?
- Settings.System
- .HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, // candidate for backup?
- Settings.System.LOCKSCREEN_DISABLED, // ?
- Settings.System.MEDIA_BUTTON_RECEIVER, // candidate for backup?
- Settings.System.MUTE_STREAMS_AFFECTED, // candidate for backup?
- Settings.System.NOTIFICATION_SOUND_CACHE, // internal cache
- Settings.System.POINTER_LOCATION, // backup candidate?
- Settings.System.DEBUG_ENABLE_ENHANCED_CALL_BLOCKING, // used for testing only
- Settings.System.RINGTONE_CACHE, // internal cache
- Settings.System.SCREEN_BRIGHTNESS, // removed in P
- Settings.System.SETUP_WIZARD_HAS_RUN, // Only used by SuW
- Settings.System.SHOW_GTALK_SERVICE_STATUS, // candidate for backup?
- Settings.System.SHOW_TOUCHES,
- Settings.System.SHOW_KEY_PRESSES,
- Settings.System.SHOW_ROTARY_INPUT,
- Settings.System.SIP_ADDRESS_ONLY, // value, not a setting
- Settings.System.SIP_ALWAYS, // value, not a setting
- Settings.System.SYSTEM_LOCALES, // bug?
- Settings.System.USER_ROTATION, // backup candidate?
- Settings.System.VIBRATE_IN_SILENT, // deprecated?
- Settings.System.VOLUME_ACCESSIBILITY, // used internally, changing value will
- // not change volume
- Settings.System.VOLUME_ALARM, // deprecated since API 2?
- Settings.System.VOLUME_ASSISTANT, // candidate for backup?
- Settings.System.VOLUME_BLUETOOTH_SCO, // deprecated since API 2?
- Settings.System.VOLUME_MASTER, // candidate for backup?
- Settings.System.VOLUME_MUSIC, // deprecated since API 2?
- Settings.System.VOLUME_NOTIFICATION, // deprecated since API 2?
- Settings.System.VOLUME_RING, // deprecated since API 2?
- Settings.System.VOLUME_SYSTEM, // deprecated since API 2?
- Settings.System.VOLUME_VOICE, // deprecated since API 2?
- Settings.System.WHEN_TO_MAKE_WIFI_CALLS, // bug?
- Settings.System.WINDOW_ORIENTATION_LISTENER_LOG, // used for debugging only
- Settings.System.SCREEN_BRIGHTNESS_FLOAT,
- Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
- Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE,
- Settings.System.WEAR_TTS_PREWARM_ENABLED,
- Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
- Settings.System.MULTI_AUDIO_FOCUS_ENABLED // form-factor/OEM specific
- );
-
private static final Set<String> BACKUP_DENY_LIST_GLOBAL_SETTINGS =
newHashSet(
Settings.Global.ACTIVITY_MANAGER_CONSTANTS,
@@ -737,6 +686,7 @@
Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS,
Settings.Secure.CONTENT_CAPTURE_ENABLED,
Settings.Secure.DEFAULT_INPUT_METHOD,
+ Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD,
Settings.Secure.DEVICE_PAIRED,
Settings.Secure.DIALER_DEFAULT_APPLICATION,
Settings.Secure.DISABLED_PRINT_SERVICES,
@@ -862,7 +812,7 @@
checkSettingsBackedUpOrDenied(
getCandidateSettings(Settings.System.class),
newHashSet(SystemSettings.SETTINGS_TO_BACKUP),
- BACKUP_DENY_LIST_SYSTEM_SETTINGS);
+ getBackUpDenyListSystemSettings());
}
@Test
@@ -937,6 +887,69 @@
checkSettingsBackedUpOrDenied(allSettings, keys, BACKUP_DENY_LIST_SECURE_SETTINGS);
}
+ /**
+ * The following denylists contain settings that should *not* be backed up and restored to
+ * another device. As a general rule, anything that is not user configurable should be
+ * denied (and conversely, things that *are* user configurable *should* be backed up)
+ */
+ private static Set<String> getBackUpDenyListSystemSettings() {
+ Set<String> settings =
+ newHashSet(
+ Settings.System.ADVANCED_SETTINGS, // candidate for backup?
+ Settings.System.ALARM_ALERT_CACHE, // internal cache
+ Settings.System.APPEND_FOR_LAST_AUDIBLE, // suffix deprecated since API 2
+ Settings.System.EGG_MODE, // I am the lolrus
+ Settings.System.END_BUTTON_BEHAVIOR, // bug?
+ Settings.System
+ .HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY,
+ // candidate for backup?
+ Settings.System.LOCKSCREEN_DISABLED, // ?
+ Settings.System.MEDIA_BUTTON_RECEIVER, // candidate for backup?
+ Settings.System.MUTE_STREAMS_AFFECTED, // candidate for backup?
+ Settings.System.NOTIFICATION_SOUND_CACHE, // internal cache
+ Settings.System.POINTER_LOCATION, // backup candidate?
+ Settings.System.DEBUG_ENABLE_ENHANCED_CALL_BLOCKING,
+ // used for testing only
+ Settings.System.RINGTONE_CACHE, // internal cache
+ Settings.System.SCREEN_BRIGHTNESS, // removed in P
+ Settings.System.SETUP_WIZARD_HAS_RUN, // Only used by SuW
+ Settings.System.SHOW_GTALK_SERVICE_STATUS, // candidate for backup?
+ Settings.System.SHOW_TOUCHES,
+ Settings.System.SHOW_KEY_PRESSES,
+ Settings.System.SHOW_ROTARY_INPUT,
+ Settings.System.SIP_ADDRESS_ONLY, // value, not a setting
+ Settings.System.SIP_ALWAYS, // value, not a setting
+ Settings.System.SYSTEM_LOCALES, // bug?
+ Settings.System.USER_ROTATION, // backup candidate?
+ Settings.System.VIBRATE_IN_SILENT, // deprecated?
+ Settings.System.VOLUME_ACCESSIBILITY,
+ // used internally, changing value will
+ // not change volume
+ Settings.System.VOLUME_ALARM, // deprecated since API 2?
+ Settings.System.VOLUME_ASSISTANT, // candidate for backup?
+ Settings.System.VOLUME_BLUETOOTH_SCO, // deprecated since API 2?
+ Settings.System.VOLUME_MASTER, // candidate for backup?
+ Settings.System.VOLUME_MUSIC, // deprecated since API 2?
+ Settings.System.VOLUME_NOTIFICATION, // deprecated since API 2?
+ Settings.System.VOLUME_RING, // deprecated since API 2?
+ Settings.System.VOLUME_SYSTEM, // deprecated since API 2?
+ Settings.System.VOLUME_VOICE, // deprecated since API 2?
+ Settings.System.WHEN_TO_MAKE_WIFI_CALLS, // bug?
+ Settings.System.WINDOW_ORIENTATION_LISTENER_LOG, // used for debugging only
+ Settings.System.SCREEN_BRIGHTNESS_FLOAT,
+ Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+ Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE,
+ Settings.System.WEAR_TTS_PREWARM_ENABLED,
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
+ Settings.System.MULTI_AUDIO_FOCUS_ENABLED // form-factor/OEM specific
+ );
+ if (!Flags.backUpSmoothDisplayAndForcePeakRefreshRate()) {
+ settings.add(Settings.System.MIN_REFRESH_RATE);
+ settings.add(Settings.System.PEAK_REFRESH_RATE);
+ }
+ return settings;
+ }
+
private static void checkSettingsBackedUpOrDenied(
Set<String> settings, Set<String> settingsToBackup, Set<String> denylist) {
Set<String> settingsNotBackedUp = difference(settings, settingsToBackup);
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 477c42e..507d9c4 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -810,6 +810,9 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_FILE_MANAGEMENT" />
<!-- Permission required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROCESSING" />
+
+ <!-- Permission required for CTS test - CtsAppFgsTestCases -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<!-- Permissions required for CTS test - CtsAppFgsTestCases -->
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index cc5dfc6..d3a89f4 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -65,6 +65,7 @@
"androidx.compose.runtime_runtime",
"androidx.compose.material3_material3",
+ "androidx.compose.material_material-icons-extended",
"androidx.activity_activity-compose",
"androidx.compose.animation_animation-graphics",
],
@@ -156,7 +157,7 @@
"SystemUI-res",
"WifiTrackerLib",
"WindowManager-Shell",
- "SystemUIAnimationLib",
+ "PlatformAnimationLib",
"SystemUICommon",
"SystemUICustomizationLib",
"SystemUILogLib",
@@ -273,7 +274,7 @@
static_libs: [
"SystemUI-res",
"WifiTrackerLib",
- "SystemUIAnimationLib",
+ "PlatformAnimationLib",
"SystemUIPluginLib",
"SystemUISharedLib",
"SystemUICustomizationLib",
diff --git a/packages/SystemUI/aconfig/Android.bp b/packages/SystemUI/aconfig/Android.bp
index 7f16ca5..03f9d74 100644
--- a/packages/SystemUI/aconfig/Android.bp
+++ b/packages/SystemUI/aconfig/Android.bp
@@ -25,6 +25,7 @@
"//frameworks/base/packages/SystemUI:__subpackages__",
"//frameworks/libs/systemui/tracinglib:__subpackages__",
"//platform_testing:__subpackages__",
+ "//cts:__subpackages__",
],
}
diff --git a/packages/SystemUI/aconfig/predictive_back.aconfig b/packages/SystemUI/aconfig/predictive_back.aconfig
new file mode 100644
index 0000000..d0e6b28
--- /dev/null
+++ b/packages/SystemUI/aconfig/predictive_back.aconfig
@@ -0,0 +1,29 @@
+package: "com.android.systemui"
+
+flag {
+ name: "predictive_back_sysui"
+ namespace: "systemui"
+ description: "Predictive Back Dispatching for SysUI"
+ bug: "309545085"
+}
+
+flag {
+ name: "predictive_back_animate_shade"
+ namespace: "systemui"
+ description: "Enable Shade Animations"
+ bug: "309545085"
+}
+
+flag {
+ name: "predictive_back_animate_bouncer"
+ namespace: "systemui"
+ description: "Enable Predictive Back Animation in Bouncer"
+ bug: "309545085"
+}
+
+flag {
+ name: "predictive_back_animate_dialogs"
+ namespace: "systemui"
+ description: "Enable Predictive Back Animation for SysUI dialogs"
+ bug: "309545085"
+}
\ No newline at end of file
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index aa0903c..5b21854 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -165,7 +165,7 @@
name: "qs_new_tiles"
namespace: "systemui"
description: "Use the new tiles in the Quick Settings. Should have no behavior changes."
- bug: "241772429"
+ bug: "311147395"
}
flag {
@@ -308,6 +308,13 @@
}
flag {
+ name: "run_fingerprint_detect_on_dismissible_keyguard"
+ namespace: "systemui"
+ description: "Run fingerprint detect instead of authenticate if the keyguard is dismissible."
+ bug: "311145851"
+}
+
+flag {
name: "bluetooth_qs_tile_dialog_auto_on_toggle"
namespace: "systemui"
description: "Displays the auto on toggle in the bluetooth QS tile dialog"
diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp
index 8438051..872187a 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -23,7 +23,7 @@
android_library {
- name: "SystemUIAnimationLib",
+ name: "PlatformAnimationLib",
use_resource_processor: true,
srcs: [
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index b738e2b..efdbfdb 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -494,7 +494,7 @@
}
for (i in 0 until drawable.numberOfLayers) {
- (drawable.getDrawable(i) as? GradientDrawable)?.cornerRadii = radii
+ applyBackgroundRadii(drawable.getDrawable(i), radii)
}
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
index 23fcb69..867bbb7d 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
@@ -65,12 +65,33 @@
return dest;
}
- // Return range [-1, 1].
+ // Integer mod. GLSL es 1.0 doesn't have integer mod :(
+ int imod(int a, int b) {
+ return a - (b * (a / b));
+ }
+
+ ivec3 imod(ivec3 a, int b) {
+ return ivec3(imod(a.x, b), imod(a.y, b), imod(a.z, b));
+ }
+
+ // Integer based hash function with the return range of [-1, 1].
vec3 hash(vec3 p) {
- p = fract(p * vec3(.3456, .1234, .9876));
- p += dot(p, p.yxz + 43.21);
- p = (p.xxy + p.yxx) * p.zyx;
- return (fract(sin(p) * 4567.1234567) - .5) * 2.;
+ ivec3 v = ivec3(p);
+ v = v * 1671731 + 10139267;
+
+ v.x += v.y * v.z;
+ v.y += v.z * v.x;
+ v.z += v.x * v.y;
+
+ ivec3 v2 = v / 65536; // v >> 16
+ v = imod((10 - imod((v + v2), 10)), 10); // v ^ v2
+
+ v.x += v.y * v.z;
+ v.y += v.z * v.x;
+ v.z += v.x * v.y;
+
+ // Use sin and cos to map the range to [-1, 1].
+ return vec3(sin(float(v.x)), cos(float(v.y)), sin(float(v.z)));
}
// Skew factors (non-uniform).
diff --git a/packages/SystemUI/compose/core/Android.bp b/packages/SystemUI/compose/core/Android.bp
index 42d088f..9a4347d 100644
--- a/packages/SystemUI/compose/core/Android.bp
+++ b/packages/SystemUI/compose/core/Android.bp
@@ -30,7 +30,7 @@
],
static_libs: [
- "SystemUIAnimationLib",
+ "PlatformAnimationLib",
"androidx.compose.runtime_runtime",
"androidx.compose.material3_material3",
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/platform/DensityAwareComposeView.kt b/packages/SystemUI/compose/core/src/com/android/compose/ui/platform/DensityAwareComposeView.kt
new file mode 100644
index 0000000..dff8753
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/ui/platform/DensityAwareComposeView.kt
@@ -0,0 +1,96 @@
+/*
+ * 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.compose.ui.platform
+
+import android.content.Context
+import android.content.res.Configuration
+import android.util.AttributeSet
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.platform.AbstractComposeView
+
+/**
+ * A ComposeView that recreates its composition if the display size or font scale was changed.
+ *
+ * TODO(b/317317814): Remove this workaround.
+ */
+class DensityAwareComposeView(context: Context) : OpenComposeView(context) {
+ private var lastDensityDpi: Int = -1
+ private var lastFontScale: Float = -1f
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+
+ val configuration = context.resources.configuration
+ lastDensityDpi = configuration.densityDpi
+ lastFontScale = configuration.fontScale
+ }
+
+ override fun dispatchConfigurationChanged(newConfig: Configuration) {
+ super.dispatchConfigurationChanged(newConfig)
+
+ // If the density or font scale changed, we dispose then recreate the composition. Note that
+ // we do this here after dispatching the new configuration to children (instead of doing
+ // this in onConfigurationChanged()) because the new configuration should first be
+ // dispatched to the AndroidComposeView that holds the current density before we recreate
+ // the composition.
+ val densityDpi = newConfig.densityDpi
+ val fontScale = newConfig.fontScale
+ if (densityDpi != lastDensityDpi || fontScale != lastFontScale) {
+ lastDensityDpi = densityDpi
+ lastFontScale = fontScale
+
+ disposeComposition()
+ if (isAttachedToWindow) {
+ createComposition()
+ }
+ }
+ }
+}
+
+/** A fork of [androidx.compose.ui.platform.ComposeView] that is open and can be subclassed. */
+open class OpenComposeView
+internal constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
+ AbstractComposeView(context, attrs, defStyleAttr) {
+
+ private val content = mutableStateOf<(@Composable () -> Unit)?>(null)
+
+ @Suppress("RedundantVisibilityModifier")
+ protected override var shouldCreateCompositionOnAttachedToWindow: Boolean = false
+
+ @Composable
+ override fun Content() {
+ content.value?.invoke()
+ }
+
+ override fun getAccessibilityClassName(): CharSequence {
+ return javaClass.name
+ }
+
+ /**
+ * Set the Jetpack Compose UI content for this view. Initial composition will occur when the
+ * view becomes attached to a window or when [createComposition] is called, whichever comes
+ * first.
+ */
+ fun setContent(content: @Composable () -> Unit) {
+ shouldCreateCompositionOnAttachedToWindow = true
+ this.content.value = content
+ if (isAttachedToWindow) {
+ createComposition()
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index 5055ee1..d31547b 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -27,6 +27,7 @@
import androidx.compose.ui.unit.dp
import androidx.lifecycle.LifecycleOwner
import com.android.compose.theme.PlatformTheme
+import com.android.compose.ui.platform.DensityAwareComposeView
import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout
import com.android.systemui.common.ui.compose.windowinsets.DisplayCutoutProvider
@@ -84,7 +85,7 @@
viewModel: FooterActionsViewModel,
qsVisibilityLifecycleOwner: LifecycleOwner,
): View {
- return ComposeView(context).apply {
+ return DensityAwareComposeView(context).apply {
setContent { PlatformTheme { FooterActions(viewModel, qsVisibilityLifecycleOwner) } }
}
}
diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp
index 796abf4b..5ab2235 100644
--- a/packages/SystemUI/compose/features/Android.bp
+++ b/packages/SystemUI/compose/features/Android.bp
@@ -38,6 +38,7 @@
"androidx.compose.runtime_runtime",
"androidx.compose.animation_animation-graphics",
"androidx.compose.material3_material3",
+ "androidx.compose.material_material-icons-extended",
"androidx.activity_activity-compose",
],
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 5a4e0a9..d201544 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
@@ -20,13 +20,13 @@
import android.os.Bundle
import android.util.SizeF
import android.widget.FrameLayout
-import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -45,6 +45,7 @@
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.outlined.Delete
+import androidx.compose.material.icons.outlined.Widgets
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ButtonDefaults
@@ -52,6 +53,7 @@
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -72,6 +74,7 @@
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
@@ -100,7 +103,8 @@
var isDraggingToRemove by remember { mutableStateOf(false) }
Box(
- modifier = modifier.fillMaxSize().background(Color.White),
+ modifier =
+ modifier.fillMaxSize().background(LocalAndroidColorScheme.current.outlineVariant),
) {
CommunalHubLazyGrid(
communalContent = communalContent,
@@ -111,7 +115,8 @@
isDraggingToRemove =
checkForDraggingToRemove(it, removeButtonCoordinates, gridCoordinates)
isDraggingToRemove
- }
+ },
+ onOpenWidgetPicker = onOpenWidgetPicker,
)
if (viewModel.isEditMode && onOpenWidgetPicker != null && onEditDone != null) {
@@ -148,13 +153,14 @@
contentPadding: PaddingValues,
setGridCoordinates: (coordinates: LayoutCoordinates) -> Unit,
updateDragPositionForRemove: (offset: Offset) -> Boolean,
+ onOpenWidgetPicker: (() -> Unit)? = null,
) {
var gridModifier = Modifier.align(Alignment.CenterStart)
val gridState = rememberLazyGridState()
var list = communalContent
var dragDropState: GridDragDropState? = null
if (viewModel.isEditMode && viewModel is CommunalEditModeViewModel) {
- val contentListState = rememberContentListState(communalContent, viewModel)
+ val contentListState = rememberContentListState(list, viewModel)
list = contentListState.list
// for drag & drop operations within the communal hub grid
dragDropState =
@@ -205,15 +211,18 @@
list[index].size.dp().value,
)
if (viewModel.isEditMode && dragDropState != null) {
- DraggableItem(dragDropState = dragDropState, enabled = true, index = index) {
- isDragging ->
- val elevation by animateDpAsState(if (isDragging) 4.dp else 1.dp)
+ DraggableItem(
+ dragDropState = dragDropState,
+ enabled = list[index] is CommunalContentModel.Widget,
+ index = index,
+ size = size
+ ) { _ ->
CommunalContent(
modifier = cardModifier,
- elevation = elevation,
model = list[index],
viewModel = viewModel,
size = size,
+ onOpenWidgetPicker = onOpenWidgetPicker,
)
}
} else {
@@ -254,16 +263,11 @@
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
- val buttonContentPadding =
- PaddingValues(
- vertical = Dimensions.ToolbarButtonPaddingVertical,
- horizontal = Dimensions.ToolbarButtonPaddingHorizontal,
- )
val spacerModifier = Modifier.width(Dimensions.ToolbarButtonSpaceBetween)
Button(
onClick = onOpenWidgetPicker,
- colors = filledSecondaryButtonColors(),
- contentPadding = buttonContentPadding
+ colors = filledButtonColors(),
+ contentPadding = Dimensions.ButtonPadding
) {
Icon(Icons.Default.Add, stringResource(R.string.button_to_open_widget_editor))
Spacer(spacerModifier)
@@ -272,25 +276,40 @@
)
}
- val buttonColors =
- if (isDraggingToRemove) filledButtonColors() else ButtonDefaults.outlinedButtonColors()
- OutlinedButton(
- onClick = {},
- colors = buttonColors,
- contentPadding = buttonContentPadding,
- modifier = Modifier.onGloballyPositioned { setRemoveButtonCoordinates(it) },
- ) {
- Icon(Icons.Outlined.Delete, stringResource(R.string.button_to_open_widget_editor))
- Spacer(spacerModifier)
- Text(
- text = stringResource(R.string.button_to_remove_widget),
- )
+ val colors = LocalAndroidColorScheme.current
+ if (isDraggingToRemove) {
+ Button(
+ // Button is disabled to make it non-clickable
+ enabled = false,
+ onClick = {},
+ colors =
+ ButtonDefaults.buttonColors(
+ disabledContainerColor = colors.primary,
+ disabledContentColor = colors.onPrimary,
+ ),
+ contentPadding = Dimensions.ButtonPadding,
+ modifier = Modifier.onGloballyPositioned { setRemoveButtonCoordinates(it) }
+ ) {
+ RemoveButtonContent(spacerModifier)
+ }
+ } else {
+ OutlinedButton(
+ // Button is disabled to make it non-clickable
+ enabled = false,
+ onClick = {},
+ colors = ButtonDefaults.outlinedButtonColors(disabledContentColor = colors.primary),
+ border = BorderStroke(width = 1.0.dp, color = colors.primary),
+ contentPadding = Dimensions.ButtonPadding,
+ modifier = Modifier.onGloballyPositioned { setRemoveButtonCoordinates(it) }
+ ) {
+ RemoveButtonContent(spacerModifier)
+ }
}
Button(
onClick = onEditDone,
colors = filledButtonColors(),
- contentPadding = buttonContentPadding
+ contentPadding = Dimensions.ButtonPadding
) {
Text(
text = stringResource(R.string.hub_mode_editing_exit_button_text),
@@ -300,6 +319,15 @@
}
@Composable
+private fun RemoveButtonContent(spacerModifier: Modifier) {
+ Icon(Icons.Outlined.Delete, stringResource(R.string.button_to_open_widget_editor))
+ Spacer(spacerModifier)
+ Text(
+ text = stringResource(R.string.button_to_remove_widget),
+ )
+}
+
+@Composable
private fun filledButtonColors(): ButtonColors {
val colors = LocalAndroidColorScheme.current
return ButtonDefaults.buttonColors(
@@ -309,25 +337,20 @@
}
@Composable
-private fun filledSecondaryButtonColors(): ButtonColors {
- val colors = LocalAndroidColorScheme.current
- return ButtonDefaults.buttonColors(
- containerColor = colors.secondary,
- contentColor = colors.onSecondary,
- )
-}
-
-@Composable
private fun CommunalContent(
model: CommunalContentModel,
viewModel: BaseCommunalViewModel,
size: SizeF,
modifier: Modifier = Modifier,
- elevation: Dp = 0.dp,
+ onOpenWidgetPicker: (() -> Unit)? = null,
) {
when (model) {
- is CommunalContentModel.Widget -> WidgetContent(model, size, elevation, modifier)
+ is CommunalContentModel.Widget -> WidgetContent(viewModel, model, size, modifier)
is CommunalContentModel.WidgetPlaceholder -> WidgetPlaceholderContent(size)
+ is CommunalContentModel.CtaTileInViewMode ->
+ CtaTileInViewModeContent(viewModel, size, modifier)
+ is CommunalContentModel.CtaTileInEditMode ->
+ CtaTileInEditModeContent(size, modifier, onOpenWidgetPicker)
is CommunalContentModel.Smartspace -> SmartspaceContent(model, modifier)
is CommunalContentModel.Tutorial -> TutorialContent(modifier)
is CommunalContentModel.Umo -> Umo(viewModel, modifier)
@@ -345,23 +368,141 @@
) {}
}
+/** Presents a CTA tile at the end of the grid, to customize the hub. */
@Composable
-private fun WidgetContent(
- model: CommunalContentModel.Widget,
+private fun CtaTileInViewModeContent(
+ viewModel: BaseCommunalViewModel,
size: SizeF,
- elevation: Dp,
modifier: Modifier = Modifier,
) {
+ val colors = LocalAndroidColorScheme.current
Card(
modifier = modifier.height(size.height.dp),
- elevation = CardDefaults.cardElevation(draggedElevation = elevation),
+ colors =
+ CardDefaults.cardColors(
+ containerColor = colors.primary,
+ contentColor = colors.onPrimary,
+ ),
+ shape = RoundedCornerShape(80.dp, 40.dp, 80.dp, 40.dp)
+ ) {
+ Column(
+ modifier = Modifier.fillMaxSize().padding(horizontal = 82.dp),
+ verticalArrangement =
+ Arrangement.spacedBy(Dimensions.Spacing, Alignment.CenterVertically),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.Widgets,
+ contentDescription = stringResource(R.string.cta_label_to_open_widget_picker),
+ modifier = Modifier.size(Dimensions.IconSize),
+ )
+ Text(
+ text = stringResource(R.string.cta_label_to_edit_widget),
+ style = MaterialTheme.typography.titleLarge,
+ textAlign = TextAlign.Center,
+ )
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.Center,
+ ) {
+ OutlinedButton(
+ colors =
+ ButtonDefaults.buttonColors(
+ contentColor = colors.onPrimary,
+ ),
+ border = BorderStroke(width = 1.0.dp, color = colors.primaryContainer),
+ contentPadding = Dimensions.ButtonPadding,
+ onClick = viewModel::onDismissCtaTile,
+ ) {
+ Text(
+ text = stringResource(R.string.cta_tile_button_to_dismiss),
+ )
+ }
+ Spacer(modifier = Modifier.size(Dimensions.Spacing))
+ Button(
+ colors =
+ ButtonDefaults.buttonColors(
+ containerColor = colors.primaryContainer,
+ contentColor = colors.onPrimaryContainer,
+ ),
+ contentPadding = Dimensions.ButtonPadding,
+ onClick = viewModel::onOpenWidgetEditor
+ ) {
+ Text(
+ text = stringResource(R.string.cta_tile_button_to_open_widget_editor),
+ )
+ }
+ }
+ }
+ }
+}
+
+/** Presents a CTA tile at the end of the hub in edit mode, to add more widgets. */
+@Composable
+private fun CtaTileInEditModeContent(
+ size: SizeF,
+ modifier: Modifier = Modifier,
+ onOpenWidgetPicker: (() -> Unit)? = null,
+) {
+ if (onOpenWidgetPicker == null) {
+ throw IllegalArgumentException("onOpenWidgetPicker should not be null.")
+ }
+ val colors = LocalAndroidColorScheme.current
+ Card(
+ modifier = modifier.height(size.height.dp),
+ colors = CardDefaults.cardColors(containerColor = Color.Transparent),
+ border = BorderStroke(1.dp, colors.primary),
+ shape = RoundedCornerShape(200.dp),
+ onClick = onOpenWidgetPicker,
+ ) {
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ verticalArrangement =
+ Arrangement.spacedBy(Dimensions.Spacing, Alignment.CenterVertically),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.Widgets,
+ contentDescription = stringResource(R.string.cta_label_to_open_widget_picker),
+ tint = colors.primary,
+ modifier = Modifier.size(Dimensions.IconSize),
+ )
+ Text(
+ text = stringResource(R.string.cta_label_to_open_widget_picker),
+ style = MaterialTheme.typography.titleLarge,
+ color = colors.primary,
+ textAlign = TextAlign.Center,
+ )
+ }
+ }
+}
+
+@Composable
+private fun WidgetContent(
+ viewModel: BaseCommunalViewModel,
+ model: CommunalContentModel.Widget,
+ size: SizeF,
+ modifier: Modifier = Modifier,
+) {
+ Box(
+ modifier = modifier.height(size.height.dp),
+ contentAlignment = Alignment.Center,
) {
AndroidView(
modifier = modifier,
factory = { context ->
- model.appWidgetHost
- .createView(context, model.appWidgetId, model.providerInfo)
- .apply { updateAppWidgetSize(Bundle.EMPTY, listOf(size)) }
+ // The AppWidgetHostView will inherit the interaction handler from the
+ // AppWidgetHost. So set the interaction handler here before creating the view, and
+ // then clear it after the view is created. This is a workaround due to the fact
+ // that the interaction handler cannot be specified when creating the view,
+ // and there are race conditions if it is set after the view is created.
+ model.appWidgetHost.setInteractionHandler(viewModel.getInteractionHandler())
+ val view =
+ model.appWidgetHost
+ .createView(context, model.appWidgetId, model.providerInfo)
+ .apply { updateAppWidgetSize(Bundle.EMPTY, listOf(size)) }
+ model.appWidgetHost.setInteractionHandler(null)
+ view
},
// For reusing composition in lazy lists.
onReset = {},
@@ -488,4 +629,10 @@
val ToolbarButtonPaddingHorizontal = 24.dp
val ToolbarButtonPaddingVertical = 16.dp
val ToolbarButtonSpaceBetween = 8.dp
+ val ButtonPadding =
+ PaddingValues(
+ vertical = ToolbarButtonPaddingVertical,
+ horizontal = ToolbarButtonPaddingHorizontal,
+ )
+ val IconSize = 48.dp
}
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 0d460aa8..1b40de4 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,6 +16,7 @@
package com.android.systemui.communal.ui.compose
+import android.util.SizeF
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
import androidx.compose.foundation.gestures.scrollBy
@@ -233,6 +234,7 @@
dragDropState: GridDragDropState,
index: Int,
enabled: Boolean,
+ size: SizeF,
modifier: Modifier = Modifier,
content: @Composable (isDragging: Boolean) -> Unit
) {
@@ -250,7 +252,11 @@
} else {
Modifier.animateItemPlacement()
}
- Box(modifier = modifier.then(draggingModifier), propagateMinConstraints = true) {
- content(dragging)
+
+ Box(modifier) {
+ if (dragging) {
+ WidgetPlaceholderContent(size)
+ }
+ Box(modifier = draggingModifier, propagateMinConstraints = true) { content(dragging) }
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
index 65a53f5..9778e53 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
@@ -40,6 +40,7 @@
import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Expanding
import com.android.systemui.res.R
import com.android.systemui.scene.ui.composable.Gone
+import com.android.systemui.scene.ui.composable.Lockscreen
import com.android.systemui.scene.ui.composable.QuickSettings as QuickSettingsSceneKey
import com.android.systemui.scene.ui.composable.Shade
@@ -77,7 +78,12 @@
toScene == Shade -> QSSceneAdapter.State.QQS
toScene == QuickSettingsSceneKey -> QSSceneAdapter.State.QS
toScene == Gone -> QSSceneAdapter.State.CLOSED
- else -> error("Bad transition for QuickSettings: $transitionState")
+ toScene == Lockscreen -> QSSceneAdapter.State.CLOSED
+ else ->
+ error(
+ "Bad transition for QuickSettings: fromScene=$fromScene," +
+ " toScene=$toScene"
+ )
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index 99f81ee..e2beaee 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -91,12 +91,19 @@
statusBarIconController: StatusBarIconController,
modifier: Modifier = Modifier,
) {
- val formatProgress = animateSceneFloatAsState(0.0f, ShadeHeader.Keys.transitionProgress)
+ val formatProgress =
+ animateSceneFloatAsState(0f, ShadeHeader.Keys.transitionProgress)
+ .unsafeCompositionState(initialValue = 0f)
val cutoutWidth = LocalDisplayCutout.current.width()
val cutoutLocation = LocalDisplayCutout.current.location
- val useExpandedFormat = formatProgress.value > 0.5f || cutoutLocation != CutoutLocation.CENTER
+ val useExpandedFormat by
+ remember(formatProgress) {
+ derivedStateOf {
+ cutoutLocation != CutoutLocation.CENTER || formatProgress.value > 0.5f
+ }
+ }
// This layout assumes it is globally positioned at (0, 0) and is the
// same size as the screen.
@@ -209,7 +216,9 @@
statusBarIconController: StatusBarIconController,
modifier: Modifier = Modifier,
) {
- val formatProgress = animateSceneFloatAsState(1.0f, ShadeHeader.Keys.transitionProgress)
+ val formatProgress =
+ animateSceneFloatAsState(1f, ShadeHeader.Keys.transitionProgress)
+ .unsafeCompositionState(initialValue = 1f)
val useExpandedFormat by
remember(formatProgress) { derivedStateOf { formatProgress.value > 0.5f } }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 54c5de7..35a5054 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -487,7 +487,7 @@
// page should be composed.
HorizontalPager(
pagerState,
- beyondBoundsPageCount = 0,
+ outOfBoundsPageCount = 0,
) { page ->
when (page) {
0 -> Box(Modifier.element(TestElements.Foo).fillMaxSize())
diff --git a/packages/SystemUI/customization/Android.bp b/packages/SystemUI/customization/Android.bp
index 927fd8e..1d18496 100644
--- a/packages/SystemUI/customization/Android.bp
+++ b/packages/SystemUI/customization/Android.bp
@@ -30,8 +30,8 @@
"src/**/*.aidl",
],
static_libs: [
+ "PlatformAnimationLib",
"PluginCoreLib",
- "SystemUIAnimationLib",
"SystemUIPluginLib",
"SystemUIUnfoldLib",
"androidx.dynamicanimation_dynamicanimation",
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 1fc2843..c8461d2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -45,10 +45,10 @@
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
import com.android.systemui.classifier.FalsingA11yDelegate
import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.log.SessionTracker
@@ -140,7 +140,7 @@
@Mock private lateinit var viewMediatorCallback: ViewMediatorCallback
@Mock private lateinit var audioManager: AudioManager
@Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
- @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor
+ @Mock private lateinit var faceAuthInteractor: DeviceEntryFaceAuthInteractor
@Mock private lateinit var faceAuthAccessibilityDelegate: FaceAuthAccessibilityDelegate
@Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
@Mock private lateinit var postureController: DevicePostureController
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index e5da1f8..cec2d74 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -85,10 +85,10 @@
import com.android.systemui.biometrics.ui.viewmodel.DeviceEntryUdfpsTouchOverlayViewModel;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.ScreenLifecycle;
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.FalsingManager;
@@ -336,7 +336,7 @@
mSessionTracker,
mAlternateBouncerInteractor,
mInputManager,
- mock(KeyguardFaceAuthInteractor.class),
+ mock(DeviceEntryFaceAuthInteractor.class),
mUdfpsKeyguardAccessibilityDelegate,
mSelectedUserInteractor,
mFpsUnlockTracker,
@@ -496,7 +496,8 @@
final float[] scaleFactor = new float[]{1f, displayHeight[1] / (float) displayHeight[0]};
final int[] rotation = new int[]{Surface.ROTATION_0, Surface.ROTATION_90};
final UdfpsOverlayParams oldParams = new UdfpsOverlayParams(sensorBounds[0],
- sensorBounds[0], displayWidth[0], displayHeight[0], scaleFactor[0], rotation[0]);
+ sensorBounds[0], displayWidth[0], displayHeight[0], scaleFactor[0], rotation[0],
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL);
for (int i1 = 0; i1 <= 1; ++i1) {
for (int i2 = 0; i2 <= 1; ++i2) {
@@ -505,7 +506,8 @@
for (int i5 = 0; i5 <= 1; ++i5) {
final UdfpsOverlayParams newParams = new UdfpsOverlayParams(
sensorBounds[i1], sensorBounds[i1], displayWidth[i2],
- displayHeight[i3], scaleFactor[i4], rotation[i5]);
+ displayHeight[i3], scaleFactor[i4], rotation[i5],
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL);
if (newParams.equals(oldParams)) {
continue;
@@ -549,7 +551,7 @@
// Initialize the overlay.
mUdfpsController.updateOverlayParams(mOpticalProps,
new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
- scaleFactor, rotation));
+ scaleFactor, rotation, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL));
// Show the overlay.
mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
@@ -560,7 +562,7 @@
// Update overlay with the same parameters.
mUdfpsController.updateOverlayParams(mOpticalProps,
new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
- scaleFactor, rotation));
+ scaleFactor, rotation, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL));
mFgExecutor.runAllReady();
// Ensure the overlay was not recreated.
@@ -642,7 +644,8 @@
// Test ROTATION_0
mUdfpsController.updateOverlayParams(testParams.sensorProps,
new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
- scaleFactor, Surface.ROTATION_0));
+ scaleFactor, Surface.ROTATION_0,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL));
MotionEvent event = obtainMotionEvent(ACTION_DOWN, displayWidth, displayHeight, touchMinor,
touchMajor);
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
@@ -657,7 +660,8 @@
reset(mFingerprintManager);
mUdfpsController.updateOverlayParams(testParams.sensorProps,
new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
- scaleFactor, Surface.ROTATION_90));
+ scaleFactor, Surface.ROTATION_90,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL));
event = obtainMotionEvent(ACTION_DOWN, displayHeight, 0, touchMinor, touchMajor);
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
mBiometricExecutor.runAllReady();
@@ -671,7 +675,8 @@
reset(mFingerprintManager);
mUdfpsController.updateOverlayParams(testParams.sensorProps,
new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
- scaleFactor, Surface.ROTATION_270));
+ scaleFactor, Surface.ROTATION_270,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL));
event = obtainMotionEvent(ACTION_DOWN, 0, displayWidth, touchMinor, touchMajor);
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
mBiometricExecutor.runAllReady();
@@ -685,7 +690,8 @@
reset(mFingerprintManager);
mUdfpsController.updateOverlayParams(testParams.sensorProps,
new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
- scaleFactor, Surface.ROTATION_180));
+ scaleFactor, Surface.ROTATION_180,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL));
// ROTATION_180 is not supported. It should be treated like ROTATION_0.
event = obtainMotionEvent(ACTION_DOWN, displayWidth, displayHeight, touchMinor, touchMajor);
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
index 1f8854f..335ac9d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
@@ -32,11 +32,11 @@
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
import com.android.systemui.bouncer.ui.BouncerView
import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
@@ -111,7 +111,7 @@
FakeTrustRepository(),
testScope.backgroundScope,
mSelectedUserInteractor,
- mock(KeyguardFaceAuthInteractor::class.java),
+ mock(DeviceEntryFaceAuthInteractor::class.java),
)
mAlternateBouncerInteractor =
AlternateBouncerInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
index 8adee8d..8d6d052 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
@@ -24,6 +24,7 @@
import android.view.WindowInsets
import android.view.WindowManager
import android.view.WindowMetrics
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider
@@ -61,7 +62,6 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.spy
@@ -69,7 +69,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class SideFpsSensorInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
@@ -104,6 +104,7 @@
contextDisplayInfo.uniqueId = "current-display"
whenever(fingerprintInteractiveToAuthProvider.enabledForCurrentUser)
.thenReturn(isRestToUnlockEnabled)
+ overrideResource(R.bool.config_restToUnlockSupported, true)
underTest =
SideFpsSensorInteractor(
mContext,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 99c1874..93ba6a4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -25,7 +25,7 @@
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.res.R
import com.android.systemui.scene.SceneTestUtils
import com.google.common.truth.Truth.assertThat
@@ -46,7 +46,7 @@
@RunWith(AndroidJUnit4::class)
class BouncerInteractorTest : SysuiTestCase() {
- @Mock private lateinit var keyguardFaceAuthInteractor: KeyguardFaceAuthInteractor
+ @Mock private lateinit var mDeviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor
private val utils = SceneTestUtils(this)
private val testScope = utils.testScope
@@ -67,7 +67,7 @@
underTest =
utils.bouncerInteractor(
authenticationInteractor = authenticationInteractor,
- keyguardFaceAuthInteractor = keyguardFaceAuthInteractor,
+ deviceEntryFaceAuthInteractor = mDeviceEntryFaceAuthInteractor,
)
}
@@ -306,7 +306,7 @@
fun intentionalUserInputEvent_notifiesFaceAuthInteractor() =
testScope.runTest {
underTest.onIntentionalUserInput()
- verify(keyguardFaceAuthInteractor).onPrimaryBouncerUserInput()
+ verify(mDeviceEntryFaceAuthInteractor).onPrimaryBouncerUserInput()
}
companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
index bdf5041..c8560c3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
@@ -26,9 +26,9 @@
import com.android.systemui.bouncer.ui.BouncerView
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.utils.os.FakeHandler
@@ -54,7 +54,7 @@
@Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
- @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor
+ @Mock private lateinit var faceAuthInteractor: DeviceEntryFaceAuthInteractor
private val mainHandler = FakeHandler(Looper.getMainLooper())
private lateinit var underTest: PrimaryBouncerInteractor
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
index a3bf3f4..a0c2acc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
@@ -30,9 +30,9 @@
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
@@ -63,7 +63,7 @@
@Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
@Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
- @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor
+ @Mock private lateinit var faceAuthInteractor: DeviceEntryFaceAuthInteractor
lateinit var bouncerInteractor: PrimaryBouncerInteractor
private val mainHandler = FakeHandler(Looper.getMainLooper())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageChangeRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageChangeRepositoryTest.kt
new file mode 100644
index 0000000..6380ace
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageChangeRepositoryTest.kt
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.common.data.repository
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Handler
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.data.shared.model.PackageChangeModel
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PackageChangeRepositoryTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ @Mock private lateinit var context: Context
+ @Mock private lateinit var packageManager: PackageManager
+ @Mock private lateinit var handler: Handler
+
+ private lateinit var repository: PackageChangeRepository
+ private lateinit var updateMonitor: PackageUpdateMonitor
+
+ @Before
+ fun setUp() =
+ with(kosmos) {
+ MockitoAnnotations.initMocks(this@PackageChangeRepositoryTest)
+ whenever(context.packageManager).thenReturn(packageManager)
+
+ repository = PackageChangeRepositoryImpl { user ->
+ updateMonitor =
+ PackageUpdateMonitor(
+ user = user,
+ bgDispatcher = testDispatcher,
+ scope = applicationCoroutineScope,
+ context = context,
+ bgHandler = handler,
+ logger = PackageUpdateLogger(logcatLogBuffer())
+ )
+ updateMonitor
+ }
+ }
+
+ @Test
+ fun packageUninstalled() =
+ with(kosmos) {
+ testScope.runTest {
+ val packageChange by collectLastValue(repository.packageChanged(USER_100))
+ assertThat(packageChange).isNull()
+
+ updateMonitor.onPackageRemoved(
+ packageName = TEST_PACKAGE,
+ uid = UserHandle.getUid(/* userId = */ 100, /* appId = */ 10)
+ )
+
+ assertThat(packageChange).isInstanceOf(PackageChangeModel.Uninstalled::class.java)
+ assertThat(packageChange?.packageName).isEqualTo(TEST_PACKAGE)
+ }
+ }
+
+ @Test
+ fun packageUpdateStarted() =
+ with(kosmos) {
+ testScope.runTest {
+ val packageChange by collectLastValue(repository.packageChanged(USER_100))
+ assertThat(packageChange).isNull()
+
+ updateMonitor.onPackageUpdateStarted(
+ packageName = TEST_PACKAGE,
+ uid = UserHandle.getUid(/* userId = */ 100, /* appId = */ 10)
+ )
+
+ assertThat(packageChange).isInstanceOf(PackageChangeModel.UpdateStarted::class.java)
+ assertThat(packageChange?.packageName).isEqualTo(TEST_PACKAGE)
+ }
+ }
+
+ @Test
+ fun packageUpdateFinished() =
+ with(kosmos) {
+ testScope.runTest {
+ val packageChange by collectLastValue(repository.packageChanged(USER_100))
+ assertThat(packageChange).isNull()
+
+ updateMonitor.onPackageUpdateFinished(
+ packageName = TEST_PACKAGE,
+ uid = UserHandle.getUid(/* userId = */ 100, /* appId = */ 10)
+ )
+
+ assertThat(packageChange)
+ .isInstanceOf(PackageChangeModel.UpdateFinished::class.java)
+ assertThat(packageChange?.packageName).isEqualTo(TEST_PACKAGE)
+ }
+ }
+
+ @Test
+ fun packageInstalled() =
+ with(kosmos) {
+ testScope.runTest {
+ val packageChange by collectLastValue(repository.packageChanged(UserHandle.ALL))
+ assertThat(packageChange).isNull()
+
+ updateMonitor.onPackageAdded(
+ packageName = TEST_PACKAGE,
+ uid = UserHandle.getUid(/* userId = */ 100, /* appId = */ 10)
+ )
+
+ assertThat(packageChange).isInstanceOf(PackageChangeModel.Installed::class.java)
+ assertThat(packageChange?.packageName).isEqualTo(TEST_PACKAGE)
+ }
+ }
+
+ @Test
+ fun packageIsChanged() =
+ with(kosmos) {
+ testScope.runTest {
+ val packageChange by collectLastValue(repository.packageChanged(USER_100))
+ assertThat(packageChange).isNull()
+
+ updateMonitor.onPackageChanged(
+ packageName = TEST_PACKAGE,
+ uid = UserHandle.getUid(/* userId = */ 100, /* appId = */ 10),
+ components = emptyArray()
+ )
+
+ assertThat(packageChange).isInstanceOf(PackageChangeModel.Changed::class.java)
+ assertThat(packageChange?.packageName).isEqualTo(TEST_PACKAGE)
+ }
+ }
+
+ @Test
+ fun filtersOutUpdatesFromOtherUsers() =
+ with(kosmos) {
+ testScope.runTest {
+ val packageChange by collectLastValue(repository.packageChanged(USER_100))
+ assertThat(packageChange).isNull()
+
+ updateMonitor.onPackageUpdateFinished(
+ packageName = TEST_PACKAGE,
+ uid = UserHandle.getUid(/* userId = */ 101, /* appId = */ 10)
+ )
+
+ updateMonitor.onPackageAdded(
+ packageName = TEST_PACKAGE,
+ uid = UserHandle.getUid(/* userId = */ 99, /* appId = */ 10)
+ )
+
+ assertThat(packageChange).isNull()
+ }
+ }
+
+ @Test
+ fun listenToUpdatesFromAllUsers() =
+ with(kosmos) {
+ testScope.runTest {
+ val packageChanges by collectValues(repository.packageChanged(UserHandle.ALL))
+ assertThat(packageChanges).isEmpty()
+
+ updateMonitor.onPackageUpdateFinished(
+ packageName = TEST_PACKAGE,
+ uid = UserHandle.getUid(/* userId = */ 101, /* appId = */ 10)
+ )
+
+ updateMonitor.onPackageAdded(
+ packageName = TEST_PACKAGE,
+ uid = UserHandle.getUid(/* userId = */ 99, /* appId = */ 10)
+ )
+
+ assertThat(packageChanges).hasSize(2)
+ }
+ }
+
+ private companion object {
+ val USER_100 = UserHandle.of(100)
+ const val TEST_PACKAGE = "pkg.test"
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageUpdateMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageUpdateMonitorTest.kt
new file mode 100644
index 0000000..d610925
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageUpdateMonitorTest.kt
@@ -0,0 +1,210 @@
+/*
+ * 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.common.data.repository
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Handler
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.data.shared.model.PackageChangeModel
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PackageUpdateMonitorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
+ @Mock private lateinit var context: Context
+ @Mock private lateinit var packageManager: PackageManager
+ @Mock private lateinit var handler: Handler
+
+ private lateinit var monitor: PackageUpdateMonitor
+
+ @Before
+ fun setUp() =
+ with(kosmos) {
+ MockitoAnnotations.initMocks(this@PackageUpdateMonitorTest)
+ whenever(context.packageManager).thenReturn(packageManager)
+
+ monitor =
+ PackageUpdateMonitor(
+ user = USER_100,
+ bgDispatcher = testDispatcher,
+ bgHandler = handler,
+ context = context,
+ scope = applicationCoroutineScope,
+ logger = PackageUpdateLogger(logcatLogBuffer())
+ )
+ }
+
+ @Test
+ fun becomesActiveWhenFlowCollected() =
+ with(kosmos) {
+ testScope.runTest {
+ assertThat(monitor.isActive).isFalse()
+ val job = monitor.packageChanged.launchIn(this)
+ runCurrent()
+ assertThat(monitor.isActive).isTrue()
+ job.cancel()
+ runCurrent()
+ assertThat(monitor.isActive).isFalse()
+ }
+ }
+
+ @Test
+ fun packageAdded() =
+ with(kosmos) {
+ testScope.runTest {
+ val packageChange by collectLastValue(monitor.packageChanged)
+ assertThat(packageChange).isNull()
+
+ monitor.onPackageAdded(TEST_PACKAGE, 123)
+
+ assertThat(packageChange)
+ .isEqualTo(
+ PackageChangeModel.Installed(packageName = TEST_PACKAGE, packageUid = 123)
+ )
+ }
+ }
+
+ @Test
+ fun packageRemoved() =
+ with(kosmos) {
+ testScope.runTest {
+ val packageChange by collectLastValue(monitor.packageChanged)
+ assertThat(packageChange).isNull()
+
+ monitor.onPackageRemoved(TEST_PACKAGE, 123)
+
+ assertThat(packageChange)
+ .isEqualTo(
+ PackageChangeModel.Uninstalled(packageName = TEST_PACKAGE, packageUid = 123)
+ )
+ }
+ }
+
+ @Test
+ fun packageChanged() =
+ with(kosmos) {
+ testScope.runTest {
+ val packageChange by collectLastValue(monitor.packageChanged)
+ assertThat(packageChange).isNull()
+
+ monitor.onPackageChanged(TEST_PACKAGE, 123, emptyArray())
+
+ assertThat(packageChange)
+ .isEqualTo(
+ PackageChangeModel.Changed(packageName = TEST_PACKAGE, packageUid = 123)
+ )
+ }
+ }
+
+ @Test
+ fun packageUpdateStarted() =
+ with(kosmos) {
+ testScope.runTest {
+ val packageChange by collectLastValue(monitor.packageChanged)
+ assertThat(packageChange).isNull()
+
+ monitor.onPackageUpdateStarted(TEST_PACKAGE, 123)
+
+ assertThat(packageChange)
+ .isEqualTo(
+ PackageChangeModel.UpdateStarted(
+ packageName = TEST_PACKAGE,
+ packageUid = 123
+ )
+ )
+ }
+ }
+
+ @Test
+ fun packageUpdateFinished() =
+ with(kosmos) {
+ testScope.runTest {
+ val packageChange by collectLastValue(monitor.packageChanged)
+ assertThat(packageChange).isNull()
+
+ monitor.onPackageUpdateFinished(TEST_PACKAGE, 123)
+
+ assertThat(packageChange)
+ .isEqualTo(
+ PackageChangeModel.UpdateFinished(
+ packageName = TEST_PACKAGE,
+ packageUid = 123
+ )
+ )
+ }
+ }
+
+ @Test
+ fun handlesBackflow() =
+ with(kosmos) {
+ testScope.runTest {
+ val latch = MutableSharedFlow<Unit>()
+ val packageChanges by collectValues(monitor.packageChanged.onEach { latch.first() })
+ assertThat(packageChanges).isEmpty()
+
+ monitor.onPackageAdded(TEST_PACKAGE, 123)
+ monitor.onPackageUpdateStarted(TEST_PACKAGE, 123)
+ monitor.onPackageUpdateFinished(TEST_PACKAGE, 123)
+
+ assertThat(packageChanges).isEmpty()
+ latch.emit(Unit)
+ assertThat(packageChanges).hasSize(1)
+ latch.emit(Unit)
+ assertThat(packageChanges).hasSize(2)
+ latch.emit(Unit)
+ assertThat(packageChanges)
+ .containsExactly(
+ PackageChangeModel.Installed(TEST_PACKAGE, 123),
+ PackageChangeModel.UpdateStarted(TEST_PACKAGE, 123),
+ PackageChangeModel.UpdateFinished(TEST_PACKAGE, 123),
+ )
+ .inOrder()
+ }
+ }
+
+ companion object {
+ private val USER_100 = UserHandle.of(100)
+ private const val TEST_PACKAGE = "pkg.test"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
similarity index 69%
rename from packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
index 455f986..92b75cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -25,13 +25,13 @@
import com.android.systemui.util.mockito.KotlinArgumentCaptor
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlin.test.Test
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
+import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
@@ -59,37 +59,43 @@
}
@Test
- fun mediaPlaying_defaultsToFalse() =
+ fun hasAnyMediaOrRecommendation_defaultsToFalse() =
testScope.runTest {
mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
- val isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
+ val mediaModel = collectLastValue(mediaRepository.mediaModel)
runCurrent()
- assertThat(isMediaPlaying()).isFalse()
+ assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
}
@Test
- fun mediaPlaying_emitsInitialValue() =
- testScope.runTest {
- // Start with media available.
- whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
-
- mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
-
- val isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
- runCurrent()
- assertThat(isMediaPlaying()).isTrue()
- }
-
- @Test
- fun mediaPlaying_updatesWhenMediaDataLoaded() =
+ fun mediaModel_updatesWhenMediaDataLoaded() =
testScope.runTest {
mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
+ // Listener is added
+ verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
+
// Initial value is false.
- var isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
+ val mediaModel = collectLastValue(mediaRepository.mediaModel)
runCurrent()
- assertThat(isMediaPlaying()).isFalse()
+ assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
+
+ // Change to media available and notify the listener.
+ whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
+ whenever(mediaData.createdTimestampMillis).thenReturn(1234L)
+ mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
+ runCurrent()
+
+ // Media active now returns true.
+ assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isTrue()
+ assertThat(mediaModel()?.createdTimestampMillis).isEqualTo(1234L)
+ }
+
+ @Test
+ fun mediaModel_updatesWhenMediaDataRemoved() =
+ testScope.runTest {
+ mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
// Listener is added
verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
@@ -97,36 +103,18 @@
// Change to media available and notify the listener.
whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
-
- // mediaPlaying now returns true.
- isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
runCurrent()
- assertThat(isMediaPlaying()).isTrue()
- }
- @Test
- fun mediaPlaying_updatesWhenMediaDataRemoved() =
- testScope.runTest {
- // Start with media available.
- whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
-
- mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
-
- // Initial value is true.
- var isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
- runCurrent()
- assertThat(isMediaPlaying()).isTrue()
-
- // Listener is added.
- verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
+ // Media active now returns true.
+ val mediaModel = collectLastValue(mediaRepository.mediaModel)
+ assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isTrue()
// Change to media unavailable and notify the listener.
whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(false)
- mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
-
- // mediaPlaying now returns false.
- isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
+ mediaDataListenerCaptor.value.onMediaDataRemoved("key")
runCurrent()
- assertThat(isMediaPlaying()).isFalse()
+
+ // Media active now returns false.
+ assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index ddaa488..449ee6f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -42,6 +42,7 @@
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flowOf
@@ -63,6 +64,8 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
+ @Mock private lateinit var appWidgetManagerOptional: Optional<AppWidgetManager>
+
@Mock private lateinit var appWidgetManager: AppWidgetManager
@Mock private lateinit var appWidgetHost: AppWidgetHost
@@ -113,6 +116,8 @@
whenever(stopwatchProviderInfo.loadLabel(any())).thenReturn("Stopwatch")
whenever(userTracker.userHandle).thenReturn(userHandle)
whenever(communalWidgetDao.getWidgets()).thenReturn(flowOf(emptyMap()))
+ whenever(appWidgetManagerOptional.isPresent).thenReturn(true)
+ whenever(appWidgetManagerOptional.get()).thenReturn(appWidgetManager)
}
@Test
@@ -296,7 +301,7 @@
private fun initCommunalWidgetRepository(): CommunalWidgetRepositoryImpl {
return CommunalWidgetRepositoryImpl(
- appWidgetManager,
+ appWidgetManagerOptional,
appWidgetHost,
testScope.backgroundScope,
testDispatcher,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 62084aa..744b65f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -148,25 +148,29 @@
whenever(target1.smartspaceTargetId).thenReturn("target1")
whenever(target1.featureType).thenReturn(SmartspaceTarget.FEATURE_WEATHER)
whenever(target1.remoteViews).thenReturn(mock(RemoteViews::class.java))
+ whenever(target1.creationTimeMillis).thenReturn(0L)
// Does not have RemoteViews
val target2 = mock(SmartspaceTarget::class.java)
- whenever(target1.smartspaceTargetId).thenReturn("target2")
- whenever(target1.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
- whenever(target1.remoteViews).thenReturn(null)
+ whenever(target2.smartspaceTargetId).thenReturn("target2")
+ whenever(target2.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
+ whenever(target2.remoteViews).thenReturn(null)
+ whenever(target2.creationTimeMillis).thenReturn(0L)
// Timer and has RemoteViews
val target3 = mock(SmartspaceTarget::class.java)
- whenever(target1.smartspaceTargetId).thenReturn("target3")
- whenever(target1.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
- whenever(target1.remoteViews).thenReturn(mock(RemoteViews::class.java))
+ whenever(target3.smartspaceTargetId).thenReturn("target3")
+ whenever(target3.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
+ whenever(target3.remoteViews).thenReturn(mock(RemoteViews::class.java))
+ whenever(target3.creationTimeMillis).thenReturn(0L)
val targets = listOf(target1, target2, target3)
smartspaceRepository.setCommunalSmartspaceTargets(targets)
- val smartspaceContent by collectLastValue(underTest.smartspaceContent)
+ val smartspaceContent by collectLastValue(underTest.ongoingContent)
assertThat(smartspaceContent?.size).isEqualTo(1)
- assertThat(smartspaceContent?.get(0)?.key).isEqualTo("smartspace_target3")
+ assertThat(smartspaceContent?.get(0)?.key)
+ .isEqualTo(CommunalContentModel.KEY.smartspace("target3"))
}
@Test
@@ -256,16 +260,12 @@
val targets = mutableListOf<SmartspaceTarget>()
for (index in 0 until totalTargets) {
- val target = mock(SmartspaceTarget::class.java)
- whenever(target.smartspaceTargetId).thenReturn("target$index")
- whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
- whenever(target.remoteViews).thenReturn(mock(RemoteViews::class.java))
- targets.add(target)
+ targets.add(smartspaceTimer(index.toString()))
}
smartspaceRepository.setCommunalSmartspaceTargets(targets)
- val smartspaceContent by collectLastValue(underTest.smartspaceContent)
+ val smartspaceContent by collectLastValue(underTest.ongoingContent)
assertThat(smartspaceContent?.size).isEqualTo(totalTargets)
for (index in 0 until totalTargets) {
assertThat(smartspaceContent?.get(index)?.size).isEqualTo(expectedSizes[index])
@@ -279,13 +279,77 @@
tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
// Media is playing.
- mediaRepository.mediaPlaying.value = true
+ mediaRepository.mediaActive()
- val umoContent by collectLastValue(underTest.umoContent)
+ val umoContent by collectLastValue(underTest.ongoingContent)
assertThat(umoContent?.size).isEqualTo(1)
assertThat(umoContent?.get(0)).isInstanceOf(CommunalContentModel.Umo::class.java)
- assertThat(umoContent?.get(0)?.key).isEqualTo(CommunalContentModel.UMO_KEY)
+ assertThat(umoContent?.get(0)?.key).isEqualTo(CommunalContentModel.KEY.umo())
+ }
+
+ @Test
+ fun ongoing_shouldOrderAndSizeByTimestamp() =
+ testScope.runTest {
+ // Keyguard showing, and tutorial completed.
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setKeyguardOccluded(false)
+ tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+
+ // Timer1 started
+ val timer1 = smartspaceTimer("timer1", timestamp = 1L)
+ smartspaceRepository.setCommunalSmartspaceTargets(listOf(timer1))
+
+ // Umo started
+ mediaRepository.mediaActive(timestamp = 2L)
+
+ // Timer2 started
+ val timer2 = smartspaceTimer("timer2", timestamp = 3L)
+ smartspaceRepository.setCommunalSmartspaceTargets(listOf(timer1, timer2))
+
+ // Timer3 started
+ val timer3 = smartspaceTimer("timer3", timestamp = 4L)
+ smartspaceRepository.setCommunalSmartspaceTargets(listOf(timer1, timer2, timer3))
+
+ val ongoingContent by collectLastValue(underTest.ongoingContent)
+ assertThat(ongoingContent?.size).isEqualTo(4)
+ assertThat(ongoingContent?.get(0)?.key)
+ .isEqualTo(CommunalContentModel.KEY.smartspace("timer3"))
+ assertThat(ongoingContent?.get(0)?.size).isEqualTo(CommunalContentSize.FULL)
+ assertThat(ongoingContent?.get(1)?.key)
+ .isEqualTo(CommunalContentModel.KEY.smartspace("timer2"))
+ assertThat(ongoingContent?.get(1)?.size).isEqualTo(CommunalContentSize.THIRD)
+ assertThat(ongoingContent?.get(2)?.key).isEqualTo(CommunalContentModel.KEY.umo())
+ assertThat(ongoingContent?.get(2)?.size).isEqualTo(CommunalContentSize.THIRD)
+ assertThat(ongoingContent?.get(3)?.key)
+ .isEqualTo(CommunalContentModel.KEY.smartspace("timer1"))
+ assertThat(ongoingContent?.get(3)?.size).isEqualTo(CommunalContentSize.THIRD)
+ }
+
+ @Test
+ fun cta_visibilityTrue_shows() =
+ testScope.runTest {
+ tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+ communalRepository.setCtaTileInViewModeVisibility(true)
+
+ val ctaTileContent by collectLastValue(underTest.ctaTileContent)
+
+ assertThat(ctaTileContent?.size).isEqualTo(1)
+ assertThat(ctaTileContent?.get(0))
+ .isInstanceOf(CommunalContentModel.CtaTileInViewMode::class.java)
+ assertThat(ctaTileContent?.get(0)?.key)
+ .isEqualTo(CommunalContentModel.KEY.CTA_TILE_IN_VIEW_MODE_KEY)
+ }
+
+ @Test
+ fun ctaTile_visibilityFalse_doesNotShow() =
+ testScope.runTest {
+ tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+ communalRepository.setCtaTileInViewModeVisibility(false)
+
+ val ctaTileContent by collectLastValue(underTest.ctaTileContent)
+
+ assertThat(ctaTileContent).isEmpty()
}
@Test
@@ -334,4 +398,13 @@
underTest.showWidgetEditor()
verify(editWidgetsActivityStarter).startActivity()
}
+
+ private fun smartspaceTimer(id: String, timestamp: Long = 0L): SmartspaceTarget {
+ val timer = mock(SmartspaceTarget::class.java)
+ whenever(timer.smartspaceTargetId).thenReturn(id)
+ whenever(timer.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
+ whenever(timer.remoteViews).thenReturn(mock(RemoteViews::class.java))
+ whenever(timer.creationTimeMillis).thenReturn(timestamp)
+ return timer
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 314dfdf..4a935d0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -33,14 +33,15 @@
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.shade.ShadeViewController
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import javax.inject.Provider
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -56,7 +57,8 @@
@Mock private lateinit var shadeViewController: ShadeViewController
@Mock private lateinit var powerManager: PowerManager
- private lateinit var testScope: TestScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private lateinit var keyguardRepository: FakeKeyguardRepository
private lateinit var communalRepository: FakeCommunalRepository
@@ -71,8 +73,6 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- testScope = TestScope()
-
val withDeps = CommunalInteractorFactory.create()
keyguardRepository = withDeps.keyguardRepository
communalRepository = withDeps.communalRepository
@@ -91,7 +91,7 @@
}
@Test
- fun communalContent_onlyWidgetsAreShownInEditMode() =
+ fun communalContent_onlyWidgetsAndCtaTileAreShownInEditMode() =
testScope.runTest {
tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
@@ -119,15 +119,30 @@
smartspaceRepository.setCommunalSmartspaceTargets(listOf(target))
// Media playing.
- mediaRepository.mediaPlaying.value = true
+ mediaRepository.mediaActive()
val communalContent by collectLastValue(underTest.communalContent)
- // Only Widgets are shown.
- assertThat(communalContent?.size).isEqualTo(2)
+ // Only Widgets and CTA tile are shown.
+ assertThat(communalContent?.size).isEqualTo(3)
assertThat(communalContent?.get(0))
.isInstanceOf(CommunalContentModel.Widget::class.java)
assertThat(communalContent?.get(1))
.isInstanceOf(CommunalContentModel.Widget::class.java)
+ assertThat(communalContent?.get(2))
+ .isInstanceOf(CommunalContentModel.CtaTileInEditMode::class.java)
}
+
+ @Test
+ fun interactionHandlerIgnoresClicks() {
+ val interactionHandler = underTest.getInteractionHandler()
+ assertThat(
+ interactionHandler.onInteraction(
+ /* view = */ mock(),
+ /* pendingIntent = */ mock(),
+ /* response = */ mock()
+ )
+ )
+ .isEqualTo(false)
+ }
}
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 8a71168..16e0bc0 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
@@ -31,6 +31,7 @@
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.communal.widgets.WidgetInteractionHandler
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.media.controls.ui.MediaHost
@@ -84,6 +85,7 @@
underTest =
CommunalViewModel(
withDeps.communalInteractor,
+ WidgetInteractionHandler(mock()),
withDeps.tutorialInteractor,
Provider { shadeViewController },
powerManager,
@@ -110,7 +112,7 @@
}
@Test
- fun ordering_smartspaceBeforeUmoBeforeWidgets() =
+ fun ordering_smartspaceBeforeUmoBeforeWidgetsBeforeCtaTile() =
testScope.runTest {
tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
@@ -138,12 +140,15 @@
smartspaceRepository.setCommunalSmartspaceTargets(listOf(target))
// Media playing.
- mediaRepository.mediaPlaying.value = true
+ mediaRepository.mediaActive()
+
+ // CTA Tile not dismissed.
+ communalRepository.setCtaTileInViewModeVisibility(true)
val communalContent by collectLastValue(underTest.communalContent)
- // Order is smart space, then UMO, then widget content.
- assertThat(communalContent?.size).isEqualTo(4)
+ // Order is smart space, then UMO, widget content and cta tile.
+ assertThat(communalContent?.size).isEqualTo(5)
assertThat(communalContent?.get(0))
.isInstanceOf(CommunalContentModel.Smartspace::class.java)
assertThat(communalContent?.get(1)).isInstanceOf(CommunalContentModel.Umo::class.java)
@@ -151,5 +156,7 @@
.isInstanceOf(CommunalContentModel.Widget::class.java)
assertThat(communalContent?.get(3))
.isInstanceOf(CommunalContentModel.Widget::class.java)
+ assertThat(communalContent?.get(4))
+ .isInstanceOf(CommunalContentModel.CtaTileInViewMode::class.java)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
similarity index 96%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 941d67f..6a14220 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.data.repository
+package com.android.systemui.deviceentry.data.repository
import android.app.StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP
import android.app.StatusBarManager.SESSION_KEYGUARD
@@ -37,10 +37,6 @@
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId.fakeInstanceId
import com.android.internal.logging.UiEventLogger
-import com.android.keyguard.FaceAuthUiEvent
-import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN
-import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED
-import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
@@ -53,21 +49,32 @@
import com.android.systemui.coroutines.FlowValue
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
+import com.android.systemui.deviceentry.shared.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN
+import com.android.systemui.deviceentry.shared.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED
+import com.android.systemui.deviceentry.shared.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER
+import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus
+import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.display.data.repository.display
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags.KEYGUARD_WM_STATE_REFACTOR
+import com.android.systemui.keyguard.data.repository.BiometricType
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeCommandQueue
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.FakeTrustRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
-import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
-import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.log.FaceAuthenticationLogger
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 6c4bb37..c4ebbdc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -24,6 +24,7 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
import com.android.systemui.common.shared.model.Position
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.doze.DozeMachine
@@ -71,6 +72,7 @@
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
private lateinit var systemClock: FakeSystemClock
+ private lateinit var facePropertyRepository: FakeFacePropertyRepository
private lateinit var underTest: KeyguardRepositoryImpl
@@ -78,6 +80,7 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
systemClock = FakeSystemClock()
+ facePropertyRepository = FakeFacePropertyRepository()
underTest =
KeyguardRepositoryImpl(
statusBarStateController,
@@ -89,6 +92,7 @@
mainDispatcher,
testScope.backgroundScope,
systemClock,
+ facePropertyRepository,
)
}
@@ -482,10 +486,7 @@
testScope.runTest {
val values = mutableListOf<Point?>()
val job = underTest.faceSensorLocation.onEach(values::add).launchIn(this)
-
- val captor = argumentCaptor<AuthController.Callback>()
runCurrent()
- verify(authController).addCallback(captor.capture())
// An initial, null value should be initially emitted so that flows combined with this
// one
@@ -500,8 +501,7 @@
Point(250, 250),
)
.onEach {
- whenever(authController.faceSensorLocation).thenReturn(it)
- captor.value.onFaceSensorLocationChanged()
+ facePropertyRepository.setSensorLocation(it)
runCurrent()
}
.also { dispatchedSensorLocations ->
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
index 00405d0..c2ce392 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
@@ -29,27 +29,37 @@
import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
+import com.android.systemui.util.time.FakeSystemClock
import java.time.Instant
import java.time.LocalDateTime
import java.util.TimeZone
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
class AlarmTileMapperTest : SysuiTestCase() {
+ private val oneMinute = 60000L
private val kosmos = Kosmos()
private val alarmTileConfig = kosmos.qsAlarmTileConfig
+ private val fakeClock = FakeSystemClock()
// Using lazy (versus =) to make sure we override the right context -- see b/311612168
private val mapper by lazy {
AlarmTileMapper(
context.orCreateTestableResources
.apply { addOverride(R.drawable.ic_alarm, TestStubDrawable()) }
.resources,
- context.theme
+ context.theme,
+ fakeClock
)
}
+ @Before
+ fun setup() {
+ fakeClock.setCurrentTimeMillis(0) // same time both in test & map()
+ }
+
@Test
fun notAlarmSet() {
val inputModel = AlarmTileModel.NoAlarmSet
@@ -66,7 +76,7 @@
@Test
fun nextAlarmSet24HourFormat() {
- val triggerTime = 1L
+ val triggerTime = fakeClock.currentTimeMillis() + oneMinute
val inputModel =
AlarmTileModel.NextAlarmSet(true, AlarmManager.AlarmClockInfo(triggerTime, null))
@@ -85,7 +95,7 @@
@Test
fun nextAlarmSet12HourFormat() {
- val triggerTime = 1L
+ val triggerTime = fakeClock.currentTimeMillis() + oneMinute
val inputModel =
AlarmTileModel.NextAlarmSet(false, AlarmManager.AlarmClockInfo(triggerTime, null))
@@ -102,6 +112,66 @@
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
+ @Test
+ fun nextAlarmSetMoreThanAWeekLater_mapsSecondaryLabelToDisplayDateOnly() {
+ val oneWeekAndOneMinute = 7 * 24 * 60 * 60 * 1000L + oneMinute
+ val triggerTime = fakeClock.currentTimeMillis() + oneWeekAndOneMinute
+ val inputModel =
+ AlarmTileModel.NextAlarmSet(false, AlarmManager.AlarmClockInfo(triggerTime, null))
+
+ val outputState = mapper.map(alarmTileConfig, inputModel)
+
+ val localDateTime =
+ LocalDateTime.ofInstant(
+ Instant.ofEpochMilli(triggerTime),
+ TimeZone.getDefault().toZoneId()
+ )
+ val expectedSecondaryLabel = AlarmTileMapper.formatterDateOnly.format(localDateTime)
+ val expectedState =
+ createAlarmTileState(QSTileState.ActivationState.ACTIVE, expectedSecondaryLabel)
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun nextAlarmSetOneMinuteLessThanAWeekLater_mapsSecondaryLabelToDisplayTime() {
+ val oneWeekMinusOneMinute = 7 * 24 * 60 * 60 * 1000L - oneMinute
+ val triggerTime = fakeClock.currentTimeMillis() + oneWeekMinusOneMinute
+ val inputModel =
+ AlarmTileModel.NextAlarmSet(false, AlarmManager.AlarmClockInfo(triggerTime, null))
+
+ val outputState = mapper.map(alarmTileConfig, inputModel)
+
+ val localDateTime =
+ LocalDateTime.ofInstant(
+ Instant.ofEpochMilli(triggerTime),
+ TimeZone.getDefault().toZoneId()
+ )
+ val expectedSecondaryLabel = AlarmTileMapper.formatter12Hour.format(localDateTime)
+ val expectedState =
+ createAlarmTileState(QSTileState.ActivationState.ACTIVE, expectedSecondaryLabel)
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun nextAlarmSetExactlyAWeekLater_mapsSecondaryLabelToDisplayDateOnly() {
+ val oneWeek = 7 * 24 * 60 * 60 * 1000L
+ val triggerTime = fakeClock.currentTimeMillis() + oneWeek
+ val inputModel =
+ AlarmTileModel.NextAlarmSet(false, AlarmManager.AlarmClockInfo(triggerTime, null))
+
+ val outputState = mapper.map(alarmTileConfig, inputModel)
+
+ val localDateTime =
+ LocalDateTime.ofInstant(
+ Instant.ofEpochMilli(triggerTime),
+ TimeZone.getDefault().toZoneId()
+ )
+ val expectedSecondaryLabel = AlarmTileMapper.formatterDateOnly.format(localDateTime)
+ val expectedState =
+ createAlarmTileState(QSTileState.ActivationState.ACTIVE, expectedSecondaryLabel)
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
private fun createAlarmTileState(
activationState: QSTileState.ActivationState,
secondaryLabel: String
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
index 90779cb..20653ca 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
@@ -55,6 +55,7 @@
private val underTest: CustomTileInteractor =
with(kosmos) {
CustomTileInteractor(
+ tileSpec,
customTileDefaultsRepository,
customTileRepository,
testScope.backgroundScope,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index efd4f9b..530d127d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -265,7 +265,8 @@
powerInteractor = powerInteractor,
bouncerInteractor = bouncerInteractor,
simBouncerInteractor = dagger.Lazy { utils.simBouncerInteractor },
- authenticationInteractor = dagger.Lazy { utils.authenticationInteractor() }
+ authenticationInteractor = dagger.Lazy { utils.authenticationInteractor() },
+ windowController = mock(),
)
startable.start()
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 2e4986d..dd22976 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
@@ -37,6 +37,7 @@
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -46,18 +47,24 @@
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Mock
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
@EnableFlags(AconfigFlags.FLAG_SCENE_CONTAINER)
class SceneContainerStartableTest : SysuiTestCase() {
+ @Mock private lateinit var windowController: NotificationShadeWindowController
+
private val utils = SceneTestUtils(this)
private val testScope = utils.testScope
private val sceneInteractor = utils.sceneInteractor()
@@ -77,22 +84,30 @@
private val falsingCollector: FalsingCollector = mock()
private val powerInteractor = PowerInteractorFactory.create().powerInteractor
- private val underTest =
- SceneContainerStartable(
- applicationScope = testScope.backgroundScope,
- sceneInteractor = sceneInteractor,
- deviceEntryInteractor = deviceEntryInteractor,
- keyguardInteractor = keyguardInteractor,
- flags = sceneContainerFlags,
- sysUiState = sysUiState,
- displayId = Display.DEFAULT_DISPLAY,
- sceneLogger = mock(),
- falsingCollector = falsingCollector,
- powerInteractor = powerInteractor,
- bouncerInteractor = bouncerInteractor,
- simBouncerInteractor = dagger.Lazy { utils.simBouncerInteractor },
- authenticationInteractor = dagger.Lazy { authenticationInteractor },
- )
+ private lateinit var underTest: SceneContainerStartable
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest =
+ SceneContainerStartable(
+ applicationScope = testScope.backgroundScope,
+ sceneInteractor = sceneInteractor,
+ deviceEntryInteractor = deviceEntryInteractor,
+ keyguardInteractor = keyguardInteractor,
+ flags = sceneContainerFlags,
+ sysUiState = sysUiState,
+ displayId = Display.DEFAULT_DISPLAY,
+ sceneLogger = mock(),
+ falsingCollector = falsingCollector,
+ powerInteractor = powerInteractor,
+ bouncerInteractor = bouncerInteractor,
+ simBouncerInteractor = dagger.Lazy { utils.simBouncerInteractor },
+ authenticationInteractor = dagger.Lazy { authenticationInteractor },
+ windowController = windowController,
+ )
+ }
@Test
fun hydrateVisibility() =
@@ -655,6 +670,58 @@
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
}
+ @Test
+ fun hydrateWindowFocus() =
+ testScope.runTest {
+ val currentDesiredSceneKey by
+ collectLastValue(sceneInteractor.desiredScene.map { it.key })
+ val transitionStateFlow =
+ prepareState(
+ isDeviceUnlocked = true,
+ initialSceneKey = SceneKey.Gone,
+ )
+ assertThat(currentDesiredSceneKey).isEqualTo(SceneKey.Gone)
+ verify(windowController, never()).setNotificationShadeFocusable(anyBoolean())
+
+ underTest.start()
+ runCurrent()
+ verify(windowController, times(1)).setNotificationShadeFocusable(false)
+
+ sceneInteractor.changeScene(SceneModel(SceneKey.Shade), "reason")
+ transitionStateFlow.value =
+ ObservableTransitionState.Transition(
+ fromScene = SceneKey.Gone,
+ toScene = SceneKey.Shade,
+ progress = flowOf(0.5f),
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ runCurrent()
+ verify(windowController, times(1)).setNotificationShadeFocusable(false)
+
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Shade), "reason")
+ transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Shade)
+ runCurrent()
+ verify(windowController, times(1)).setNotificationShadeFocusable(true)
+
+ sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason")
+ transitionStateFlow.value =
+ ObservableTransitionState.Transition(
+ fromScene = SceneKey.Shade,
+ toScene = SceneKey.Gone,
+ progress = flowOf(0.5f),
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ runCurrent()
+ verify(windowController, times(1)).setNotificationShadeFocusable(true)
+
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone), "reason")
+ transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone)
+ runCurrent()
+ verify(windowController, times(2)).setNotificationShadeFocusable(false)
+ }
+
private fun TestScope.prepareState(
isDeviceUnlocked: Boolean = false,
isBypassEnabled: Boolean = false,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
index de767e3..7274c0c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
@@ -21,29 +21,30 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
-import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import android.content.BroadcastReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.TestableLooper.RunWithLooper;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.Dependency;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.model.SysUiState;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -61,17 +62,17 @@
public class SystemUIDialogTest extends SysuiTestCase {
@Mock
- private FeatureFlags mFeatureFlags;
- @Mock
private BroadcastDispatcher mBroadcastDispatcher;
@Mock
private SystemUIDialog.Delegate mDelegate;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mDependency.injectTestDependency(FeatureFlags.class, mFeatureFlags);
mDependency.injectTestDependency(BroadcastDispatcher.class, mBroadcastDispatcher);
}
@@ -110,16 +111,13 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_DIALOGS)
public void usePredictiveBackAnimFlag() {
- when(mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM))
- .thenReturn(true);
final SystemUIDialog dialog = new SystemUIDialog(mContext);
dialog.show();
assertTrue(dialog.isShowing());
- verify(mFeatureFlags, atLeast(1))
- .isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM);
dialog.dismiss();
assertFalse(dialog.isShowing());
@@ -174,7 +172,6 @@
private SystemUIDialog createDialogWithDelegate() {
SystemUIDialog.Factory factory = new SystemUIDialog.Factory(
getContext(),
- mFeatureFlags,
Dependency.get(SystemUIDialogManager.class),
Dependency.get(SysUiState.class),
Dependency.get(BroadcastDispatcher.class),
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorTest.kt
similarity index 70%
copy from packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
copy to packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorTest.kt
index efc7431..dbff63f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorTest.kt
@@ -14,12 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.impl.custom.di.bound
+package com.android.systemui.volume.panel.domain.interactor
-import javax.inject.Qualifier
+class ComponentsInteractorTest {
-/** User associated with current custom tile binding. */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class CustomTileUser
+ // TODO(b/318080198) Write tests
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
similarity index 70%
copy from packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
copy to packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
index efc7431..e5fb942 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
@@ -14,12 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.impl.custom.di.bound
+package com.android.systemui.volume.panel.ui.viewmodel
-import javax.inject.Qualifier
+class DefaultComponentsLayoutManagerTest {
-/** User associated with current custom tile binding. */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class CustomTileUser
+ // TODO(b/318080198) Write tests
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
similarity index 70%
copy from packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
copy to packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
index efc7431..9795237 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
@@ -14,12 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.impl.custom.di.bound
+package com.android.systemui.volume.panel.ui.viewmodel
-import javax.inject.Qualifier
+class VolumePanelViewModelTest {
-/** User associated with current custom tile binding. */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class CustomTileUser
+ // TODO(b/318080198) Write tests
+}
diff --git a/packages/SystemUI/plugin/Android.bp b/packages/SystemUI/plugin/Android.bp
index 0537f17..9063a02 100644
--- a/packages/SystemUI/plugin/Android.bp
+++ b/packages/SystemUI/plugin/Android.bp
@@ -46,8 +46,8 @@
static_libs: [
"androidx.annotation_annotation",
"androidx-constraintlayout_constraintlayout",
+ "PlatformAnimationLib",
"PluginCoreLib",
- "SystemUIAnimationLib",
"SystemUICommon",
"SystemUILogLib",
"androidx.annotation_annotation",
diff --git a/packages/SystemUI/res/layout/remote_input.xml b/packages/SystemUI/res/layout/remote_input.xml
index f4b0a45..84681d34 100644
--- a/packages/SystemUI/res/layout/remote_input.xml
+++ b/packages/SystemUI/res/layout/remote_input.xml
@@ -89,7 +89,6 @@
android:textColorHint="@color/remote_input_hint"
android:textSize="16sp"
android:background="@null"
- android:maxLines="4"
android:ellipsize="start"
android:inputType="textShortMessage|textMultiLine|textAutoCorrect|textCapSentences"
android:imeOptions="actionSend|flagNoExtractUi|flagNoFullscreen" />
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 74e92ba..40fddc8 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"á á
áá¥á®áœ ááµá¥ áá£áª ášááµá³áá»áᜠáá°áá áªá«á á«ááá¥á©"</string>
<string name="install_app" msgid="5066668100199613936">"áá°áá áªá«á á«á"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"áá° áá«á áá³á« ááážá£ášá
?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"ášááµá¥ áá³á«á ááážá£ášááᢠášááµ áá³á«á áá ááá¢"</string>
<string name="mirror_display" msgid="2515262008898122928">"áá³á«á á áážá£áá
"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"á á°áá¥áµ"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"áá³á« á°áááá·á"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 8aeeaf7..d19c77b 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -330,12 +330,9 @@
<string name="quick_settings_screen_record_label" msgid="8650355346742003694">"تسجÙÙ Ø§ÙØŽØ§ØŽØ©"</string>
<string name="quick_settings_screen_record_start" msgid="1574725369331638985">"ؚدء"</string>
<string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Ø¥ÙÙØ§Ù"</string>
- <!-- no translation found for qs_record_issue_label (8166290137285529059) -->
- <skip />
- <!-- no translation found for qs_record_issue_start (2979831312582567056) -->
- <skip />
- <!-- no translation found for qs_record_issue_stop (3531747965741982657) -->
- <skip />
+ <string name="qs_record_issue_label" msgid="8166290137285529059">"تسجÙ٠اÙÙ
ØŽÙÙØ©"</string>
+ <string name="qs_record_issue_start" msgid="2979831312582567056">"ؚدء"</string>
+ <string name="qs_record_issue_stop" msgid="3531747965741982657">"Ø¥ÙÙØ§Ù"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Ù
ا ÙÙ Ø§ÙØ¬Ø§ÙØš Ø§ÙØ°Ù ØªØ£Ø«ÙØ± Ù٠تجرؚة استخداÙ
Ø§ÙØ¬ÙازØ"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Ø§Ø®ØªÙØ§Ø± ÙÙØ¹ اÙÙ
ØŽÙÙØ©"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"تسجÙÙ Ø§ÙØŽØ§ØŽØ©"</string>
@@ -416,12 +413,9 @@
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ø¬Ø§Ø±Ù Ø§ÙØŽØÙ • ستÙ
ØªÙØŠ Ø§ÙØšØ·Ø§Ø±ÙØ© Ø®ÙØ§Ù <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Ù
رÙÙØ± Ø³Ø±ÙØ¹Ùا ÙÙÙÙ
ÙÙ ÙØšØ¯Ø¡ Ø§ÙØ¯ÙÙÙ Ø§ÙØªÙجÙÙÙ Ø§ÙØ¹Ø§Ù
."</string>
<string name="button_to_open_widget_editor" msgid="5599945944349057600">"ÙØªØ Ù
ØØ±ÙÙØ± Ø§ÙØªØ·ØšÙÙØ§Øª اÙÙ
ØµØºÙØ±Ø©"</string>
- <!-- no translation found for button_to_remove_widget (3948204829181214098) -->
- <skip />
- <!-- no translation found for hub_mode_add_widget_button_text (4831464661209971729) -->
- <skip />
- <!-- no translation found for hub_mode_editing_exit_button_text (3704686734192264771) -->
- <skip />
+ <string name="button_to_remove_widget" msgid="3948204829181214098">"Ø¥Ø²Ø§ÙØ©"</string>
+ <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Ø¥Ø¶Ø§ÙØ© تطؚÙÙ Ù
ØµØºÙØ±"</string>
+ <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"تÙ
"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"تؚدÙ٠اÙÙ
ستخدÙ
"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"اÙÙØ§ØŠÙ
Ø© اÙÙ
ÙØ³Ø¯ÙØ©"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Ø³ÙØªÙ
ØØ°Ù ÙÙ Ø§ÙØªØ·ØšÙÙØ§Øª ÙØ§ÙØšÙØ§Ùات ÙÙ ÙØ°Ù Ø§ÙØ¬Ùسة."</string>
@@ -1214,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ÙÙ
ÙÙ٠ضؚط تطؚÙ٠تدÙÙ٠اÙÙ
ÙØ§ØØžØ§Øª Ø§ÙØªÙÙØ§ØŠÙ ÙÙ \"Ø§ÙØ¥Ø¹Ø¯Ø§Ø¯Ø§Øª\"."</string>
<string name="install_app" msgid="5066668100199613936">"ØªØ«ØšÙØª Ø§ÙØªØ·ØšÙÙ"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ÙÙ ØªØ±ÙØ¯ ؚث Ù
ØØªÙÙ Ø¬ÙØ§Ø²Ù عÙÙ Ø§ÙØŽØ§ØŽØ© Ø§ÙØ®Ø§Ø±Ø¬ÙØ©Ø"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Ø³ÙØªÙ
اÙÙØ³Ø® اÙÙ
طاؚ٠ÙÙ
ØØªÙÙ Ø§ÙØŽØ§ØŽØ© Ø§ÙØ¯Ø§Ø®ÙÙØ©Ø ÙØ¥ÙÙØ§Ù Ø§ÙØŽØ§ØŽØ© Ø§ÙØ£Ù
اÙ
ÙØ©."</string>
<string name="mirror_display" msgid="2515262008898122928">"ؚث اÙÙ
ØØªÙ٠عÙÙ Ø§ÙØŽØ§ØŽØ©"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Ø¥ØºÙØ§Ù"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"تÙ
ØªÙØµÙÙ Ø§ÙØŽØ§ØŽØ©"</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index a1b5389..4e78f55 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"àŠà§àŠàŠ¿àŠàŠ€ àŠà§àŠàŠŸà§° àŠ¡àŠ¿àŠ«’àŠ²à§àŠ àŠàŠªà§ àŠà§àŠ àŠà§°àŠ"</string>
<string name="install_app" msgid="5066668100199613936">"àŠàŠªà§àŠà§ àŠàŠšàŠ·à§àŠàв àŠà§°àŠ"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"àŠ¬àŠŸàŠ¹à§àŠ¯àŠ¿àŠ àŠ¡àŠ¿àŠàŠªà§àŠ²à§’àŠ²à§ àŠ®àŠ¿à§°’à§° àŠà§°àŠ¿àŠ¬àŠšà§?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"àŠàŠªà§àŠšàŠŸà§° àŠàŠšàŠŸà§° àŠ¡àŠ¿àŠàŠªà§àŠ²à§’ àŠªà§à§°àŠ€àŠ¿àŠ¬àŠ¿àŠ®à§àŠ¬àŠ¿àŠ€ àŠà§°àŠŸ àŠ¹’àŠ¬à¥€ àŠàŠªà§àŠšàŠŸà§° àŠ«à§à§°àŠ£à§àŠ àŠ¡àŠ¿àŠàŠªà§àŠ²à§’ àŠ
àŠ« àŠà§°àŠŸ àŠ¹’àŠ¬à¥€"</string>
<string name="mirror_display" msgid="2515262008898122928">"àŠ¡àŠ¿àŠàŠªà§àŠ²à§’ àŠ®àŠ¿à§°’à§° àŠà§°àŠ"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"àŠ
àŠà§à§°àŠŸàŠ¹à§àН àŠà§°àŠ"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"àŠ¡àŠ¿àŠàŠªà§àŠ²à§’ àŠžàŠàНà§àŠ àŠà§°àŠŸ àŠ¹à§àŠà§"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 2a9a9cf..bf32c5e 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ayarlarda defolt qeydlÉr tÉtbiqi ayarlayın"</string>
<string name="install_app" msgid="5066668100199613936">"TÉtbiqi quraÅdırın"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Xarici displeyÉ Éks etdirilsin?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"İç displey Éks etdirilÉcÉk. Ön ekran deaktiv edilÉcÉk."</string>
<string name="mirror_display" msgid="2515262008898122928">"Displeyi Éks etdirin"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"İmtina edin"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Displey qoÅulub"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 47468ff..56c3e45 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Podesite podrazumevanu aplikaciju za beleške u Podešavanjima"</string>
<string name="install_app" msgid="5066668100199613936">"Instaliraj aplikaciju"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Åœelite li da preslikate na spoljnji ekran?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Unutrašnji ekran Äe se preslikati. Prednji ekran Äe se iskljuÄiti."</string>
<string name="mirror_display" msgid="2515262008898122928">"Preslikaj ekran"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Odbaci"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Ekran je povezan"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 1fe96a6..22d167e 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ÐаЎайÑе ÑÑаМЎаÑÑМП пÑОлПжеМОе за бележкО ÐŸÑ ÐœÐ°ÑÑÑПйкОÑе"</string>
<string name="install_app" msgid="5066668100199613936">"ÐМÑÑалОÑаМе Ма пÑОлПжеМОеÑП"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Ðа Ñе ÐŽÑблОÑа лО Ма вÑМÑÐœÐžÑ ÐµÐºÑаМ?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"СÑÐŽÑÑжаМОеÑП Ма вÑÑÑеÑÐœÐžÑ Ð²Ðž ЎОÑплей Ñе бÑЎе ÐŽÑблОÑаМП. ÐÑеЎМОÑÑ Ð²Ðž ЎОÑплей Ñе бÑЎе ОзклÑÑеМ."</string>
<string name="mirror_display" msgid="2515262008898122928">"ÐÑблОÑаМе Ма ЎОÑплеÑ"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"ÐÑÑ
вÑÑлÑМе"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"СвÑÑзаМ е екÑаМ"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index fa30131..045af93 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"\'àŠžà§àŠàŠ¿àŠàŠž\' àŠ¥à§àŠà§ àŠ¡àŠ¿àŠ«àŠ²à§àŠ àŠšà§àŠ àŠšà§àŠàŠ¯àŠŒàŠŸàŠ° àŠ
à§àŠ¯àŠŸàŠª àŠžà§àŠ àŠàаà§àŠš"</string>
<string name="install_app" msgid="5066668100199613936">"àŠ
à§àŠ¯àŠŸàŠª àŠàŠšàŠžà§àŠàв àŠàаà§àŠš"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"àŠàŠà§àŠžàŠàŠŸàŠ°à§àŠšàŠŸàŠ² àŠ¡àŠ¿àŠžàŠªà§àвà§àŠ€à§ àŠ®àŠ¿àŠ°àŠ° àŠàŠ°àŠ¬à§àŠš?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"àŠàŠªàŠšàŠŸàŠ° àŠàŠšàŠŸàŠ° àŠ¡àŠ¿àŠžàŠªà§àŠ²à§ àŠ®àŠ¿àŠ°àŠ° àŠàŠ°àŠŸ àŠ¹àŠ¬à§à¥€ àŠàŠªàŠšàŠŸàŠ° àŠ«à§àŠ°àŠšà§àŠ àŠ¡àŠ¿àŠžàŠªà§àŠ²à§ àŠ¬àŠšà§àЧ àŠàŠ°àŠŸ àŠ¹àŠ¬à§à¥€"</string>
<string name="mirror_display" msgid="2515262008898122928">"àŠ¡àŠ¿àŠžàŠªà§àŠ²à§ àŠŠà§àŠàŠŸàŠš"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"àŠ¬àŠŸàŠ€àŠ¿àŠ² àŠàаà§àŠš"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"àŠ¡àŠ¿àŠžàŠªà§àŠ²à§ àŠàŠŸàŠšà§àŠà§àŠ àŠàŠ°àŠŸ àŠàŠà§"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index db5987fb..ae962ce 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Postavite zadanu aplikaciju za bilješke u Postavkama"</string>
<string name="install_app" msgid="5066668100199613936">"Instaliraj aplikaciju"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Preslikati na vanjski ekran?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Unutrašnji ekran Äe se preslikavati. Prednji ekran Äe se iskljuÄiti."</string>
<string name="mirror_display" msgid="2515262008898122928">"Preslikaj ekran"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Odbaci"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Ekran je povezan"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index af4492b..8cf8828 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -1207,10 +1207,9 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"S\'ha detectat la presència d\'usuaris"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Defineix l\'aplicació de notes predeterminada a Configuració"</string>
<string name="install_app" msgid="5066668100199613936">"Instal·la l\'aplicació"</string>
- <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Replicar a la pantalla externa?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
- <string name="mirror_display" msgid="2515262008898122928">"Replica la pantalla"</string>
+ <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Duplicar a la pantalla externa?"</string>
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"La pantalla interior es duplicarà. La pantalla frontal es desactivarà."</string>
+ <string name="mirror_display" msgid="2515262008898122928">"Duplica la pantalla"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Ignora"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Pantalla connectada"</string>
<string name="privacy_dialog_title" msgid="7839968133469098311">"Micròfon i càmera"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 25fcf99..17084dc 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Výchozí aplikaci pro poznámky nastavíte v Nastavení"</string>
<string name="install_app" msgid="5066668100199613936">"Nainstalovat aplikaci"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Zrcadlit na externí displej?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"VnitÅní displej bude zrcadlen. PÅední displej bude vypnutý."</string>
<string name="mirror_display" msgid="2515262008898122928">"Zrcadlit displej"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"ZavÅít"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Displej pÅipojen"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 91c223a..41abea3 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Angiv standardapp til noter i Indstillinger"</string>
<string name="install_app" msgid="5066668100199613936">"Installer app"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vil du spejle til ekstern skærm?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Din indre skærm spejles. Din skærm på forsiden slukkes."</string>
<string name="mirror_display" msgid="2515262008898122928">"Spejl skærm"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Luk"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Skærmen er tilsluttet"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 29b7018..d95e229 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Standard-Notizen-App in den Einstellungen einrichten"</string>
<string name="install_app" msgid="5066668100199613936">"App installieren"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Auf externen Bildschirm spiegeln?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Dein inneres Display wird gespiegelt. Das Frontdisplay wird ausgeschaltet."</string>
<string name="mirror_display" msgid="2515262008898122928">"Bildschirm spiegeln"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Schließen"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Bildschirm verbunden"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index dd24ca6..5848e4f 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ορίστε την προεπιλεγμÎνη εφαρμογή σημειÏσεων στις Ρυθμίσεις"</string>
<string name="install_app" msgid="5066668100199613936">"Εγκατάσταση εφαρμογής"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ΚατοπτρισμÏς σε εξωτερική οθÏνη;"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Θα γίνει κατοπτρισμÏς της εσωτερικής προβολής. Η μπροστινή οθÏνη θα απενεργοποιηθεί."</string>
<string name="mirror_display" msgid="2515262008898122928">"ΚατοπτρισμÏς οθÏνης"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Παράβλεψη"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Η οθÏνη είναι συνδεδεμÎνη"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 2d05291..870e4dd 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Set default notes app in Settings"</string>
<string name="install_app" msgid="5066668100199613936">"Install app"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Mirror to external display?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Your inner display will be mirrored. Your front display will be turned off."</string>
<string name="mirror_display" msgid="2515262008898122928">"Mirror display"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Dismiss"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Display connected"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index c043b2c..f25baf2 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Set default notes app in Settings"</string>
<string name="install_app" msgid="5066668100199613936">"Install app"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Mirror to external display?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Your inner display will be mirrored. Your front display will be turned off."</string>
<string name="mirror_display" msgid="2515262008898122928">"Mirror display"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Dismiss"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Display connected"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 2d05291..870e4dd 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Set default notes app in Settings"</string>
<string name="install_app" msgid="5066668100199613936">"Install app"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Mirror to external display?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Your inner display will be mirrored. Your front display will be turned off."</string>
<string name="mirror_display" msgid="2515262008898122928">"Mirror display"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Dismiss"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Display connected"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 2d05291..870e4dd 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Set default notes app in Settings"</string>
<string name="install_app" msgid="5066668100199613936">"Install app"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Mirror to external display?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Your inner display will be mirrored. Your front display will be turned off."</string>
<string name="mirror_display" msgid="2515262008898122928">"Mirror display"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Dismiss"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Display connected"</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index 3691b2c..b3ed714 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Set default notes app in Settings"</string>
<string name="install_app" msgid="5066668100199613936">"Install app"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Mirror to external display?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Your inner display will be mirrored. Your front display will be turned off."</string>
<string name="mirror_display" msgid="2515262008898122928">"Mirror display"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Dismiss"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Display connected"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 6a4818e..c535560 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Configura la app de notas predeterminada en Configuración"</string>
<string name="install_app" msgid="5066668100199613936">"Instalar app"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"¿Quieres duplicar en la pantalla externa?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Se duplicará la pantalla interior. Se apagará la pantalla frontal."</string>
<string name="mirror_display" msgid="2515262008898122928">"Duplicar pantalla"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Descartar"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Pantalla conectada"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 4e41db3..1157ff1 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Configura la aplicación de notas predeterminada en Ajustes"</string>
<string name="install_app" msgid="5066668100199613936">"Instalar aplicación"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"¿Proyectar a pantalla externa?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Se proyectará tu pantalla interior. Se apagará tu pantalla frontal."</string>
<string name="mirror_display" msgid="2515262008898122928">"Proyectar pantalla"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Cerrar"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Pantalla conectada"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 231ddeb..36b3f1e 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Määrake seadetes märkmete vaikerakendus."</string>
<string name="install_app" msgid="5066668100199613936">"Installi rakendus"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Kas peegeldada välisekraanile?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Teie siseekraani peegeldatakse. Teie esiekraan lülitatakse välja."</string>
<string name="mirror_display" msgid="2515262008898122928">"Peegelda ekraani"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Loobu"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Kuvar on ühendatud"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 20bf71b..5ea02d1 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ezarri oharren aplikazio lehenetsia ezarpenetan"</string>
<string name="install_app" msgid="5066668100199613936">"Instalatu aplikazioa"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Kanpoko pantailan islatu nahi duzu?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Barneko pantaila islatuko da. Aurreko pantaila desaktibatu egingo da."</string>
<string name="mirror_display" msgid="2515262008898122928">"Islatu pantaila"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Baztertu"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Konektatutako pantaila"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index e161d7a..aa77b4b 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ØšØ±ÙØ§Ù
Ù ÙŸÛØŽÙرض ÛØ§Ø¯Ø¯Ø§ØŽØª را در «ØªÙØžÛÙ
ات» ØªÙØžÛÙ
Ú©ÙÛØ¯"</string>
<string name="install_app" msgid="5066668100199613936">"ÙØµØš ØšØ±ÙØ§Ù
Ù"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"رÙÛ ÙÙ
Ø§ÛØŽÚ¯Ø± Ø®Ø§Ø±Ø¬Û ÙØ±ÛÙÙØ³Ø§Ø²Û ØŽÙØ¯Ø"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"ÙÙ
Ø§ÛØŽÚ¯Ø± داخÙÛ ØŽÙ
ا ÙØ±ÛÙÙØ³Ø§Ø²Û Ù
ÛØŽÙد. ÙÙ
Ø§ÛØŽÚ¯Ø± جÙ٠خاÙ
ÙØŽ Ù
ÛØŽÙد."</string>
<string name="mirror_display" msgid="2515262008898122928">"ÙØ±ÛÙÙØ³Ø§Ø²Û ÙÙ
Ø§ÛØŽÚ¯Ø±"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"ؚستÙ"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"ÙÙ
Ø§ÛØŽÚ¯Ø± Ù
تص٠؎د"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 240607b..882c42c 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Aseta oletusmuistiinpanosovellus Asetuksista"</string>
<string name="install_app" msgid="5066668100199613936">"Asenna sovellus"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Peilataanko ulkoiselle näytölle?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Sisänäyttö peilataan. Etunäyttö laitetaan pois päältä."</string>
<string name="mirror_display" msgid="2515262008898122928">"Peilaa näyttö"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Ohita"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Näyttö yhdistetty"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 82a56f5..8370b01 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Définir l\'application de prise de notes par défaut dans les Paramètres"</string>
<string name="install_app" msgid="5066668100199613936">"Installer l\'application"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Dupliquer l\'écran sur un moniteur externe?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Votre écran intérieur sera dupliqué. Votre écran frontal sera désactivé."</string>
<string name="mirror_display" msgid="2515262008898122928">"Dupliquer l\'écran"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Fermer"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Écran connecté"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index c35f9ee..af81a31 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Définir une appli de notes par défaut dans les paramètres"</string>
<string name="install_app" msgid="5066668100199613936">"Installer l\'appli"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Dupliquer sur l\'écran externe ?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Votre écran intérieur sera dupliqué. Votre écran frontal sera éteint."</string>
<string name="mirror_display" msgid="2515262008898122928">"Dupliquer l\'écran"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Fermer"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Écran connecté"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 9cf968e..ef0751b 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Establece a aplicación de notas predeterminada en Configuración"</string>
<string name="install_app" msgid="5066668100199613936">"Instalar aplicación"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Queres proxectar contido nunha pantalla externa?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Proxectarase a pantalla interior. Desactivarase a pantalla frontal."</string>
<string name="mirror_display" msgid="2515262008898122928">"Proxectar pantalla"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Pechar"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Pantalla conectada"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index df7b203..d92231c 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ઞà«àªàª¿àªàªàª®àªŸàª ચà«àªàª§àªšà« ડિફà«àª²à«àª àªàªª ઞà«àª àªàª°à«"</string>
<string name="install_app" msgid="5066668100199613936">"àªàªª àªàªšà«àªžà«àªà«àª² àªàª°à«"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"શà«àª બટહà«àª¯ ડિઞà«àªªà«àª²à« પર મિરર àªàª°à«àª?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"઀મટરà«àª àªàªšàª° ડિઞà«àªªà«àª²à« મિરર àªàª°àªµàªŸàª®àªŸàª àªàªµàª¶à«. ઀મટરà«àª ફà«àª°àªšà«àª ડિઞà«àªªà«àª²à« બàªàª§ àªàª°àªµàªŸàª®àªŸàª àªàªµàª¶à«."</string>
<string name="mirror_display" msgid="2515262008898122928">"મિરર ડિઞà«àªªà«àª²à«"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"àªà«àª¡à« ઊà«"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Display àªàªšà«àªà«àªà«àª¡ àªà«"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index eec3078..f8c78a9 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"à€žà¥à€à€¿à€à€ à€®à¥à€ à€à€Ÿà€à€°, à€šà¥à€ à€²à¥à€šà¥ à€à¥ à€žà¥à€µà€¿à€§à€Ÿ à€Šà¥à€šà¥ à€µà€Ÿà€²à¥ à€à€ªà¥à€²à€¿à€à¥à€¶à€š à€à¥ à€¡à€¿à€«à€Œà¥à€²à¥à€ à€à¥ à€€à¥à€° à€ªà€° à€žà¥à€ à€à€°à¥à€"</string>
<string name="install_app" msgid="5066668100199613936">"à€à€ªà¥à€²à€¿à€à¥à€¶à€š à€à€à€žà¥à€à¥à€² à€à€°à¥à€"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"à€à¥à€¯à€Ÿ à€à€ªà€à¥ à€à€¿à€žà¥ à€¬à€Ÿà€¹à€°à¥ à€¡à€¿à€µà€Ÿà€à€ž à€ªà€° à€¡à€¿à€žà€ªà¥à€²à¥ à€à€°à€šà€Ÿ à€¹à¥?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"à€à€ªà€à¥ à€«à€Œà¥à€š à€à¥ à€à€šà€° à€¡à€¿à€žà€ªà¥à€²à¥ à€à¥ à€žà¥à€à¥à€°à¥à€š à€¶à¥à€¯à€° à€à¥ à€à€Ÿà€à€à¥. à€«à€Œà¥à€°à€à€ à€¡à€¿à€žà€ªà¥à€²à¥ à€à¥ à€¬à€à€Š à€à€° à€Šà€¿à€¯à€Ÿ à€à€Ÿà€à€à€Ÿ."</string>
<string name="mirror_display" msgid="2515262008898122928">"à€¡à€¿à€žà€ªà¥à€²à¥ à€à€°à¥à€"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"à€à€Ÿà€°à€¿à€ à€à€°à¥à€"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"à€¡à€¿à€žà€ªà¥à€²à¥ à€à€šà¥à€à¥à€ à€à€¿à€¯à€Ÿ à€à€¯à€Ÿ"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index f0ed400..0803aeb 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Postavite zadanu aplikaciju za bilješke u postavkama"</string>
<string name="install_app" msgid="5066668100199613936">"Instalacija"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Åœelite li zrcaliti na vanjski zaslon?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Unutarnji zaslon bit Äe zrcaljen. Prednji zaslon bit Äe iskljuÄen."</string>
<string name="mirror_display" msgid="2515262008898122928">"Zrcaljenje zaslona"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Odbaci"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Zaslon je povezan"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 30d2961..fd9d832 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Állítson be alapértelmezett jegyzetkészítÅ alkalmazást a Beállításokban"</string>
<string name="install_app" msgid="5066668100199613936">"Alkalmazás telepítése"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Tükrözi a kijelzÅt a külsÅ képernyÅre?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"A belsÅ kijelzÅ tükrözve lesz. Az elülsÅ kijelzÅ ki lesz kapcsolva."</string>
<string name="mirror_display" msgid="2515262008898122928">"KijelzÅ tükrözése"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Elvetés"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"KijelzÅ csatlakoztatva"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index bbdeb16..a9252e2 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ô¿Õ¡ÖÕ£Õ¡ÕŸÕžÖÕ¥Ö Õ¶Õ·ÕžÖÕŽÕ¶Õ¥ÖÕ« Õ¯Õ¡Õ¶ÕÕ¡Õ€ÖÕŸÕ¡Õ® Õ°Õ¡ÕŸÕ¥Õ¬ÕŸÕ¡Õ® Ô¿Õ¡ÖÕ£Õ¡ÕŸÕžÖÕžÖÕŽÕ¶Õ¥ÖÕžÖÕŽ"</string>
<string name="install_app" msgid="5066668100199613936">"ÕÕ¥Õ²Õ¡Õ€ÖÕ¥Õ¬ Õ°Õ¡ÕŸÕ¥Õ¬ÕŸÕ¡Õ®Õš"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ÕÕ¡ÕµÕ¥Õ¬Õ¡ÕºÕ¡Õ¿Õ³Õ¥Õ¶Õ¥ÕÕ¬ Õ¡ÖÕ¿Õ¡ÖÕ«Õ¶ Õ§Õ¯ÖÕ¡Õ¶Õ«Õ¶"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"ÕÕ¥ÖÖÕ«Õ¶ Õ§Õ¯ÖÕ¡Õ¶Õš Õ¯Õ°Õ¡ÕµÕ¥Õ¬Õ¡ÕºÕ¡Õ¿Õ³Õ¥Õ¶ÕŸÕ«Ö Ô±ÕŒÕ»ÖÕ« Õ§Õ¯ÖÕ¡Õ¶Õš Õ¯Õ¡Õ¶Õ»Õ¡Õ¿ÕŸÕ«Ö"</string>
<string name="mirror_display" msgid="2515262008898122928">"ÕÕ¡ÕµÕ¥Õ¬Õ¡ÕºÕ¡Õ¿Õ³Õ¥Õ¶Õ¥Õ¬ Õ§Õ¯ÖÕ¡Õ¶Õš"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"ÕÕ¡Õ¯Õ¥Õ¬"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Ô·Õ¯ÖÕ¡Õ¶Õš ÕŽÕ«Õ¡ÖÕ¡Õ® Õ§"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index f2ed0e7..8ef809a 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Setel aplikasi catatan default di Setelan"</string>
<string name="install_app" msgid="5066668100199613936">"Instal aplikasi"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Cerminkan ke layar eksternal?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Layar dalam akan dicerminkan. Layar depan akan dinonaktifkan."</string>
<string name="mirror_display" msgid="2515262008898122928">"Cerminkan layar"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Tutup"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Layar terhubung"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index f1f1b0f..cef3285 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Stilltu sjálfgefið glósuforrit í stillingunum"</string>
<string name="install_app" msgid="5066668100199613936">"Setja upp forrit"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Spegla yfir á ytri skjá?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Innri skjárinn þinn verður speglaður. Slökkt verður á framskjánum þínum."</string>
<string name="mirror_display" msgid="2515262008898122928">"Spegla skjá"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Hunsa"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Skjár tengdur"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 223e39f..c5079fe 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Imposta l\'app per le note predefinita nelle Impostazioni"</string>
<string name="install_app" msgid="5066668100199613936">"Installa app"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vuoi eseguire il mirroring al display esterno?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Verrà eseguito il mirroring del tuo display interno. Il tuo display frontale verrà spento."</string>
<string name="mirror_display" msgid="2515262008898122928">"Esegui il mirroring del display"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Chiudi"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Display collegato"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 44fd608..486b22f 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"׊ך×× ×××××ך ×ת ××€××ק׊××ת ×ך×ךת ××××× ×׀תק×× ×\'×××ך×ת\'"</string>
<string name="install_app" msgid="5066668100199613936">"××ª×§× ×ª ×××€××ק׊××"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"×שקף ×××¡× ××׊×× ×?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"×××¡× ××€× ××× ×©×× ×ש×××€×. ×××¡× ×××׊×× × ×©×× ×××××."</string>
<string name="mirror_display" msgid="2515262008898122928">"ת׊××ת ×ך××"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"ס××ך×"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"×××¡× ××××ך"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 38f2dc8..c742f93 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"[èšå®] ã§ããã©ã«ãã®ã¡ã¢ã¢ããªãèšå®ããŠãã ãã"</string>
<string name="install_app" msgid="5066668100199613936">"ã¢ããªãã€ã³ã¹ããŒã«"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"å€éšãã£ã¹ãã¬ã€ã«ãã©ãŒãªã³ã°ããŸããïŒ"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"ã€ã³ã㌠ãã£ã¹ãã¬ã€ããã©ãŒãªã³ã°ãããŸããããã³ã ãã£ã¹ãã¬ã€ã¯ OFF ã«ãªããŸãã"</string>
<string name="mirror_display" msgid="2515262008898122928">"ãã£ã¹ãã¬ã€ããã©ãŒãªã³ã°ãã"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"éãã"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"ãã£ã¹ãã¬ã€ã«æ¥ç¶ããŸãã"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index bb21f3f..9d386ef 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"áááá§áááá áááá£ááá¡á®áááá ášáááášáááááá¡ ááá ááá áááá¢á ááášá"</string>
<string name="install_app" msgid="5066668100199613936">"áááá¡ ááá¡á¢ááááªáá"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ááá ááááá¡ ááá á ááá áááá?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"áá¥áááá ášááá ááá ááá ááá áááááá. áá¥áááá á¬ááá ááá ááá áááááá ááááá."</string>
<string name="mirror_display" msgid="2515262008898122928">"ááá áááá¡ áá ááááá"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"ááá®á£á áá"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"ááá ááá áááááášáá ááá£ááá"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index b39d5a5..d2c60f1 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ÐаÑаЌеÑÑлеÑЎеМ ÓÐŽÐµÐ¿ÐºÑ Ð¶Ð°Ð·Ð±Ð° ÒПлЎаМбаÑÑМ ПÑМаÑÑÒ£Ñз."</string>
<string name="install_app" msgid="5066668100199613936">"ÒÐŸÐ»ÐŽÐ°ÐœÐ±Ð°ÐœÑ ÐŸÑМаÑÑ"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"СÑÑÑÒÑ ÑкÑаМ аÑÒÑÐ»Ñ ÐŽÐ° көÑÑеÑÑ ÐºÐµÑек пе?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"ÐÑÐºÑ ÑкÑаМ көÑÑÑЌеÑÑ ÐºÓ©ÑÑеÑÑлеЎÑ. ÐлЎÑÒ£ÒÑ ÑкÑаМ Ó©ÑÑÑÑлеЎÑ."</string>
<string name="mirror_display" msgid="2515262008898122928">"ÐÓ©ÑÑеÑÑ"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"ÐабÑ"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"ÐОÑплей ÒПÑÑлЎÑ"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index e989a9d..ec81523 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ááááááááááá·áážáááááá
ááá¶ááááá¶áááŸááá
áááá»ááá¶áááááá"</string>
<string name="install_app" msgid="5066668100199613936">"ááá¡áŸáâáááááá·ááž"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"áááá
á¶áááá
âáááá¶ááá¢ááááááâáá¶ááááá
�"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"á¢áááááááá¶ááááá»áááááá¢ááááá¹áááááŒááá¶áááááŸâáááá¶ááááááá
áá·ááá
ááá á¢áááááááá»áááááá¢ááááá¹áááááŒááá¶ááá·áá"</string>
<string name="mirror_display" msgid="2515262008898122928">"áááá
á¶áááá
áááá¶ááá¢áááááá"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"á
ááá¶áá
áá"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"áááá¶ááá¢ááááááááááŒááá¶ááááá¶áá"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 39b1468..33f4528 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -93,7 +93,7 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"à²à³à²³à²à²¿à²š ಬà³à²à²¡à²°à²¿ ಶà³à²à²¡à²Ÿ <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"à²à²¡à²à²Ÿà²à²Š ಬà³à²à²¡à²°à²¿ ಶà³à²à²¡à²Ÿ <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"ಬಲà²à²Ÿà²à²Š ಬà³à²à²¡à²°à²¿ ಶà³à²à²¡à²Ÿ <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
- <string name="screenshot_work_profile_notification" msgid="203041724052970693">"à²à³à²²à²žà²Š ಪà³à²°à³à²«à³à²²à³à²šà²²à³à²²à²¿à²š <xliff:g id="APP">%1$s</xliff:g> ಚಲà³à²²à²¿ à²à²³à²¿à²žà²²à²Ÿà²à²¿à²Šà³"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"à²à³à²²à²žà²Š ಪà³à²°à³à²«à³à²²à³à²šà²²à³à²²à²¿à²š <xliff:g id="APP">%1$s</xliff:g> ಚಲà³à²²à²¿ ಞà³à²µà³ ಮಟಡಲಟà²à²¿à²Šà³"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ಫà³à²²à³à²à²³à³"</string>
<string name="screenshot_detected_template" msgid="7940376642921719915">"ಠಞà³à²à³à²°à³à²šà³à²¶à²Ÿà²à³ à²
ಚà³à²šà³ <xliff:g id="APPNAME">%1$s</xliff:g> ಪಀà³à²€à³à²¹à²à³à²à²¿à²Šà³."</string>
<string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> ಹಟà²à³ ಀà³à²°à³à²Šà²¿à²°à³à²µ à²à²€à²° à²à³à²¯à²ªà³à²à²³à³ ಠಞà³à²à³à²°à³à²šà³à²¶à²Ÿà²à³ à²
ಚà³à²šà³ ಪಀà³à²€à³à²¹à²à³à²à²¿à²µà³."</string>
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ಞà³à²à³à²à²¿à²à²à³à²à²³à²²à³à²²à²¿ ಡà³à²«à²Ÿà²²à³à²à³ à²à²¿à²ªà³à²ªà²£à²¿à²à²³ à²à³à²¯à²ªà³ à²
ಚà³à²šà³ ಞà³à²à³ ಮಟಡಿ"</string>
<string name="install_app" msgid="5066668100199613936">"à²à³à²¯à²ªà³ à²à²šà³à²žà³à²à²Ÿà²²à³ ಮಟಡಿ"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ಬಟಹà³à²¯ ಡಿಞà³à²ªà³à²²à³à²à³ ಪà³à²°à²€à²¿à²¬à²¿à²à²¬à²¿à²žà²¬à³à²à³?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"ಚಿಮà³à²® à²à²³à²à²¿à²š ಡಿಞà³à²ªà³à²²à³ à²
ಚà³à²šà³ ಪà³à²°à²€à²¿à²¬à²¿à²à²¬à²¿à²žà²²à²Ÿà²à³à²€à³à²€à²Šà³. ಚಿಮà³à²® ಫà³à²°à²à²à³ ಡಿಞà³à²ªà³à²²à³ à²
ಚà³à²šà³ à²à²«à³ ಮಟಡಲಟà²à³à²€à³à²€à²Šà³."</string>
<string name="mirror_display" msgid="2515262008898122928">"ಮಿರರೠಡಿಞà³à²ªà³à²²à³"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"ವà²à²Ÿà²à³à²³à²¿à²žà²¿"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"ಡಿಞà³à²ªà³à²²à³ à²à²šà³à²à³à²à³ à²à²à²¿à²Šà³"</string>
diff --git a/packages/SystemUI/res/values-kn/tiles_states_strings.xml b/packages/SystemUI/res/values-kn/tiles_states_strings.xml
index 16e82ea..876562d 100644
--- a/packages/SystemUI/res/values-kn/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-kn/tiles_states_strings.xml
@@ -33,147 +33,147 @@
<!-- no translation found for tile_states_default:2 (9192445505551219506) -->
<string-array name="tile_states_internet">
<item msgid="5499482407653291407">"ಲà²à³à²¯à²µà²¿à²²à³à²²"</item>
- <item msgid="3048856902433862868">"à²à²«à³ ಮಟಡಿ"</item>
+ <item msgid="3048856902433862868">"à²à²«à³"</item>
<item msgid="6877982264300789870">"à²à²šà³ ಮಟಡಿ"</item>
</string-array>
<string-array name="tile_states_wifi">
<item msgid="8054147400538405410">"ಲà²à³à²¯à²µà²¿à²²à³à²²"</item>
- <item msgid="4293012229142257455">"à²à²«à³ ಮಟಡಿ"</item>
+ <item msgid="4293012229142257455">"à²à²«à³"</item>
<item msgid="6221288736127914861">"à²à²šà³ ಮಟಡಿ"</item>
</string-array>
<string-array name="tile_states_cell">
<item msgid="1235899788959500719">"ಲà²à³à²¯à²µà²¿à²²à³à²²"</item>
- <item msgid="2074416252859094119">"à²à²«à³ ಮಟಡಿ"</item>
+ <item msgid="2074416252859094119">"à²à²«à³"</item>
<item msgid="287997784730044767">"à²à²šà³ ಮಟಡಿ"</item>
</string-array>
<string-array name="tile_states_battery">
<item msgid="6311253873330062961">"ಲà²à³à²¯à²µà²¿à²²à³à²²"</item>
- <item msgid="7838121007534579872">"à²à²«à³ ಮಟಡಿ"</item>
+ <item msgid="7838121007534579872">"à²à²«à³"</item>
<item msgid="1578872232501319194">"à²à²šà³ ಮಟಡಿ"</item>
</string-array>
<string-array name="tile_states_dnd">
<item msgid="467587075903158357">"ಲà²à³à²¯à²µà²¿à²²à³à²²"</item>
- <item msgid="5376619709702103243">"à²à²«à³ ಮಟಡಿ"</item>
+ <item msgid="5376619709702103243">"à²à²«à³"</item>
<item msgid="4875147066469902392">"à²à²šà³ ಮಟಡಿ"</item>
</string-array>
<string-array name="tile_states_flashlight">
<item msgid="3465257127433353857">"ಲà²à³à²¯à²µà²¿à²²à³à²²"</item>
- <item msgid="5044688398303285224">"à²à²«à³ ಮಟಡಿ"</item>
+ <item msgid="5044688398303285224">"à²à²«à³"</item>
<item msgid="8527389108867454098">"à²à²šà³ ಮಟಡಿ"</item>
</string-array>
<string-array name="tile_states_rotation">
<item msgid="4578491772376121579">"ಲà²à³à²¯à²µà²¿à²²à³à²²"</item>
- <item msgid="5776427577477729185">"à²à²«à³ ಮಟಡಿ"</item>
+ <item msgid="5776427577477729185">"à²à²«à³"</item>
<item msgid="7105052717007227415">"à²à²šà³ ಮಟಡಿ"</item>
</string-array>
<string-array name="tile_states_bt">
<item msgid="5330252067413512277">"ಲà²à³à²¯à²µà²¿à²²à³à²²"</item>
- <item msgid="5315121904534729843">"à²à²«à³ ಮಟಡಿ"</item>
+ <item msgid="5315121904534729843">"à²à²«à³"</item>
<item msgid="503679232285959074">"à²à²šà³ ಮಟಡಿ"</item>
</string-array>
<string-array name="tile_states_airplane">
<item msgid="1985366811411407764">"ಲà²à³à²¯à²µà²¿à²²à³à²²"</item>
- <item msgid="4801037224991420996">"à²à²«à³ ಮಟಡಿ"</item>
+ <item msgid="4801037224991420996">"à²à²«à³"</item>
<item msgid="1982293347302546665">"à²à²šà³ ಮಟಡಿ"</item>
</string-array>
<string-array name="tile_states_location">
<item msgid="3316542218706374405">"ಲà²à³à²¯à²µà²¿à²²à³à²²"</item>
- <item msgid="4813655083852587017">"à²à²«à³ ಮಟಡಿ"</item>
+ <item msgid="4813655083852587017">"à²à²«à³"</item>
<item msgid="6744077414775180687">"à²à²šà³ ಮಟಡಿ"</item>
</string-array>
<string-array name="tile_states_hotspot">
<item msgid="3145597331197351214">"ಲà²à³à²¯à²µà²¿à²²à³à²²"</item>
- <item msgid="5715725170633593906">"à²à²«à³ ಮಟಡಿ"</item>
+ <item msgid="5715725170633593906">"à²à²«à³"</item>
<item msgid="2075645297847971154">"à²à²šà³ ಮಟಡಿ"</item>
</string-array>
<string-array name="tile_states_color_correction">
<item msgid="2840507878437297682">"ಲà²à³à²¯à²µà²¿à²²à³à²²"</item>
- <item msgid="1909756493418256167">"à²à²«à³ ಮಟಡಿ"</item>
+ <item msgid="1909756493418256167">"à²à²«à³"</item>
<item msgid="4531508423703413340">"à²à²šà³ ಮಟಡಿ"</item>
</string-array>
<string-array name="tile_states_inversion">
<item msgid="3638187931191394628">"ಲà²à³à²¯à²µà²¿à²²à³à²²"</item>
- <item msgid="9103697205127645916">"à²à²«à³ ಮಟಡಿ"</item>
+ <item msgid="9103697205127645916">"à²à²«à³"</item>
<item msgid="8067744885820618230">"à²à²šà³ ಮಟಡಿ"</item>
</string-array>
<string-array name="tile_states_saver">
<item msgid="39714521631367660">"ಲà²à³à²¯à²µà²¿à²²à³à²²"</item>
- <item msgid="6983679487661600728">"à²à²«à³ ಮಟಡಿ"</item>
+ <item msgid="6983679487661600728">"à²à²«à³"</item>
<item msgid="7520663805910678476">"à²à²šà³ ಮಟಡಿ"</item>
</string-array>
<string-array name="tile_states_dark">
<item msgid="2762596907080603047">"ಲà²à³à²¯à²µà²¿à²²à³à²²"</item>
- <item msgid="400477985171353">"à²à²«à³ ಮಟಡಿ"</item>
+ <item msgid="400477985171353">"à²à²«à³"</item>
<item msgid="630890598801118771">"à²à²šà³ ಮಟಡಿ"</item>
</string-array>
<string-array name="tile_states_work">
<item msgid="389523503690414094">"ಲà²à³à²¯à²µà²¿à²²à³à²²"</item>
- <item msgid="8045580926543311193">"à²à²«à³ ಮಟಡಿ"</item>
+ <item msgid="8045580926543311193">"à²à²«à³"</item>
<item msgid="4913460972266982499">"à²à²šà³ ಮಟಡಿ"</item>
</string-array>
<string-array name="tile_states_cast">
<item msgid="6032026038702435350">"ಲà²à³à²¯à²µà²¿à²²à³à²²"</item>
- <item msgid="1488620600954313499">"à²à²«à³ ಮಟಡಿ"</item>
+ <item msgid="1488620600954313499">"à²à²«à³"</item>
<item msgid="588467578853244035">"à²à²šà³ ಮಟಡಿ"</item>
</string-array>
<string-array name="tile_states_night">
<item msgid="7857498964264855466">"ಲà²à³à²¯à²µà²¿à²²à³à²²"</item>
- <item msgid="2744885441164350155">"à²à²«à³ ಮಟಡಿ"</item>
+ <item msgid="2744885441164350155">"à²à²«à³"</item>
<item msgid="151121227514952197">"à²à²šà³ ಮಟಡಿ"</item>
</string-array>
<string-array name="tile_states_screenrecord">
<item msgid="1085836626613341403">"ಲà²à³à²¯à²µà²¿à²²à³à²²"</item>
- <item msgid="8259411607272330225">"à²à²«à³ ಮಟಡಿ"</item>
+ <item msgid="8259411607272330225">"à²à²«à³"</item>
<item msgid="578444932039713369">"à²à²šà³ ಮಟಡಿ"</item>
</string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"ಲà²à³à²¯à²µà²¿à²²à³à²²"</item>
- <item msgid="8707481475312432575">"à²à²«à³ ಮಟಡಿ"</item>
+ <item msgid="8707481475312432575">"à²à²«à³"</item>
<item msgid="8031106212477483874">"à²à²šà³ ಮಟಡಿ"</item>
</string-array>
<string-array name="tile_states_reduce_brightness">
<item msgid="1839836132729571766">"ಲà²à³à²¯à²µà²¿à²²à³à²²"</item>
- <item msgid="4572245614982283078">"à²à²«à³ ಮಟಡಿ"</item>
+ <item msgid="4572245614982283078">"à²à²«à³"</item>
<item msgid="6536448410252185664">"à²à²šà³ ಮಟಡಿ"</item>
</string-array>
<string-array name="tile_states_cameratoggle">
<item msgid="6680671247180519913">"ಲà²à³à²¯à²µà²¿à²²à³à²²"</item>
- <item msgid="4765607635752003190">"à²à²«à³ ಮಟಡಿ"</item>
+ <item msgid="4765607635752003190">"à²à²«à³"</item>
<item msgid="1697460731949649844">"à²à²šà³ ಮಟಡಿ"</item>
</string-array>
<string-array name="tile_states_mictoggle">
<item msgid="6895831614067195493">"ಲà²à³à²¯à²µà²¿à²²à³à²²"</item>
- <item msgid="3296179158646568218">"à²à²«à³ ಮಟಡಿ"</item>
+ <item msgid="3296179158646568218">"à²à²«à³"</item>
<item msgid="8998632451221157987">"à²à²šà³ ಮಟಡಿ"</item>
</string-array>
<string-array name="tile_states_controls">
<item msgid="8199009425335668294">"ಲà²à³à²¯à²µà²¿à²²à³à²²"</item>
- <item msgid="4544919905196727508">"à²à²«à³ ಮಟಡಿ"</item>
+ <item msgid="4544919905196727508">"à²à²«à³"</item>
<item msgid="3422023746567004609">"à²à²šà³ ಮಟಡಿ"</item>
</string-array>
<string-array name="tile_states_wallet">
<item msgid="4177615438710836341">"ಲà²à³à²¯à²µà²¿à²²à³à²²"</item>
- <item msgid="7571394439974244289">"à²à²«à³ ಮಟಡಿ"</item>
+ <item msgid="7571394439974244289">"à²à²«à³"</item>
<item msgid="6866424167599381915">"à²à²šà³ ಮಟಡಿ"</item>
</string-array>
<string-array name="tile_states_qr_code_scanner">
<item msgid="7435143266149257618">"ಲà²à³à²¯à²µà²¿à²²à³à²²"</item>
- <item msgid="3301403109049256043">"à²à²«à³ ಮಟಡಿ"</item>
+ <item msgid="3301403109049256043">"à²à²«à³"</item>
<item msgid="8878684975184010135">"à²à²šà³ ಮಟಡಿ"</item>
</string-array>
<string-array name="tile_states_alarm">
<item msgid="4936533380177298776">"ಲà²à³à²¯à²µà²¿à²²à³à²²"</item>
- <item msgid="2710157085538036590">"à²à²«à³ ಮಟಡಿ"</item>
+ <item msgid="2710157085538036590">"à²à²«à³"</item>
<item msgid="7809470840976856149">"à²à²šà³ ಮಟಡಿ"</item>
</string-array>
<string-array name="tile_states_onehanded">
<item msgid="8189342855739930015">"ಲà²à³à²¯à²µà²¿à²²à³à²²"</item>
- <item msgid="146088982397753810">"à²à²«à³ ಮಟಡಿ"</item>
+ <item msgid="146088982397753810">"à²à²«à³"</item>
<item msgid="460891964396502657">"à²à²šà³ ಮಟಡಿ"</item>
</string-array>
<string-array name="tile_states_dream">
<item msgid="6184819793571079513">"ಲà²à³à²¯à²µà²¿à²²à³à²²"</item>
- <item msgid="8014986104355098744">"à²à²«à³ ಮಟಡಿ"</item>
+ <item msgid="8014986104355098744">"à²à²«à³"</item>
<item msgid="5966994759929723339">"à²à²šà³ ಮಟಡಿ"</item>
</string-array>
<string-array name="tile_states_font_scaling">
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 7bd6e6f..897b266 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ì€ì ìì Ʞ볞 ë©ëªš ì± ì€ì "</string>
<string name="install_app" msgid="5066668100199613936">"ì± ì€ì¹"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ìžë¶ ëì€íë ìŽë¡ 믞ë¬ë§íìê² ìµëê¹?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"ëŽë¶ ëì€íë ìŽê° 믞ë¬ë§ë©ëë€. ì 멎 ëì€íë ìŽë 꺌ì§ëë€."</string>
<string name="mirror_display" msgid="2515262008898122928">"ëì€íë ìŽ ë¯žë¬ë§"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"ë«êž°"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"ëì€íë ìŽ ì°ê²°ëš"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index d4974e8..2f091ec 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ÐаÑаЌеÑÑлеÑЎеМ ЎеЌейкО кÑÑка жазÑÑÐ»Ð°Ñ ÐºÐŸÐ»ÐŽÐŸÐœÐŒÐŸÑÑМ ÑÑÑÑалаңÑз"</string>
<string name="install_app" msgid="5066668100199613936">"ÐÐŸÐ»ÐŽÐŸÐœÐŒÐŸÐœÑ ÐŸÑМПÑÑÑ"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ТÑÑÐºÑ ÑкÑаМга ÑÑгаÑаÑÑзбÑ?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"ÐÑкО ÑкÑаМÑÒ£Ñз баÑка ÑкÑаМга ÑÑгаÑÑлаÑ. ÐлЎÑÒ£ÐºÑ ÑкÑаМÑÒ£Ñз Ó©ÑÒ¯ÑүлөÑ."</string>
<string name="mirror_display" msgid="2515262008898122928">"ТÑÑÐºÑ ÑкÑаМ"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"ÐабÑÑ"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"ÐкÑаМ ÑÑÑаÑÑÑÑÑлЎÑ"</string>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index 431b77b..c0c5d44 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"àºàº±à»àºàºà»àº²à»àºàº±àºàºàº»àºàºàº±àºàºàº¶àºà»àº¥àºµà»àº¡àºàº»à»àºà»àºàºàº²àºàºàº±à»àºàºà»àº²"</string>
<string name="install_app" msgid="5066668100199613936">"àºàºŽàºàºàº±à»àºà»àºàº±àº"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ສາàºà»àºªà»àºà»àºªàº°à»àºàºàºàº»àºàºàº²àºàºàºàºàºà»?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"àºàº²àºàºªàº°à»àºàºàºàº»àºàºàº²àºà»àºàºàºàºàºà»àº²àºàºàº°àºàº·àºàºªàº°àºà»àºàº. àºàº²àºàºªàº°à»àºàºàºàº»àºàºàº²àºà»à»àº²àºàºàºàºà»àº²àºàºàº°àºàº·àºàºàºŽàºà»àº§à»."</string>
<string name="mirror_display" msgid="2515262008898122928">"àºà»àºªàº°à»àºàºàºàº»àºà»àºàºàºªàº°àºà»àºàº"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"àºàºŽàºà»àº§à»"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"à»àºàº·à»àºàº¡àºà»à»àºà»à»àº¥à»àº§"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index a0eaea2..83d2724 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Nustatykite numatytÄ
jÄ
uÅŸrašÅ³ programÄ
Nustatymuose"</string>
<string name="install_app" msgid="5066668100199613936">"Ä®diegti programÄ
"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Bendrinti ekrano vaizdÄ
išoriniame ekrane?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Bus bendrinamas vidinio rodinio ekrano vaizdas. Priekinis rodinys bus išjungtas."</string>
<string name="mirror_display" msgid="2515262008898122928">"Bendrinti ekrano vaizdÄ
"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Atsisakyti"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Ekranas prijungtas"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index a419f87..4bc71c3 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"IestatÄ«jumos iestatiet noklusÄjuma piezÄ«mju lietotni."</string>
<string name="install_app" msgid="5066668100199613936">"InstalÄt lietotni"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vai spoguÄŒot ÄrÄjÄ displejÄ?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"JÅ«su iekšÄjais displejs tiks spoguÄŒots. JÅ«su priekšÄjais displejs tiks izslÄgts."</string>
<string name="mirror_display" msgid="2515262008898122928">"SpoguÄŒot displeju"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"NerÄdÄ«t"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Pievienots displejs"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index ca9fe9e..28a2a02 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ÐПÑÑавеÑе ÑÑаМЎаÑЎМа аплОкаÑОÑа за белеÑкО вП „ÐПÑÑавкО“"</string>
<string name="install_app" msgid="5066668100199613936">"ÐМÑÑалОÑаÑÑе Ñа аплОкаÑОÑаÑа"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Ðа Ñе пÑеÑлОка Ма МаЎвПÑеÑÐœÐžÐŸÑ ÐµÐºÑаМ?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"ÐаÑÐžÐŸÑ Ð²ÐœÐ°ÑÑеÑеМ екÑаМ Ñе Ñе ПÑÑлОка. ÐаÑÐžÐŸÑ Ð¿ÑеЎеМ екÑаМ Ñе Ñе ОÑклÑÑО."</string>
<string name="mirror_display" msgid="2515262008898122928">"ÐÑеÑÐ»ÐžÐºÐ°Ñ ÐµÐºÑаМ"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"ÐÑÑÑлО"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"ÐкÑÐ°ÐœÐŸÑ Ðµ пПвÑзаМ"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 5ff4656..057f0b0 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"àŽàµàŽ°àŽ®àµàŽàŽ°àŽ£àŽ€àµàŽ€àŽ¿àµœ àŽàµàŽ±àŽ¿àŽªàµàŽªàµàŽàµŸàŽàµàŽàµà޳àµà޳ àŽ¡àŽ¿àŽ«àµàµŸàŽàµàŽàµ àŽàŽªàµàŽªàµ àŽžàŽàµàŽàµàŽàŽ°àŽ¿àŽàµàŽàµàŽ"</string>
<string name="install_app" msgid="5066668100199613936">"àŽàŽªàµàŽªàµ àŽàµ»àŽžàµàޱàµàŽ±àŽŸàµŸ àŽàµàޝàµàޝàµ"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"àŽ¬àŽŸàŽ¹àµàޝ àŽ¡àŽ¿àŽžàµàŽªàµà޲àµàŽ¯àŽ¿àŽ²àµàŽàµàŽàµ àŽ®àŽ¿àŽ±àµŒ àŽàµàޝàµàŽ¯àŽ£àµ?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"àŽšàŽ¿àŽàµàŽà޳àµàŽàµ àŽàŽšàµàŽšàµŒ àŽ¡àŽ¿àŽžàµàŽªàµàŽ²àµ àŽ®àŽ¿àŽ±àµŒ àŽàµàޝàµàޝàµàŽ. àŽšàŽ¿àŽàµàŽà޳àµàŽàµ àŽ«àµàŽ°àŽ£àµàŽàµ àŽ¡àŽ¿àŽžàµàŽªàµàŽ²àµ àŽàŽ«àŽŸàŽàµàŽ."</string>
<string name="mirror_display" msgid="2515262008898122928">"àŽ®àŽ¿àŽ±àµŒ àŽ¡àŽ¿àŽžàµàŽªàµà޲àµ"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"àŽ¡àŽ¿àŽžàµàŽ®àŽ¿àŽžàµ àŽàµàޝàµàޝàµàŽ"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"àŽ¡àŽ¿àŽžàµàŽªàµàŽ²àµ àŽàŽ£àŽàµàޱàµàŽ±àµ àŽàµàޝàµàŽ€àŽ¿àŽ°àŽ¿àŽàµàŽàµàŽšàµàŽšàµ"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index a71aca2..933652b 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ТПÑ
ОÑгППМЎ ÑÑЌЎÑглÑлОйМ өгөгЎЌөл апп ÑПÑ
ОÑÑÑлМа ÑÑ"</string>
<string name="install_app" msgid="5066668100199613936">"ÐппÑг ÑÑÑлгаÑ
"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ÐÐ°ÐŽÐœÑ ÐŽÑлгÑÑÑÐŽ ÑÑÑгал Ò¯Ò¯ÑгÑÑ
Ò¯Ò¯?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Ð¢Ð°ÐœÑ ÐŽÐŸÑППЎ ÐŽÑлгÑÑОйМ ÑÑÑгалÑг Ò¯Ò¯ÑгÑМÑ. Ð¢Ð°ÐœÑ ÑÑÐŽ ÑалÑМ ÐŽÑлгÑÑОйг ÑМÑÑааМа."</string>
<string name="mirror_display" msgid="2515262008898122928">"ÐÑлгÑÑОйМ ÑÑÑгал Ò¯Ò¯ÑгÑÑ
"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"ХааÑ
"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"ÐÑлгÑÑ Ñ
ПлбПгЎÑПМ"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 9dfaa00..aad269b 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"à€žà¥à€à€¿à€à€à¥à€ à€®à€§à¥à€¯à¥ à€¡à¥à€«à¥à€²à¥à€ à€à€¿à€ªà€Ÿ à€
à¥
à€ª à€žà¥à€ à€à€°à€Ÿ"</string>
<string name="install_app" msgid="5066668100199613936">"à€
à¥
à€ª à€à€à€žà¥à€à¥à€² à€à€°à€Ÿ"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"à€¬à€Ÿà€¹à¥à€¯ à€¡à€¿à€žà¥à€ªà¥à€²à¥à€µà€° à€®à€¿à€°à€° à€à€°à€Ÿà€¯à€à¥ à€à€¹à¥ à€à€Ÿ?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"à€€à¥à€®à€à€Ÿ à€
à€à€€à€°à¥à€à€€ à€¡à€¿à€žà¥à€ªà¥à€²à¥ à€®à€¿à€°à€° à€à¥à€²à€Ÿ à€à€Ÿà€à€². à€€à¥à€®à€à€Ÿ à€ªà¥à€¢à¥à€² à€¡à€¿à€žà¥à€ªà¥à€²à¥ à€¬à€à€Š à€à¥à€²à€Ÿ à€à€Ÿà€à€²."</string>
<string name="mirror_display" msgid="2515262008898122928">"à€¡à€¿à€žà¥à€ªà¥à€²à¥ à€®à€¿à€°à€° à€à€°à€Ÿ"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"à€¡à€¿à€žà€®à€¿à€ž à€à€°à€Ÿ"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"à€¡à€¿à€žà¥à€ªà¥à€²à¥ à€à€šà¥à€à¥à€ à€à¥à€²à€Ÿ à€à€¹à¥"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 553d221..8287d2b 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Tetapkan apl nota lalai dalam Tetapan"</string>
<string name="install_app" msgid="5066668100199613936">"Pasang apl"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Paparkan pada paparan luaran?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Paparan dalaman anda akan dicerminkan. Paparan depan anda akan dimatikan."</string>
<string name="mirror_display" msgid="2515262008898122928">"Segerakkan paparan"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Ketepikan"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Paparan disambungkan"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index d3f1bad..25afad4 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"áááºáááºáá»á¬ážááœáẠáá°áááºážááŸááºá
á¯áá»á¬ážá¡ááºáẠáááºááŸááºáá«"</string>
<string name="install_app" msgid="5066668100199613936">"á¡ááºáẠááá·áºááœááºážáááº"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ááŒááºááááºáá¬ážááŒááºááá¯á· á
ááááºááœá¬ážááá¬ážá"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"á¡ááœááºážááŒááœááºááᯠá
ááááºááœá¬ážáá«áááºá ááŸá±á·áá»ááºááŸá¬ááŒááºááŒááœááºááᯠááááºáááºá"</string>
<string name="mirror_display" msgid="2515262008898122928">"áááºáá¬ážááŒááºááᯠá
ááááºááœá¬ážáááº"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"áááºáááº"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"áááºáá¬ážááŒááºááᯠáá»áááºáááºááá¯ááºáá«ááŒá®"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 234e725..d915743 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Du kan velge en standardapp for notater i Innstillinger"</string>
<string name="install_app" msgid="5066668100199613936">"Installer appen"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vil du speile til en ekstern skjerm?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Den indre skjermen speiles. Den ytre skjermen slås av."</string>
<string name="mirror_display" msgid="2515262008898122928">"Speil skjermen"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Lukk"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"En skjerm er koblet til"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 51839db..4abb4a4 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"à€žà¥à€à€¿à€à€®à€Ÿ à€à€ à€šà¥à€ à€¬à€šà€Ÿà€à€šà¥ à€¡à€¿à€«à€²à¥à€ à€à€ª à€€à¥à€à¥à€šà¥à€¹à¥à€žà¥"</string>
<string name="install_app" msgid="5066668100199613936">"à€à€ª à€à€šà¥à€žà¥à€à€² à€à€°à¥à€šà¥à€¹à¥à€žà¥"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"à€¬à€Ÿà€¹à¥à€¯ à€¡à€¿à€žà¥à€ªà¥à€²à¥à€®à€Ÿ à€®à€¿à€°à€° à€à€°à¥à€šà¥ à€¹à¥?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"à€€à€ªà€Ÿà€à€à€à¥ à€à€¿à€€à¥à€°à¥ à€¡à€¿à€žà¥à€ªà¥à€²à¥ à€®à€¿à€°à€° à€à€°à€¿à€šà¥ à€à¥€ à€€à€ªà€Ÿà€à€à€à¥ à€
à€à€Ÿà€¡à€¿à€à¥ à€¡à€¿à€žà¥à€ªà¥à€²à¥ à€
à€« à€à€°à€¿à€šà¥ à€à¥€"</string>
<string name="mirror_display" msgid="2515262008898122928">"à€¡à€¿à€žà¥à€ªà¥à€²à¥ à€®à€¿à€°à€° à€à€°à¥à€šà¥à€¹à¥à€žà¥"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"à€à€Ÿà€°à¥à€ à€à€°à¥à€šà¥à€¹à¥à€žà¥"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"à€¡à€¿à€žà¥à€ªà¥à€²à¥ à€à€šà¥à€à¥à€ à€à€°à€¿à€à€à¥ à€"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index f66b4e1..0576730 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Standaard notitie-app instellen in Instellingen"</string>
<string name="install_app" msgid="5066668100199613936">"App installeren"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Spiegelen naar extern scherm?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Het scherm aan de binnenkant wordt gemirrord. Het scherm aan de voorkant wordt uitgezet."</string>
<string name="mirror_display" msgid="2515262008898122928">"Scherm spiegelen"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Sluiten"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Scherm verbonden"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 0a45769..cb58e64 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ଞàà¬à¬¿à¬à¬žà¬°à ଡିଫଲàଠଚàà¬àଞ à¬à¬ª ଞàଠà¬à¬°à¬šà଀à"</string>
<string name="install_app" msgid="5066668100199613936">"à¬à¬ª à¬à¬šà¬·àà¬à¬² à¬à¬°à¬šà଀à"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"à¬à¬àଞà¬à¬°àଚଲ ଡିଞପàଲàà¬à ମିରର à¬à¬°à¬¿à¬¬à?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"à¬à¬ªà¬£à¬àଠà¬à¬šà¬° ଡିଞପàଲàà¬à ମିରର à¬à¬°à¬Ÿà¬¯à¬¿à¬¬à¥€ à¬à¬ªà¬£à¬àଠଫàରଣàଠଡିଞପàଲàà¬à ବଚàଊ à¬à¬°à¬Ÿà¬¯à¬¿à¬¬à¥€"</string>
<string name="mirror_display" msgid="2515262008898122928">"ଡିଞପàଲà ମିରର à¬à¬°à¬šà଀à"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"à¬à¬Ÿà¬°à¬ à¬à¬°à¬šà଀à"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"ଡିଞପàଲà à¬à¬šàà¬àଠà¬à¬°à¬Ÿà¬¯à¬Ÿà¬à¬à¬¿"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index c83659f..0340bd1 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"àšžà©àšàš¿à©°àšàšŸàš àšµàš¿à©±àš àšàšŸ àšà© àšªà©àš°àšµ-àššàš¿àš°àš§àšŸàš°àš¿àš€ àššà©àš àšàšª àššà©à©° àšžà©à©±àš àšàš°à©"</string>
<string name="install_app" msgid="5066668100199613936">"àšàšª àšžàš¥àšŸàšªàš€ àšàš°à©"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"àšà© àš¬àšŸàš¹àš°à© àš¡àš¿àšžàšªàš²à© \'àš€à© àšªà©àš°àš€àš¿àš¬àš¿à©°àš¬àš¿àš€ àšàš°àššàšŸ àš¹à©?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"àš€à©àš¹àšŸàš¡à© àš
à©°àšŠàš°à©àššà© àš¡àš¿àšžàšªàš²à© àšªà©àš°àš€à©àš¬àš¿à©°àš¬àš€ àšà©àš€à© àšàšŸàšµà©àšà©à¥€ àš€à©àš¹àšŸàš¡à© àš
àšàš²à© àš¡àš¿àšžàšªàš²à© àš¬à©°àšŠ àšàš° àšŠàš¿à©±àš€à© àšàšŸàšµà©àšà©à¥€"</string>
<string name="mirror_display" msgid="2515262008898122928">"àš¡àš¿àšžàšªàš²à© àššà©à©° àšªà©àš°àš€àš¿àš¬àš¿à©°àš¬àš¿àš€ àšàš°à©"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"àšàšŸàš°àš àšàš°à©"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"àš¡àš¿àšžàšªàš²à© àššà©à©° àšàššà©àšàš àšà©àš€àšŸ àšàš¿àš"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 91f2c78..aa24818 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ustaw domyÅlnÄ
aplikacjÄ do obsÅugi notatek w Ustawieniach"</string>
<string name="install_app" msgid="5066668100199613936">"Zainstaluj aplikacjÄ"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"PowieliÄ na wyÅwietlaczu zewnÄtrznym?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Powstanie odbicie lustrzane Twojego wewnÄtrznego ekranu. Przedni ekran zostanie wyÅÄ
czony."</string>
<string name="mirror_display" msgid="2515262008898122928">"Powielaj obraz"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Zamknij"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"WyÅwietlacz podÅÄ
czony"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 9fe372b..c65c56e 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Defina o app de notas padrão nas Configurações"</string>
<string name="install_app" msgid="5066668100199613936">"Instalar o app"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Espelhar para a tela externa?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Seu display interno será espelhado. O display frontal será desligado."</string>
<string name="mirror_display" msgid="2515262008898122928">"Espelhar tela"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Dispensar"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Tela conectada"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 3bf9c7b..0b9580d 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Predefina a app de notas nas Definições"</string>
<string name="install_app" msgid="5066668100199613936">"Instalar app"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Espelhar para o ecrã externo?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"O seu ecrã interior vai ser espelhado. O seu ecrã frontal vai ser desativado."</string>
<string name="mirror_display" msgid="2515262008898122928">"Espelhar ecrã"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Ignorar"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Ecrã ligado"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 9fe372b..c65c56e 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Defina o app de notas padrão nas Configurações"</string>
<string name="install_app" msgid="5066668100199613936">"Instalar o app"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Espelhar para a tela externa?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Seu display interno será espelhado. O display frontal será desligado."</string>
<string name="mirror_display" msgid="2515262008898122928">"Espelhar tela"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Dispensar"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Tela conectada"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 18aa668..b2fdef3 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"SeteazÄ aplicaÈia prestabilitÄ de note din SetÄri"</string>
<string name="install_app" msgid="5066668100199613936">"InstaleazÄ aplicaÈia"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"OglindeÈti pe ecranul extern?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Ecranul interior va fi oglindit. Ecranul frontal va fi dezactivat."</string>
<string name="mirror_display" msgid="2515262008898122928">"AfiÈare în oglindÄ"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Închide"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Ecran conectat"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 51192f8..ff4bd45 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ÐаЎайÑе ÑÑаМЎаÑÑМПе пÑОлПжеМОе ÐŽÐ»Ñ Ð·Ð°ÐŒÐµÑПк в МаÑÑÑПйкаÑ
."</string>
<string name="install_app" msgid="5066668100199613936">"УÑÑаМПвОÑÑ Ð¿ÑОлПжеМОе"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ÐÑблОÑПваÑÑ ÐœÐ° вМеÑМОй ЎОÑплей?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"ÐÐ»Ñ Ð²ÐœÑÑÑеММегП ÑкÑаМа вклÑÑОÑÑÑ ÐŽÑблОÑПваМОе. ÐеÑеЎМОй ÑкÑаМ бÑÐŽÐµÑ ÐŸÑклÑÑеМ."</string>
<string name="mirror_display" msgid="2515262008898122928">"ÐÑблОÑПваÑÑ"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"ÐакÑÑÑÑ"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"ÐкÑаМ пПЎклÑÑеМ"</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 7ade110..4b4d08b 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"à·à·à¶à·à·à¶žà· à¶à·à·
à¶Žà·à¶»à¶±à·à¶žà· à·à¶§à·à¶±à· යà·à¶¯à·à¶ž à·à¶à·à¶±à·à¶±"</string>
<string name="install_app" msgid="5066668100199613936">"යà·à¶¯à·à¶ž à·à·à¶®à·à¶Žà¶±à¶º à¶à¶»à¶±à·à¶±"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"à¶¶à·à·à·à¶» à·à¶à¶¯à¶»à·à·à¶à¶ºà¶§ දරà·à¶Žà¶«à¶º à¶à¶»à¶±à·à¶± ද?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"à¶à¶¶à· à¶
à¶·à·à¶ºà¶±à·à¶à¶» à·à¶à¶¯à¶»à·à·à¶à¶º à¶Žà·à·
à·à¶¶à·à¶¹à· à·à¶±à· à¶à¶. à¶à¶¶à· à¶à¶¯à·à¶»à·à¶Žà· à·à¶à¶¯à¶»à·à·à¶à¶º à¶à·à¶»à·à¶ºà·à·à·à¶»à·à·à¶ à·à¶±à· à¶à¶."</string>
<string name="mirror_display" msgid="2515262008898122928">"à·à¶à¶¯à¶»à·à·à¶à¶º දරà·à¶Žà¶«à¶º à¶à¶»à¶±à·à¶±"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"à¶
à·à· à¶à¶»à¶±à·à¶±"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"à·à¶à¶¯à¶»à·à·à¶à¶º à·à¶žà·à¶¶à¶±à·à¶° à¶à¶» à¶à¶"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 8d760d5..9e9507e 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Nastavte predvolenú aplikáciu na poznámky v Nastaveniach"</string>
<string name="install_app" msgid="5066668100199613936">"InštalovaÅ¥ aplikáciu"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Chcete zrkadliÅ¥ na externú obrazovku?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Vnútorná obrazovka bude zrkadlená. Predná obrazovka bude vypnutá."</string>
<string name="mirror_display" msgid="2515262008898122928">"Zrkadliť obrazovku"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Zavrieť"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Obrazovka je pripojená"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 4ae80ef..cba5416 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Nastavite privzeto aplikacijo za zapiske v nastavitvah."</string>
<string name="install_app" msgid="5066668100199613936">"Namesti aplikacijo"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Åœelite zrcaliti na zunanji zaslon?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Notranji zaslon bo zrcaljen. Sprednji zaslon bo izklopljen."</string>
<string name="mirror_display" msgid="2515262008898122928">"Zrcali zaslon"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Opusti"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Zaslon je povezan"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 8372703..b35668f 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Cakto aplikacionin e parazgjedhur të shënimeve te \"Cilësimet\""</string>
<string name="install_app" msgid="5066668100199613936">"Instalo aplikacionin"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Të pasqyrohet në ekranin e jashtëm?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Ekrani i brendshëm do të pasqyrohet. Ekrani i parmë do të çaktivizohet."</string>
<string name="mirror_display" msgid="2515262008898122928">"Pasqyro ekranin"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Hiq"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Ekrani është lidhur"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 1bda1db..1d45fbd 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ÐПЎеÑОÑе пПЎÑазÑÐŒÐµÐ²Ð°ÐœÑ Ð°Ð¿Ð»ÐžÐºÐ°ÑОÑÑ Ð·Ð° белеÑке Ñ ÐПЎеÑаваÑОЌа"</string>
<string name="install_app" msgid="5066668100199613936">"ÐМÑÑалОÑÐ°Ñ Ð°Ð¿Ð»ÐžÐºÐ°ÑОÑÑ"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ÐелОÑе лО Ўа пÑеÑлОкаÑе Ма ÑпПÑÑО екÑаМ?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"УМÑÑÑаÑÑО екÑаМ Ñе Ñе пÑеÑлОкаÑО. ÐÑеЎÑО екÑаМ Ñе Ñе ОÑкÑÑÑОÑО."</string>
<string name="mirror_display" msgid="2515262008898122928">"ÐÑеÑÐ»ÐžÐºÐ°Ñ ÐµÐºÑаМ"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"ÐЎбаÑО"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"ÐкÑаМ Ñе пПвезаМ"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 8cfafcf..0d6272f 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ställ in en standardapp för anteckningar i inställningarna"</string>
<string name="install_app" msgid="5066668100199613936">"Installera appen"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vill du spegla till extern skärm?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Den inre skärmen speglas. Den främre skärmen stängs av."</string>
<string name="mirror_display" msgid="2515262008898122928">"Spegla skärm"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Ignorera"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Skärm har anslutits"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 286e65a..1bd2ed7 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -252,7 +252,7 @@
<string name="quick_settings_bluetooth_label" msgid="7018763367142041481">"Bluetooth"</string>
<string name="quick_settings_bluetooth_detail_empty_text" msgid="5760239584390514322">"Hakuna vifaa vilivyooanishwa vinavyopatikana"</string>
<string name="quick_settings_bluetooth_tile_subtitle" msgid="212752719010829550">"Gusa ili uunganishe au utenganishe kifaa"</string>
- <string name="pair_new_bluetooth_devices" msgid="4601767620843349645">"Oanisha kifaa kipya"</string>
+ <string name="pair_new_bluetooth_devices" msgid="4601767620843349645">"Unganisha kifaa kipya"</string>
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Angalia vyote"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Tumia Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Imeunganishwa"</string>
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Teua programu chaguomsingi ya madokezo katika Mipangilio"</string>
<string name="install_app" msgid="5066668100199613936">"Sakinisha programu"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Ungependa kuonyesha kwenye skrini ya nje?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Mwonekano wa ndani wa kifaa chako utaakisiwa. Mwonekano wa mbele wa kifaa chako utazimwa."</string>
<string name="mirror_display" msgid="2515262008898122928">"Akisi skrini"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Ondoa"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Skrini imeunganishwa"</string>
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index 1f671ac..f1017d8 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -40,6 +40,9 @@
<!-- Whether to show bottom sheets edge to edge -->
<bool name="config_edgeToEdgeBottomSheetDialog">false</bool>
+ <!-- Flag to activate drag notification to contents feature -->
+ <bool name="config_notificationToContents">true</bool>
+
<!-- A collection of defaults for the quick affordances on the lock screen. Each item must be a
string with two parts: the ID of the slot and the comma-delimited list of affordance IDs,
separated by a colon ':' character. For example: <item>bottom_end:home,wallet</item>. The
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index aab713f..0cd076f 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"à®à¯à®±à®¿à®ªà¯à®ªà¯ à®à®à¯à®ªà¯à®ªà®€à®±à¯à®à®Ÿà®© à®à®¯à®²à¯à®ªà¯à®šà®¿à®²à¯ à®à®ªà¯à®žà¯ à®
à®®à¯à®ªà¯à®ªà¯à®à®³à®¿à®²à¯ à®
à®®à¯à®¯à¯à®à¯à®à®³à¯"</string>
<string name="install_app" msgid="5066668100199613936">"à®à®ªà¯à®žà¯ சிறà¯à®µà¯à®à¯à®à®³à¯"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"வà¯à®³à®¿à®ªà¯à®ªà¯à®±à®à¯ à®à®Ÿà®à¯à®à®¿à®à¯à®à¯ மிரர௠à®à¯à®¯à¯à®¯à®µà®Ÿ?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"à®à®à¯à®à®³à¯ à®à®à¯à®ªà¯à®± à®à®¿à®žà¯à®ªà®¿à®³à¯ பிர஀ிபலிà®à¯à®à®ªà¯à®ªà®à¯à®®à¯. à®à®à¯à®à®³à¯ à®®à¯à®©à¯à®ªà¯à®± à®à®¿à®žà¯à®ªà®¿à®³à¯ à®®à¯à®à®à¯à®à®ªà¯à®ªà®à¯à®®à¯."</string>
<string name="mirror_display" msgid="2515262008898122928">"à®à®¿à®žà¯à®ªà®¿à®³à¯à®¯à¯ மிரர௠à®à¯à®¯à¯"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"வà¯à®£à¯à®à®Ÿà®®à¯"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"à®à®¿à®žà¯à®ªà¯à®³à¯ à®à®£à¯à®à¯à®à®ªà¯à®ªà®à¯à®à®€à¯"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 2536a96..6a59812 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"à°žà±à°à±à°à°¿à°à°à±à°²à°²à± à°à°à±à°®à±à°à°¿à°à±à°à°Ÿ à°à°à°¡à±à°²à°Ÿ à°à° à°šà±à°à±à°žà± యటపà±à°šà± à°žà±à°à± à°à±à°žà±à°à±à°à°¡à°¿"</string>
<string name="install_app" msgid="5066668100199613936">"యటపà±à°šà± à°à°šà±à°žà±à°à°Ÿà°²à± à°à±à°¯à°à°¡à°¿"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"à°à°à±à°žà±à°à°°à±à°šà°²à± à°¡à°¿à°žà±à°ªà±à°²à±à°à°¿ మిరà±à°°à°°à± à°à±à°¯à°Ÿà°²à°Ÿ?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"మౠలà±à°ªà°²à°¿ à°¡à°¿à°žà±à°ªà±à°²à± మిరà±à°°à°°à± à°à±à°¯à°¬à°¡à±à°€à±à°à°Šà°¿. మౠమà±à°à°Šà± à°µà±à°ªà± à°¡à°¿à°žà±à°ªà±à°²à± à°à°«à± à°à±à°¯à°¬à°¡à±à°€à±à°à°Šà°¿."</string>
<string name="mirror_display" msgid="2515262008898122928">"మిరà±à°°à°°à± à°¡à°¿à°žà±à°ªà±à°²à±"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"విఞà±à°®à°°à°¿à°à°à°à°¡à°¿"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"à°¡à°¿à°žà±à°ªà±à°²à± à°à°šà±à°à±à°à± à°à±à°¯à°¬à°¡à°¿à°à°Šà°¿"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 3c96e30..dc4c6cf 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"àžàž³àž«àžàžà¹àžàžàžàž²àž£àžàžàžàž±àžàžàž¶àžà¹àž£àžŽà¹àž¡àžà¹àžà¹àžàžàž²àž£àžàž±à¹àžàžà¹àž²"</string>
<string name="install_app" msgid="5066668100199613936">"àžàžŽàžàžàž±à¹àžà¹àžàž"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"àž¡àžŽà¹àž£àžàž£à¹à¹àžàž¢àž±àžàžàžà¹àžªàžàžàžàž¥àž àž²àž¢àžàžàžà¹àž«àž¡"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"àž£àž°àžàžàžàž°àž¡àžŽà¹àž£àžàž£à¹àž«àžà¹àž²àžàžàžà¹àž²àžà¹àž à¹àž¥àž°àžàž°àžàžŽàžàž«àžà¹àž²àžàžàžà¹àž²àžàž«àžà¹àž²"</string>
<string name="mirror_display" msgid="2515262008898122928">"àž¡àžŽà¹àž£àžàž£à¹àžàžà¹àžªàžàžàžàž¥"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"àžàžŽàž"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"à¹àžàž·à¹àžàž¡àžà¹àžàžàžà¹àžªàžàžàžàž¥à¹àž¥à¹àž§"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 8fc5b6b..c09ac97 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Magtakda ng default na app sa pagtatala sa Mga Setting"</string>
<string name="install_app" msgid="5066668100199613936">"I-install ang app"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"I-mirror sa external na display?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Imi-mirror ang inner display mo. Io-off ang iyong front display."</string>
<string name="mirror_display" msgid="2515262008898122928">"I-mirror ang display"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"I-dismiss"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Naikonekta ang display"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index a4083ef..ee1909b 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ayarlar\'ı kullanarak varsayılan notlar ayarlayın"</string>
<string name="install_app" msgid="5066668100199613936">"Uygulamayı yükle"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Harici ekrana yansıtılsın mı?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"İç ekranınız yansıtılacak. Ön ekranınız kapatılacak."</string>
<string name="mirror_display" msgid="2515262008898122928">"Ekranı yansıt"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Kapat"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Ekran baÄlandı"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 5e0f2c2..04a97bc 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ÐÑОзМаÑÑе ÑÑаМЎаÑÑМОй ЎПЎаÑПк ÐŽÐ»Ñ ÐœÐŸÑаÑПк Ñ ÐœÐ°Ð»Ð°ÑÑÑваММÑÑ
"</string>
<string name="install_app" msgid="5066668100199613936">"УÑÑаМПвОÑО ЎПЎаÑПк"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ÐÑблÑваÑО Ма зПвМÑÑМÑÐŸÐŒÑ ÐµÐºÑаМÑ?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"ÐÐ°Ñ Ð²ÐœÑÑÑÑÑМÑй екÑаМ бÑЎе пÑПЎÑблÑПваМП. ÐеÑеЎМÑй екÑаМ бÑЎе вОЌкМеМП."</string>
<string name="mirror_display" msgid="2515262008898122928">"ÐÑблÑваÑО екÑаМ"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"ÐакÑОÑО"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"ÐОÑплей пÑÐŽ’ÑЎМаМП"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index c28e064..fd984b9 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ØªØ±ØªÛØšØ§Øª Ù
ÛÚº ÚÛÙØ§ÙÙ¹ ÙÙٹس اÛÙŸ سÛÙ¹ کرÛÚº"</string>
<string name="install_app" msgid="5066668100199613936">"اÛÙŸ Ø§ÙØ³Ù¹Ø§Ù کرÛÚº"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ØšÛØ±ÙÙÛ ÚØ³ÙŸÙÛ ÙŸØ± Ù
رر کرÛÚºØ"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"آٟ Ú©Û Ø§ÙØ¯Ø±ÙÙÛ ÚØ³ÙŸÙÛ Ú©Ù Ø¯Ù Ø·Ø±ÙÛ Ù
Ø·Ø§ØšÙØª ÙŸØ°ÛØ± ØšÙØ§Ûا Ø¬Ø§ØŠÛ Ú¯Ø§Û Ø¢ÙŸ کا ÙØ±ÙÙ¹ ÚØ³ÙŸÙÛ Ø¢Ù ÛÙ Ø¬Ø§ØŠÛ Ú¯Ø§Û"</string>
<string name="mirror_display" msgid="2515262008898122928">"ÚØ³ÙŸÙÛ Ú©Ù Ù
رر کرÛÚº"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"ؚرخاست کرÛÚº"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"ÚØ³ÙŸÙÛ Ù
ÙØ³ÙÚ© ÛÛ"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index e032c76..b9a9832 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Standart qaydlar ilovasini Sozlamalar orqali tanlang"</string>
<string name="install_app" msgid="5066668100199613936">"Ilovani oʻrnatish"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Tashqi displeyda aks ettirilsinmi?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Ichki ekran uchun aks ettirish yoqiladi. Old ekran oʻchiriladi."</string>
<string name="mirror_display" msgid="2515262008898122928">"Displeyni aks ettirish"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Yopish"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Displey ulandi"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index b87619e..b1ff9a8 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Äặt ứng dụng ghi chú mặc Äá»nh trong phần Cài Äặt"</string>
<string name="install_app" msgid="5066668100199613936">"Cài Äặt ứng dụng"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Phản chiếu sang màn hình ngoài?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Màn hình trong cá»§a bạn sẜ ÄÆ°á»£c phản chiếu. Màn hình ngoài cá»§a bạn sẜ tắt."</string>
<string name="mirror_display" msgid="2515262008898122928">"Phản chiếu màn hình"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Äóng"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Äã kết ná»i màn hình"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 43dfbe8..237fd57 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"åšè®Ÿçœ®äžè®Ÿçœ®é»è®€è®°äºåºçš"</string>
<string name="install_app" msgid="5066668100199613936">"å®è£
åºçš"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"éåå°å€æ¥æŸç€ºå±ïŒ"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"ç³»ç»å°éåæšçå
å±ïŒèå
³éå€å±ã"</string>
<string name="mirror_display" msgid="2515262008898122928">"éåå°æŸç€ºå±"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"å
³é"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"æŸç€ºå±å·²è¿æ¥"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 784f667..313af30 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"åšãèšå®ãäžæå®é èšçèšæçšçšåŒ"</string>
<string name="install_app" msgid="5066668100199613936">"å®è£æçšçšåŒ"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"èŠé¡åæå°è³å€éšé¡¯ç€ºå±åïŒ"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"é¡åç«é¢å°é¡¯ç€ºåšå
§éšè¢å¹ïŒåæ¹è¢å¹åæééã"</string>
<string name="mirror_display" msgid="2515262008898122928">"é¡å顯瀺"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"éé"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"已飿¥é¡¯ç€ºå±"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 15da239..6a13d3d 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"åšãèšå®ãäžæå®é èšèšäºæçšçšåŒ"</string>
<string name="install_app" msgid="5066668100199613936">"å®è£æçšçšåŒ"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"èŠä»¥é¡åæ¹åŒææŸè³å€éšè¢å¹åïŒ"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"é¡åç«é¢å°é¡¯ç€ºåšå
§è¢å¹ïŒå°é¢è¢å¹åæééã"</string>
<string name="mirror_display" msgid="2515262008898122928">"é¡å顯瀺"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"éé"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"è¢å¹å·²é£çµ"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 31cdca9..23862a7 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -1208,8 +1208,7 @@
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Setha i-app yamanothi azenzakalelayo Kumsethingi"</string>
<string name="install_app" msgid="5066668100199613936">"Faka i-app"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Fanisa nesibonisi sangaphandle?"</string>
- <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
- <skip />
+ <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Isibonisi sakho sangaphakathi sizoboniswa. Isibonisi sakho sangaphambili sizovalwa."</string>
<string name="mirror_display" msgid="2515262008898122928">"Isibonisi sokufanisa"</string>
<string name="dismiss_dialog" msgid="2195508495854675882">"Chitha"</string>
<string name="connected_display_icon_desc" msgid="6373560639989971997">"Isibonisi sixhunyiwe"</string>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 5c362b2..64a1d24 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -538,15 +538,15 @@
-->
<string translatable="false" name="config_frontBuiltInDisplayCutoutProtection"></string>
- <!-- ID for the camera of outer display that needs extra protection -->
+ <!-- ID for the camera of outer display that needs extra protection -->
<string translatable="false" name="config_protectedCameraId"></string>
- <!-- Physical ID for the camera of outer display that needs extra protection -->
+ <!-- Physical ID for the camera of outer display that needs extra protection -->
<string translatable="false" name="config_protectedPhysicalCameraId"></string>
<!-- Similar to config_frontBuiltInDisplayCutoutProtection but for inner display. -->
<string translatable="false" name="config_innerBuiltInDisplayCutoutProtection"></string>
- <!-- ID for the camera of inner display that needs extra protection -->
+ <!-- ID for the camera of inner display that needs extra protection. -->
<string translatable="false" name="config_protectedInnerCameraId"></string>
<!-- Physical ID for the camera of inner display that needs extra protection -->
<string translatable="false" name="config_protectedInnerPhysicalCameraId"></string>
@@ -650,13 +650,20 @@
<!-- Whether to use window background blur for the volume dialog. -->
<bool name="config_volumeDialogUseBackgroundBlur">false</bool>
- <!-- The properties of the face auth camera in pixels -->
+ <!-- The properties of the face auth front camera for outer display in pixels -->
<integer-array name="config_face_auth_props">
<!-- sensorLocationX -->
<!-- sensorLocationY -->
<!--sensorRadius -->
</integer-array>
+ <!-- The properties of the face auth front camera for inner display in pixels -->
+ <integer-array name="config_inner_face_auth_props">
+ <!-- sensorLocationX -->
+ <!-- sensorLocationY -->
+ <!--sensorRadius -->
+ </integer-array>
+
<!-- Overrides the behavior of the face unlock keyguard bypass setting:
0 - Don't override the setting (default)
1 - Override the setting to always bypass keyguard
@@ -962,6 +969,9 @@
<!-- Whether to show bottom sheets edge to edge -->
<bool name="config_edgeToEdgeBottomSheetDialog">true</bool>
+ <!-- Device specific config that controls whether rest to unlock feature is supported. -->
+ <bool name="config_restToUnlockSupported">false</bool>
+
<!--
Time in milliseconds the user has to touch the side FPS sensor to successfully authenticate when
the screen is turned off with AOD not enabled.
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index ee89ede..90d8cdb 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -608,6 +608,11 @@
<dimen name="volume_panel_slice_vertical_padding">8dp</dimen>
<dimen name="volume_panel_slice_horizontal_padding">24dp</dimen>
+ <dimen name="volume_panel_corner_radius">52dp</dimen>
+ <dimen name="volume_panel_content_padding">24dp</dimen>
+ <dimen name="volume_panel_bottom_bar_horizontal_padding">24dp</dimen>
+ <dimen name="volume_panel_bottom_bar_bottom_padding">20dp</dimen>
+
<!-- Size of each item in the ringer selector drawer. -->
<dimen name="volume_ringer_drawer_item_size">42dp</dimen>
<dimen name="volume_ringer_drawer_item_size_half">21dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index e7eb984..3f11fae 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1079,6 +1079,14 @@
<!-- Description for the button that opens the widget editor on click. [CHAR LIMIT=50] -->
<string name="button_to_open_widget_editor">Open the widget editor</string>
+ <!-- Text for CTA button that launches the hub mode widget editor on click. [CHAR LIMIT=50] -->
+ <string name="cta_tile_button_to_open_widget_editor">Customize</string>
+ <!-- Text for CTA button that dismisses the tile on click. [CHAR LIMIT=50] -->
+ <string name="cta_tile_button_to_dismiss">Dismiss</string>
+ <!-- Label for CTA tile to edit the glanceable hub [CHAR LIMIT=100] -->
+ <string name="cta_label_to_edit_widget">Add, remove, and reorder your widgets in this space</string>
+ <!-- Label for CTA tile that opens widget picker on click in edit mode [CHAR LIMIT=50] -->
+ <string name="cta_label_to_open_widget_picker">Add more widgets</string>
<!-- Description for the button that removes a widget on click. [CHAR LIMIT=50] -->
<string name="button_to_remove_widget">Remove</string>
<!-- Text for the button that launches the hub mode widget picker. [CHAR LIMIT=50] -->
@@ -3262,6 +3270,9 @@
<!-- Label for a button that, when clicked, sends the user to the app store to install an app. [CHAR LIMIT=64]. -->
<string name="install_app">Install app</string>
+ <!-- Instructions informing the user they can swipe up on the lockscreen to dismiss [CHAR LIMIT=48]-->
+ <string name="dismissible_keyguard_swipe">Swipe to continue</string>
+
<!--- Title of the dialog appearing when an external display is connected, asking whether to start mirroring [CHAR LIMIT=NONE]-->
<string name="connected_display_dialog_start_mirroring">Mirror to external display?</string>
<!--- Body of the mirroring dialog, shown when dual display is enabled. This signals that enabling mirroring will stop concurrent displays on a foldable device. [CHAR LIMIT=NONE]-->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index c48dd9d..3f026a4 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -930,6 +930,15 @@
<item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item>
</style>
+ <style name="Theme.VolumePanelActivity" parent="@style/Theme.SystemUI">
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:windowActionBar">false</item>
+ <item name="android:windowNoTitle">true</item>
+ <!-- Setting a placeholder will avoid using the SystemUI icon on the splash screen. -->
+ <item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_blank</item>
+ </style>
+
<style name="Theme.UserSwitcherFullscreenDialog" parent="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen">
<item name="android:statusBarColor">@color/user_switcher_fullscreen_bg</item>
<item name="android:windowBackground">@color/user_switcher_fullscreen_bg</item>
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 2b41178..05106c9 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -51,8 +51,8 @@
],
static_libs: [
"BiometricsSharedLib",
+ "PlatformAnimationLib",
"PluginCoreLib",
- "SystemUIAnimationLib",
"SystemUIPluginLib",
"SystemUIUnfoldLib",
"SystemUISharedLib-Keyguard",
@@ -66,6 +66,7 @@
"kotlinx_coroutines",
"dagger2",
"jsr330",
+ "com_android_systemui_shared_flags_lib",
],
resource_dirs: [
"res",
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/UdfpsOverlayParams.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/UdfpsOverlayParams.kt
index 65c5a49..e31fb89 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/UdfpsOverlayParams.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/UdfpsOverlayParams.kt
@@ -17,6 +17,8 @@
package com.android.systemui.biometrics.shared.model
import android.graphics.Rect
+import android.hardware.fingerprint.FingerprintSensorProperties.SensorType
+import android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL
import android.view.Surface
import android.view.Surface.Rotation
@@ -39,6 +41,8 @@
* the native resolution.
*
* [rotation] current rotation of the display.
+ *
+ * [sensorType] fingerprint sensor type
*/
data class UdfpsOverlayParams(
val sensorBounds: Rect = Rect(),
@@ -46,7 +50,8 @@
val naturalDisplayWidth: Int = 0,
val naturalDisplayHeight: Int = 0,
val scaleFactor: Float = 1f,
- @Rotation val rotation: Int = Surface.ROTATION_0
+ @Rotation val rotation: Int = Surface.ROTATION_0,
+ @SensorType val sensorType: Int = TYPE_UDFPS_OPTICAL
) {
/** Same as [sensorBounds], but in native resolution. */
diff --git a/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/KeyButtonRipple.java
similarity index 98%
rename from packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
rename to packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/KeyButtonRipple.java
index f005af3..92473e8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/KeyButtonRipple.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.navigationbar.buttons;
+package com.android.systemui.shared.navigationbar;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -125,7 +125,7 @@
/**
* @param onInvisibleRunnable run after we are next drawn invisibly. Only used once.
*/
- void setOnInvisibleRunnable(Runnable onInvisibleRunnable) {
+ public void setOnInvisibleRunnable(Runnable onInvisibleRunnable) {
mOnInvisibleRunnable = onInvisibleRunnable;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java
index a4b6451..2145166 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java
@@ -30,7 +30,7 @@
import androidx.annotation.DimenRes;
-import com.android.systemui.navigationbar.buttons.KeyButtonRipple;
+import com.android.systemui.shared.navigationbar.KeyButtonRipple;
public class FloatingRotationButtonView extends ImageView {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 131eb6b..c08b083 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -20,10 +20,11 @@
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
+import static com.android.systemui.shared.Flags.shadeAllowBackGesture;
+
import android.annotation.IntDef;
import android.content.Context;
import android.content.res.Resources;
-import android.os.SystemProperties;
import android.view.ViewConfiguration;
import android.view.WindowManagerPolicyConstants;
@@ -132,8 +133,7 @@
SYSUI_STATE_WAKEFULNESS_TRANSITION | SYSUI_STATE_AWAKE;
// Whether the back gesture is allowed (or ignored) by the Shade
- public static final boolean ALLOW_BACK_GESTURE_IN_SHADE = SystemProperties.getBoolean(
- "persist.wm.debug.shade_allow_back_gesture", false);
+ public static final boolean ALLOW_BACK_GESTURE_IN_SHADE = shadeAllowBackGesture();
@Retention(RetentionPolicy.SOURCE)
@IntDef({SYSUI_STATE_SCREEN_PINNING,
diff --git a/packages/SystemUI/shared/src/com/android/systemui/statusbar/policy/CallbackController.java b/packages/SystemUI/shared/src/com/android/systemui/statusbar/policy/CallbackController.java
index 047ff75..9f82201 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/statusbar/policy/CallbackController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/statusbar/policy/CallbackController.java
@@ -21,6 +21,11 @@
import androidx.lifecycle.LifecycleEventObserver;
import androidx.lifecycle.LifecycleOwner;
+/**
+ * Implementation of the collection used and thread guarantees are left to the discretion of the
+ * client. Consider using {@link com.android.systemui.util.ListenerSet} to prevent concurrent
+ * modification exceptions.
+ */
public interface CallbackController<T> {
/** Add a callback */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index a8bf229..05eeac6f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -133,7 +133,8 @@
static final int BOUNCER_DISMISS_EXTENDED_ACCESS = 3;
// Bouncer is dismissed due to sim card unlock code entered.
static final int BOUNCER_DISMISS_SIM = 4;
-
+ // Bouncer dismissed after being allowed to dismiss by forceDismissiblekeyguard
+ static final int BOUNCER_DISMISSIBLE_KEYGUARD = 5;
private static final String TAG = "KeyguardSecurityView";
// Make the view move slower than the finger, as if the spring were applying force.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 5e35e77..e457ca1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -21,6 +21,7 @@
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_EXTENDED_ACCESS;
+import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISSIBLE_KEYGUARD;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_NONE_SECURITY;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_PASSWORD;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_SIM;
@@ -78,10 +79,10 @@
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.classifier.FalsingA11yDelegate;
import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.ActivityStarter;
@@ -135,7 +136,7 @@
private final SessionTracker mSessionTracker;
private final Optional<SideFpsController> mSideFpsController;
private final FalsingA11yDelegate mFalsingA11yDelegate;
- private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
+ private final DeviceEntryFaceAuthInteractor mDeviceEntryFaceAuthInteractor;
private final BouncerMessageInteractor mBouncerMessageInteractor;
private int mTranslationY;
private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@@ -216,7 +217,7 @@
@Override
public void onUserInput() {
mBouncerMessageInteractor.onPrimaryBouncerUserInput();
- mKeyguardFaceAuthInteractor.onPrimaryBouncerUserInput();
+ mDeviceEntryFaceAuthInteractor.onPrimaryBouncerUserInput();
}
@Override
@@ -347,11 +348,11 @@
private final SwipeListener mSwipeListener = new SwipeListener() {
@Override
public void onSwipeUp() {
- if (mKeyguardFaceAuthInteractor.canFaceAuthRun()) {
+ if (mDeviceEntryFaceAuthInteractor.canFaceAuthRun()) {
mKeyguardSecurityCallback.userActivity();
}
- mKeyguardFaceAuthInteractor.onSwipeUpOnBouncer();
- if (mKeyguardFaceAuthInteractor.isFaceAuthEnabledAndEnrolled()) {
+ mDeviceEntryFaceAuthInteractor.onSwipeUpOnBouncer();
+ if (mDeviceEntryFaceAuthInteractor.isFaceAuthEnabledAndEnrolled()) {
mUpdateMonitor.requestActiveUnlock(
ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT,
"swipeUpOnBouncer");
@@ -456,7 +457,7 @@
TelephonyManager telephonyManager,
ViewMediatorCallback viewMediatorCallback,
AudioManager audioManager,
- KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
+ DeviceEntryFaceAuthInteractor deviceEntryFaceAuthInteractor,
BouncerMessageInteractor bouncerMessageInteractor,
Provider<JavaAdapter> javaAdapter,
SelectedUserInteractor selectedUserInteractor,
@@ -495,7 +496,7 @@
mTelephonyManager = telephonyManager;
mViewMediatorCallback = viewMediatorCallback;
mAudioManager = audioManager;
- mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
+ mDeviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor;
mBouncerMessageInteractor = bouncerMessageInteractor;
mSelectedUserInteractor = selectedUserInteractor;
mDeviceEntryInteractor = deviceEntryInteractor;
@@ -862,7 +863,11 @@
boolean finish = false;
int eventSubtype = -1;
BouncerUiEvent uiEvent = BouncerUiEvent.UNKNOWN;
- if (mUpdateMonitor.getUserHasTrust(targetUserId)) {
+ if (mUpdateMonitor.forceIsDismissibleIsKeepingDeviceUnlocked()) {
+ finish = true;
+ eventSubtype = BOUNCER_DISMISSIBLE_KEYGUARD;
+ // TODO: b/308417021 add UI event
+ } else if (mUpdateMonitor.getUserHasTrust(targetUserId)) {
finish = true;
eventSubtype = BOUNCER_DISMISS_EXTENDED_ACCESS;
uiEvent = BouncerUiEvent.BOUNCER_DISMISS_EXTENDED_ACCESS;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index f3cd01f..fe96099 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -102,6 +102,7 @@
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.foldables.FoldGracePeriodProvider;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.InstanceId;
import com.android.internal.logging.UiEventLogger;
@@ -113,24 +114,26 @@
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.CoreStartable;
import com.android.systemui.Dumpable;
+import com.android.systemui.Flags;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfig;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
+import com.android.systemui.deviceentry.domain.interactor.FaceAuthenticationListener;
+import com.android.systemui.deviceentry.shared.model.AcquiredFaceAuthenticationStatus;
+import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus;
+import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus;
+import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus;
+import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus;
+import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus;
+import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.dump.DumpsysTableLogger;
-import com.android.systemui.keyguard.domain.interactor.FaceAuthenticationListener;
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.keyguard.shared.constants.TrustAgentUiEvent;
-import com.android.systemui.keyguard.shared.model.AcquiredFaceAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.FaceDetectionStatus;
-import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.clocks.WeatherData;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -217,7 +220,8 @@
private static final int MSG_BIOMETRIC_ENROLLMENT_STATE_CHANGED = 348;
/** Biometric authentication state: Not listening. */
- private static final int BIOMETRIC_STATE_STOPPED = 0;
+ @VisibleForTesting
+ protected static final int BIOMETRIC_STATE_STOPPED = 0;
/** Biometric authentication state: Listening. */
private static final int BIOMETRIC_STATE_RUNNING = 1;
@@ -305,6 +309,10 @@
private boolean mKeyguardOccluded;
private boolean mCredentialAttempted;
private boolean mKeyguardGoingAway;
+ /**
+ * Whether the keyguard is forced into a dismissible state.
+ */
+ private boolean mForceIsDismissible;
private boolean mGoingToSleep;
private boolean mPrimaryBouncerFullyShown;
private boolean mPrimaryBouncerIsOrWillBeShowing;
@@ -358,7 +366,10 @@
@Nullable
private final BiometricManager mBiometricManager;
@Nullable
- private KeyguardFaceAuthInteractor mFaceAuthInteractor;
+ private DeviceEntryFaceAuthInteractor mFaceAuthInteractor;
+ @VisibleForTesting
+ protected FoldGracePeriodProvider mFoldGracePeriodProvider =
+ new FoldGracePeriodProvider();
private final DevicePostureController mDevicePostureController;
private final TaskStackChangeListeners mTaskStackChangeListeners;
private final IActivityTaskManager mActivityTaskManager;
@@ -372,6 +383,7 @@
private List<SubscriptionInfo> mSubscriptionInfo;
@VisibleForTesting
protected int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
+ private boolean mFingerprintDetectRunning;
private boolean mIsDreaming;
private boolean mLogoutEnabled;
private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -724,6 +736,14 @@
}
/**
+ * Updates KeyguardUpdateMonitor's internal state to know the device should remain unlocked
+ * until the next signal to lock. Does nothing if the keyguard is already showing.
+ */
+ public void tryForceIsDismissibleKeyguard() {
+ setForceIsDismissibleKeyguard(true);
+ }
+
+ /**
* Updates KeyguardUpdateMonitor's internal state to know if keyguard is going away.
*/
public void setKeyguardGoingAway(boolean goingAway) {
@@ -985,6 +1005,7 @@
final boolean wasCancellingRestarting = mFingerprintRunningState
== BIOMETRIC_STATE_CANCELLING_RESTARTING;
mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
+ mFingerprintDetectRunning = false;
if (wasCancellingRestarting) {
KeyguardUpdateMonitor.this.updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
} else {
@@ -1093,6 +1114,9 @@
boolean wasRunning = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING;
boolean isRunning = fingerprintRunningState == BIOMETRIC_STATE_RUNNING;
mFingerprintRunningState = fingerprintRunningState;
+ if (mFingerprintRunningState == BIOMETRIC_STATE_STOPPED) {
+ mFingerprintDetectRunning = false;
+ }
mLogger.logFingerprintRunningState(mFingerprintRunningState);
// Clients of KeyguardUpdateMonitor don't care about the internal state about the
// asynchronousness of the cancel cycle. So only notify them if the actually running state
@@ -1265,14 +1289,14 @@
return getFaceAuthInteractor() != null && getFaceAuthInteractor().isRunning();
}
- private @Nullable KeyguardFaceAuthInteractor getFaceAuthInteractor() {
+ private @Nullable DeviceEntryFaceAuthInteractor getFaceAuthInteractor() {
return mFaceAuthInteractor;
}
/**
* Set the face auth interactor that should be used for initiating face authentication.
*/
- public void setFaceAuthInteractor(KeyguardFaceAuthInteractor faceAuthInteractor) {
+ public void setFaceAuthInteractor(DeviceEntryFaceAuthInteractor faceAuthInteractor) {
if (mFaceAuthInteractor != null) {
mFaceAuthInteractor.unregisterListener(mFaceAuthenticationListener);
}
@@ -1355,7 +1379,7 @@
* @return whether the current user has been authenticated with face. This may be true
* on the lockscreen if the user doesn't have bypass enabled.
*
- * @deprecated Use {@link KeyguardFaceAuthInteractor#isAuthenticated()}
+ * @deprecated Use {@link DeviceEntryFaceAuthInteractor#isAuthenticated()}
*/
@Deprecated
public boolean getIsFaceAuthenticated() {
@@ -1363,7 +1387,18 @@
}
public boolean getUserCanSkipBouncer(int userId) {
- return getUserHasTrust(userId) || getUserUnlockedWithBiometric(userId);
+ return getUserHasTrust(userId) || getUserUnlockedWithBiometric(userId)
+ || forceIsDismissibleIsKeepingDeviceUnlocked();
+ }
+
+ /**
+ * Whether the keyguard should be kept unlocked for the folding grace period.
+ */
+ public boolean forceIsDismissibleIsKeepingDeviceUnlocked() {
+ if (mFoldGracePeriodProvider.isEnabled()) {
+ return mForceIsDismissible && isUnlockingWithForceKeyguardDismissibleAllowed();
+ }
+ return false;
}
public boolean getUserHasTrust(int userId) {
@@ -1386,7 +1421,7 @@
/**
* Returns whether the user is unlocked with face.
- * @deprecated Use {@link KeyguardFaceAuthInteractor#isAuthenticated()} instead
+ * @deprecated Use {@link DeviceEntryFaceAuthInteractor#isAuthenticated()} instead
*/
@Deprecated
public boolean isCurrentUserUnlockedWithFace() {
@@ -1467,6 +1502,10 @@
return isUnlockingWithBiometricAllowed(true);
}
+ private boolean isUnlockingWithForceKeyguardDismissibleAllowed() {
+ return isUnlockingWithBiometricAllowed(false);
+ }
+
public boolean isUnlockingWithBiometricAllowed(boolean isStrongBiometric) {
// StrongAuthTracker#isUnlockingWithBiometricAllowed includes
// STRONG_AUTH_REQUIRED_AFTER_LOCKOUT which is the same as mFingerprintLockedOutPermanent;
@@ -1803,6 +1842,7 @@
public void onFingerprintDetected(int sensorId, int userId,
boolean isStrongBiometric) {
handleBiometricDetected(userId, FINGERPRINT, isStrongBiometric);
+ setFingerprintRunningState(BIOMETRIC_STATE_STOPPED);
}
};
@@ -1977,6 +2017,7 @@
protected void handleStartedGoingToSleep(int arg1) {
Assert.isMainThread();
+ setForceIsDismissibleKeyguard(false);
clearFingerprintRecognized();
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -2064,6 +2105,7 @@
@VisibleForTesting
void resetBiometricListeningState() {
mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
+ mFingerprintDetectRunning = false;
}
@VisibleForTesting
@@ -2455,7 +2497,7 @@
/**
* @return true if there's at least one face enrolled
- * @deprecated Use {@link KeyguardFaceAuthInteractor#isFaceAuthEnabledAndEnrolled()}
+ * @deprecated Use {@link DeviceEntryFaceAuthInteractor#isFaceAuthEnabledAndEnrolled()}
*/
@Deprecated
public boolean isFaceEnabledAndEnrolled() {
@@ -2502,8 +2544,10 @@
return;
}
final boolean shouldListenForFingerprint = shouldListenForFingerprint(isUdfpsSupported());
- final boolean runningOrRestarting = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING
+ final boolean running = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING;
+ final boolean runningOrRestarting = running
|| mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING;
+ final boolean runDetect = shouldRunFingerprintDetect();
if (runningOrRestarting && !shouldListenForFingerprint) {
if (action == BIOMETRIC_ACTION_START) {
mLogger.v("Ignoring stopListeningForFingerprint()");
@@ -2515,10 +2559,24 @@
mLogger.v("Ignoring startListeningForFingerprint()");
return;
}
- startListeningForFingerprint();
+ startListeningForFingerprint(runDetect);
+ } else if (running && runDetect && !mFingerprintDetectRunning) {
+ if (action == BIOMETRIC_ACTION_STOP) {
+ mLogger.v("Ignoring startListeningForFingerprint(detect)");
+ return;
+ }
+ // stop running authentication and start running fingerprint detection
+ stopListeningForFingerprint();
+ startListeningForFingerprint(true);
}
}
+ private boolean shouldRunFingerprintDetect() {
+ return !isUnlockingWithFingerprintAllowed()
+ || (Flags.runFingerprintDetectOnDismissibleKeyguard()
+ && getUserCanSkipBouncer(mSelectedUserInteractor.getSelectedUserId()));
+ }
+
/**
* If a user is encrypted or not.
* This is NOT related to the lock screen being visible or not.
@@ -2774,7 +2832,6 @@
&& biometricEnabledForUser
&& !isUserInLockdown(user);
final boolean strongerAuthRequired = !isUnlockingWithFingerprintAllowed();
- final boolean isSideFps = isSfpsSupported() && isSfpsEnrolled();
final boolean shouldListenBouncerState =
!strongerAuthRequired || !mPrimaryBouncerIsOrWillBeShowing;
@@ -2820,7 +2877,7 @@
/**
* If face auth is allows to scan on this exact moment.
*
- * @deprecated Use {@link KeyguardFaceAuthInteractor#canFaceAuthRun()}
+ * @deprecated Use {@link DeviceEntryFaceAuthInteractor#canFaceAuthRun()}
*/
@Deprecated
public boolean shouldListenForFace() {
@@ -2837,7 +2894,7 @@
}
}
- private void startListeningForFingerprint() {
+ private void startListeningForFingerprint(boolean runDetect) {
final int userId = mSelectedUserInteractor.getSelectedUserId();
final boolean unlockPossible = isUnlockWithFingerprintPossible(userId);
if (mFingerprintCancelSignal != null) {
@@ -2867,18 +2924,20 @@
mFingerprintInteractiveToAuthProvider.getVendorExtension(userId));
}
- if (!isUnlockingWithFingerprintAllowed()) {
+ if (runDetect) {
mLogger.v("startListeningForFingerprint - detect");
mFpm.detectFingerprint(
mFingerprintCancelSignal,
mFingerprintDetectionCallback,
fingerprintAuthenticateOptions);
+ mFingerprintDetectRunning = true;
} else {
mLogger.v("startListeningForFingerprint");
mFpm.authenticate(null /* crypto */, mFingerprintCancelSignal,
mFingerprintAuthenticationCallback,
null /* handler */,
fingerprintAuthenticateOptions);
+ mFingerprintDetectRunning = false;
}
setFingerprintRunningState(BIOMETRIC_STATE_RUNNING);
}
@@ -2889,7 +2948,7 @@
}
/**
- * @deprecated Use {@link KeyguardFaceAuthInteractor#isLockedOut()}
+ * @deprecated Use {@link DeviceEntryFaceAuthInteractor#isLockedOut()}
*/
@Deprecated
public boolean isFaceLockedOut() {
@@ -2930,7 +2989,7 @@
}
/**
- * @deprecated Use {@link KeyguardFaceAuthInteractor#isFaceAuthEnabledAndEnrolled()}
+ * @deprecated Use {@link DeviceEntryFaceAuthInteractor#isFaceAuthEnabledAndEnrolled()}
*/
@VisibleForTesting
@Deprecated
@@ -3031,6 +3090,7 @@
void handleUserSwitching(int userId, Runnable resultCallback) {
mLogger.logUserSwitching(userId, "from UserTracker");
Assert.isMainThread();
+ setForceIsDismissibleKeyguard(false);
clearFingerprintRecognized();
boolean trustUsuallyManaged = mTrustManager.isTrustUsuallyManaged(userId);
mLogger.logTrustUsuallyManagedUpdated(userId, mUserTrustIsUsuallyManaged.get(userId),
@@ -3609,6 +3669,31 @@
}
}
+ private void setForceIsDismissibleKeyguard(boolean forceIsDismissible) {
+ Assert.isMainThread();
+ if (!mFoldGracePeriodProvider.isEnabled()) {
+ // never send updates if the feature isn't enabled
+ return;
+ }
+ if (mKeyguardShowing && forceIsDismissible) {
+ // never keep the device unlocked if the keyguard was already showing
+ mLogger.d("Skip setting forceIsDismissibleKeyguard to true. "
+ + "Keyguard already showing.");
+ return;
+ }
+ if (mForceIsDismissible != forceIsDismissible) {
+ mForceIsDismissible = forceIsDismissible;
+ mLogger.logForceIsDismissibleKeyguard(mForceIsDismissible);
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onForceIsDismissibleChanged(forceIsDismissibleIsKeepingDeviceUnlocked());
+ }
+ }
+ }
+
+ }
+
public boolean isSimPinVoiceSecure() {
// TODO: only count SIMs that handle voice
return isSimPinSecure();
@@ -3870,10 +3955,14 @@
@Override
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("KeyguardUpdateMonitor state:");
+ pw.println(" forceIsDismissible=" + mForceIsDismissible);
+ pw.println(" forceIsDismissibleIsKeepingDeviceUnlocked="
+ + forceIsDismissibleIsKeepingDeviceUnlocked());
pw.println(" getUserHasTrust()=" + getUserHasTrust(
mSelectedUserInteractor.getSelectedUserId()));
pw.println(" getUserUnlockedWithBiometric()="
+ getUserUnlockedWithBiometric(mSelectedUserInteractor.getSelectedUserId()));
+ pw.println(" mFingerprintDetectRunning=" + mFingerprintDetectRunning);
pw.println(" SIM States:");
for (SimData data : mSimDatas.values()) {
pw.println(" " + data.toString());
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 9d216dce..7ac5ac2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -339,4 +339,9 @@
* On biometric enrollment state changed
*/
public void onBiometricEnrollmentStateChanged(BiometricSourceType biometricSourceType) { }
+
+ /**
+ * On force is dismissible state changed.
+ */
+ public void onForceIsDismissibleChanged(boolean forceIsDismissible) { }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 175fcdb..d5dc85c 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -26,7 +26,6 @@
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1;
import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
-import static com.android.systemui.Flags.newAodTransition;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import android.annotation.SuppressLint;
@@ -395,16 +394,6 @@
mView.updateIcon(ICON_LOCK, true);
mView.setContentDescription(mLockedLabel);
mView.setVisibility(View.VISIBLE);
- } else if (mIsDozing && newAodTransition()) {
- mView.animate()
- .alpha(0f)
- .setDuration(FADE_OUT_DURATION_MS)
- .withEndAction(() -> {
- mView.clearIcon();
- mView.setVisibility(View.INVISIBLE);
- mView.setContentDescription(null);
- })
- .start();
} else {
mView.clearIcon();
mView.setVisibility(View.INVISIBLE);
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 055ca56..1f4e732 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -639,4 +639,12 @@
{ "fingerprint acquire message: $int1" }
)
}
+ fun logForceIsDismissibleKeyguard(keepUnlocked: Boolean) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ { bool1 = keepUnlocked },
+ { "keepUnlockedOnFold changed to: $bool1" }
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index e03c627..d6d5c26 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -68,7 +68,7 @@
import com.android.internal.util.Preconditions;
import com.android.settingslib.Utils;
-import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.data.repository.FacePropertyRepository;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.decor.CutoutDecorProviderFactory;
import com.android.systemui.decor.DebugRoundedCornerDelegate;
@@ -92,6 +92,7 @@
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.ThreadFactory;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.SecureSettings;
import dalvik.annotation.optimization.NeverCompile;
@@ -131,8 +132,6 @@
};
private final ScreenDecorationsLogger mLogger;
- private final AuthController mAuthController;
-
private DisplayTracker mDisplayTracker;
@VisibleForTesting
protected boolean mIsRegistered;
@@ -183,6 +182,9 @@
private DisplayCutout mDisplayCutout;
private boolean mPendingManualConfigUpdate;
+ private FacePropertyRepository mFacePropertyRepository;
+ private JavaAdapter mJavaAdapter;
+
@VisibleForTesting
protected void showCameraProtection(@NonNull Path protectionPath, @NonNull Rect bounds) {
if (mFaceScanningFactory.shouldShowFaceScanningAnim()) {
@@ -330,7 +332,8 @@
PrivacyDotDecorProviderFactory dotFactory,
FaceScanningProviderFactory faceScanningFactory,
ScreenDecorationsLogger logger,
- AuthController authController) {
+ FacePropertyRepository facePropertyRepository,
+ JavaAdapter javaAdapter) {
mContext = context;
mSecureSettings = secureSettings;
mCommandRegistry = commandRegistry;
@@ -342,22 +345,10 @@
mFaceScanningFactory = faceScanningFactory;
mFaceScanningViewId = com.android.systemui.res.R.id.face_scanning_anim;
mLogger = logger;
- mAuthController = authController;
+ mFacePropertyRepository = facePropertyRepository;
+ mJavaAdapter = javaAdapter;
}
-
- private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
- @Override
- public void onFaceSensorLocationChanged() {
- mLogger.onSensorLocationChanged();
- if (mExecutor != null) {
- mExecutor.execute(
- () -> updateOverlayProviderViews(
- new Integer[]{mFaceScanningViewId}));
- }
- }
- };
-
private final ScreenDecorCommand.Callback mScreenDecorCommandCallback = (cmd, pw) -> {
// If we are exiting debug mode, we can set it (false) and bail, otherwise we will
// ensure that debug mode is set
@@ -407,7 +398,8 @@
mExecutor = mThreadFactory.buildDelayableExecutorOnHandler(mHandler);
mExecutor.execute(this::startOnScreenDecorationsThread);
mDotViewController.setUiExecutor(mExecutor);
- mAuthController.addCallback(mAuthControllerCallback);
+ mJavaAdapter.alwaysCollectFlow(mFacePropertyRepository.getSensorLocation(),
+ this::onFaceSensorLocationChanged);
mCommandRegistry.registerCommand(ScreenDecorCommand.SCREEN_DECOR_CMD_NAME,
() -> new ScreenDecorCommand(mScreenDecorCommandCallback));
}
@@ -1320,6 +1312,16 @@
view.setLayoutParams(params);
}
+ @VisibleForTesting
+ void onFaceSensorLocationChanged(Point location) {
+ mLogger.onSensorLocationChanged();
+ if (mExecutor != null) {
+ mExecutor.execute(
+ () -> updateOverlayProviderViews(
+ new Integer[]{mFaceScanningViewId}));
+ }
+ }
+
public static class DisplayCutoutView extends DisplayCutoutBaseView {
final List<Rect> mBounds = new ArrayList();
final Rect mBoundingRect = new Rect();
diff --git a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
index 066cba23..6076f32 100644
--- a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
@@ -22,10 +22,9 @@
import android.window.OnBackInvokedDispatcher
import android.window.WindowOnBackInvokedDispatcher
import com.android.systemui.CoreStartable
+import com.android.systemui.Flags.predictiveBackAnimateShade
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
import com.android.systemui.shade.QuickSettingsController
@@ -48,14 +47,13 @@
private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
private val shadeController: ShadeController,
private val notificationShadeWindowController: NotificationShadeWindowController,
- private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor,
- featureFlags: FeatureFlags,
+ private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor
) : CoreStartable {
private var isCallbackRegistered = false
private val callback =
- if (featureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE)) {
+ if (predictiveBackAnimateShade()) {
/**
* New callback that handles back gesture invoked, cancel, progress and provides
* feedback via Shade animation.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index a4f90eb..093a1ff 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -148,10 +148,6 @@
private final Display mDisplay;
private float mScaleFactor = 1f;
- // sensor locations without any resolution scaling nor rotation adjustments:
- @Nullable private final Point mFaceSensorLocationDefault;
- // cached sensor locations:
- @Nullable private Point mFaceSensorLocation;
@Nullable private Point mFingerprintSensorLocation;
@Nullable private Rect mUdfpsBounds;
private final Set<Callback> mCallbacks = new HashSet<>();
@@ -622,7 +618,6 @@
mScaleFactor = mUdfpsUtils.getScaleFactor(mCachedDisplayInfo);
updateUdfpsLocation();
updateFingerprintLocation();
- updateFaceLocation();
}
/**
* @return where the fingerprint sensor exists in pixels in its natural orientation.
@@ -682,31 +677,6 @@
}
/**
- * @return where the face sensor exists in pixels in the current device orientation. Returns
- * null if no face sensor exists.
- */
- @Nullable public Point getFaceSensorLocation() {
- return mFaceSensorLocation;
- }
-
- private void updateFaceLocation() {
- if (mFaceProps == null || mFaceSensorLocationDefault == null) {
- mFaceSensorLocation = null;
- } else {
- mFaceSensorLocation = rotateToCurrentOrientation(
- new Point(
- (int) (mFaceSensorLocationDefault.x * mScaleFactor),
- (int) (mFaceSensorLocationDefault.y * mScaleFactor)),
- mCachedDisplayInfo
- );
- }
-
- for (final Callback cb : mCallbacks) {
- cb.onFaceSensorLocationChanged();
- }
- }
-
- /**
* @param inOutPoint point on the display in pixels. Going in, represents the point
* in the device's natural orientation. Going out, represents
* the point in the display's current orientation.
@@ -821,17 +791,7 @@
mWakefulnessLifecycle = wakefulnessLifecycle;
mPanelInteractionDetector = panelInteractionDetector;
-
mFaceProps = mFaceManager != null ? mFaceManager.getSensorPropertiesInternal() : null;
- int[] faceAuthLocation = context.getResources().getIntArray(
- com.android.systemui.res.R.array.config_face_auth_props);
- if (faceAuthLocation == null || faceAuthLocation.length < 2) {
- mFaceSensorLocationDefault = null;
- } else {
- mFaceSensorLocationDefault = new Point(
- faceAuthLocation[0],
- faceAuthLocation[1]);
- }
mDisplay = mContext.getDisplay();
updateSensorLocations();
@@ -868,7 +828,8 @@
mCachedDisplayInfo.getNaturalWidth(),
mCachedDisplayInfo.getNaturalHeight(),
mScaleFactor,
- mCachedDisplayInfo.rotation);
+ mCachedDisplayInfo.rotation,
+ udfpsProp.sensorType);
mUdfpsController.updateOverlayParams(udfpsProp, mUdfpsOverlayParams);
if (!Objects.equals(previousUdfpsBounds, mUdfpsBounds) || !Objects.equals(
@@ -1358,8 +1319,6 @@
final AuthDialog dialog = mCurrentDialog;
pw.println(" mCachedDisplayInfo=" + mCachedDisplayInfo);
pw.println(" mScaleFactor=" + mScaleFactor);
- pw.println(" faceAuthSensorLocationDefault=" + mFaceSensorLocationDefault);
- pw.println(" faceAuthSensorLocation=" + getFaceSensorLocation());
pw.println(" fingerprintSensorLocationInNaturalOrientation="
+ getFingerprintSensorLocationInNaturalOrientation());
pw.println(" fingerprintSensorLocation=" + getFingerprintSensorLocation());
@@ -1433,11 +1392,5 @@
* {@link #onFingerprintLocationChanged}.
*/
default void onUdfpsLocationChanged(UdfpsOverlayParams udfpsOverlayParams) {}
-
- /**
- * Called when the location of the face unlock sensor (typically the front facing camera)
- * changes. The location in pixels can change due to resolution changes.
- */
- default void onFaceSensorLocationChanged() {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 6345312..86f372a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -32,13 +32,14 @@
import com.android.settingslib.Utils
import com.android.systemui.CoreStartable
import com.android.systemui.Flags.lightRevealMigration
-import com.android.systemui.res.R
+import com.android.systemui.biometrics.data.repository.FacePropertyRepository
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.log.core.LogLevel
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.res.R
import com.android.systemui.statusbar.CircleReveal
import com.android.systemui.statusbar.LiftReveal
import com.android.systemui.statusbar.LightRevealEffect
@@ -50,6 +51,7 @@
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.ViewController
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import java.io.PrintWriter
import javax.inject.Inject
import javax.inject.Provider
@@ -62,6 +64,7 @@
*
* The ripple uses the accent color of the current theme.
*/
+@ExperimentalCoroutinesApi
@SysUISingleton
class AuthRippleController @Inject constructor(
private val sysuiContext: Context,
@@ -75,10 +78,10 @@
private val udfpsControllerProvider: Provider<UdfpsController>,
private val statusBarStateController: StatusBarStateController,
private val displayMetrics: DisplayMetrics,
- private val featureFlags: FeatureFlags,
private val logger: KeyguardLogger,
private val biometricUnlockController: BiometricUnlockController,
private val lightRevealScrim: LightRevealScrim,
+ private val facePropertyRepository: FacePropertyRepository,
rippleView: AuthRippleView?
) :
ViewController<AuthRippleView>(rippleView),
@@ -262,7 +265,7 @@
fun updateSensorLocation() {
fingerprintSensorLocation = authController.fingerprintSensorLocation
- faceSensorLocation = authController.faceSensorLocation
+ faceSensorLocation = facePropertyRepository.sensorLocation.value
}
private fun updateRippleColor() {
@@ -313,6 +316,18 @@
mView.fadeDwellRipple()
}
}
+
+ override fun onBiometricDetected(
+ userId: Int,
+ biometricSourceType: BiometricSourceType,
+ isStrongBiometric: Boolean
+ ) {
+ // TODO (b/309804148): add support detect auth ripple for deviceEntryUdfpsRefactor
+ if (!DeviceEntryUdfpsRefactor.isEnabled &&
+ keyguardUpdateMonitor.getUserCanSkipBouncer(userId)) {
+ showUnlockRipple(biometricSourceType)
+ }
+ }
}
private val configurationChangedListener =
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt b/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt
index cb75049..3ea1632 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt
@@ -22,7 +22,7 @@
import android.view.accessibility.AccessibilityNodeInfo
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.res.R
import javax.inject.Inject
@@ -35,7 +35,7 @@
@Inject
constructor(
@Main private val resources: Resources,
- private val faceAuthInteractor: KeyguardFaceAuthInteractor,
+ private val faceAuthInteractor: DeviceEntryFaceAuthInteractor,
) : View.AccessibilityDelegate() {
override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) {
super.onInitializeAccessibilityNodeInfo(host, info)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 2fd13b3..d664637 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -80,11 +80,11 @@
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor;
import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.ScreenLifecycle;
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.FalsingManager;
@@ -149,7 +149,7 @@
@NonNull private final DumpManager mDumpManager;
@NonNull private final SystemUIDialogManager mDialogManager;
@NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @NonNull private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
+ @NonNull private final DeviceEntryFaceAuthInteractor mDeviceEntryFaceAuthInteractor;
@NonNull private final VibratorHelper mVibrator;
@NonNull private final FalsingManager mFalsingManager;
@NonNull private final PowerManager mPowerManager;
@@ -664,7 +664,7 @@
@NonNull SessionTracker sessionTracker,
@NonNull AlternateBouncerInteractor alternateBouncerInteractor,
@NonNull InputManager inputManager,
- @NonNull KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
+ @NonNull DeviceEntryFaceAuthInteractor deviceEntryFaceAuthInteractor,
@NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate,
@NonNull SelectedUserInteractor selectedUserInteractor,
@NonNull FpsUnlockTracker fpsUnlockTracker,
@@ -736,7 +736,7 @@
}
return Unit.INSTANCE;
});
- mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
+ mDeviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor;
final UdfpsOverlayController mUdfpsOverlayController = new UdfpsOverlayController();
mFingerprintManager.setUdfpsOverlayController(mUdfpsOverlayController);
@@ -1027,7 +1027,7 @@
if (!mOnFingerDown) {
playStartHaptic();
- mKeyguardFaceAuthInteractor.onUdfpsSensorTouched();
+ mDeviceEntryFaceAuthInteractor.onUdfpsSensorTouched();
}
mOnFingerDown = true;
mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, pointerId, x, y,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
index b0143f5..aaccbc1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
@@ -22,6 +22,7 @@
import android.hardware.display.DisplayManager.DisplayListener
import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
import android.os.Handler
+import android.util.Size
import android.view.DisplayInfo
import com.android.internal.util.ArrayUtils
import com.android.systemui.biometrics.shared.model.DisplayRotation
@@ -40,6 +41,7 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/** Repository for the current state of the display */
@@ -58,6 +60,9 @@
/** Provides the current display rotation */
val currentRotation: StateFlow<DisplayRotation>
+
+ /** Provides the current display size */
+ val currentDisplaySize: StateFlow<Size>
}
// TODO(b/296211844): This class could directly use DeviceStateRepository and DisplayRepository
@@ -110,17 +115,13 @@
initialValue = false,
)
- private fun getDisplayRotation(): DisplayRotation {
+ private fun getDisplayInfo(): DisplayInfo {
val cachedDisplayInfo = DisplayInfo()
context.display?.getDisplayInfo(cachedDisplayInfo)
- var rotation = cachedDisplayInfo.rotation
- if (isReverseDefaultRotation) {
- rotation = (rotation + 1) % 4
- }
- return rotation.toDisplayRotation()
+ return cachedDisplayInfo
}
- override val currentRotation: StateFlow<DisplayRotation> =
+ private val currentDisplayInfo: StateFlow<DisplayInfo> =
conflatedCallbackFlow {
val callback =
object : DisplayListener {
@@ -129,11 +130,11 @@
override fun onDisplayAdded(displayId: Int) {}
override fun onDisplayChanged(displayId: Int) {
- val rotation = getDisplayRotation()
+ val displayInfo = getDisplayInfo()
trySendWithFailureLogging(
- rotation,
+ displayInfo,
TAG,
- "Error sending display rotation to $rotation"
+ "Error sending displayInfo to $displayInfo"
)
}
}
@@ -148,7 +149,37 @@
.stateIn(
applicationScope,
started = SharingStarted.Eagerly,
- initialValue = getDisplayRotation(),
+ initialValue = getDisplayInfo(),
+ )
+
+ private fun rotationToDisplayRotation(rotation: Int): DisplayRotation {
+ var adjustedRotation = rotation
+ if (isReverseDefaultRotation) {
+ adjustedRotation = (rotation + 1) % 4
+ }
+ return adjustedRotation.toDisplayRotation()
+ }
+
+ override val currentRotation: StateFlow<DisplayRotation> =
+ currentDisplayInfo
+ .map { rotationToDisplayRotation(it.rotation) }
+ .stateIn(
+ applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = rotationToDisplayRotation(currentDisplayInfo.value.rotation)
+ )
+
+ override val currentDisplaySize: StateFlow<Size> =
+ currentDisplayInfo
+ .map { Size(it.naturalWidth, it.naturalHeight) }
+ .stateIn(
+ applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue =
+ Size(
+ currentDisplayInfo.value.naturalWidth,
+ currentDisplayInfo.value.naturalHeight
+ ),
)
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt
index 0ae2e16..ae1539e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt
@@ -17,25 +17,39 @@
package com.android.systemui.biometrics.data.repository
+import android.content.Context
+import android.graphics.Point
+import android.hardware.camera2.CameraManager
import android.hardware.face.FaceManager
import android.hardware.face.FaceSensorPropertiesInternal
import android.hardware.face.IFaceAuthenticatorsRegisteredCallback
import android.util.Log
+import android.util.RotationUtils
+import android.util.Size
+import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.biometrics.shared.model.LockoutMode
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.biometrics.shared.model.toLockoutMode
+import com.android.systemui.biometrics.shared.model.toRotation
import com.android.systemui.biometrics.shared.model.toSensorStrength
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
+import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
@@ -47,20 +61,38 @@
/** Get the current lockout mode for the user. This makes a binder based service call. */
suspend fun getLockoutMode(userId: Int): LockoutMode
+
+ /** The current face sensor location in current device rotation */
+ val sensorLocation: StateFlow<Point?>
}
/** Describes a biometric sensor */
data class FaceSensorInfo(val id: Int, val strength: SensorStrength)
+/** Data class for camera info */
+private data class CameraInfo(
+ /** The logical id of the camera */
+ val cameraId: String,
+ /** The physical id of the camera */
+ val cameraPhysicalId: String?,
+ /** The center point of the camera in natural orientation */
+ val cameraLocation: Point?,
+)
+
private const val TAG = "FaceSensorPropertyRepositoryImpl"
@SysUISingleton
class FacePropertyRepositoryImpl
@Inject
constructor(
+ @Application val applicationContext: Context,
+ @Main mainExecutor: Executor,
@Application private val applicationScope: CoroutineScope,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val faceManager: FaceManager?,
+ private val cameraManager: CameraManager,
+ displayStateRepository: DisplayStateRepository,
+ configurationRepository: ConfigurationRepository,
) : FacePropertyRepository {
override val sensorInfo: StateFlow<FaceSensorInfo?> =
@@ -89,10 +121,179 @@
.onEach { Log.d(TAG, "sensorProps changed: $it") }
.stateIn(applicationScope, SharingStarted.Eagerly, null)
+ private val cameraInfoList: List<CameraInfo> = loadCameraInfoList()
+ private var currentPhysicalCameraId: String? = null
+
+ private val defaultSensorLocation: StateFlow<Point?> =
+ ConflatedCallbackFlow.conflatedCallbackFlow {
+ val callback =
+ object : CameraManager.AvailabilityCallback() {
+
+ // This callback will only be called when there is more than one front
+ // camera on the device (e.g. foldable device with cameras on both outer &
+ // inner display).
+ override fun onPhysicalCameraAvailable(
+ cameraId: String,
+ physicalCameraId: String
+ ) {
+ currentPhysicalCameraId = physicalCameraId
+ val cameraInfo =
+ cameraInfoList.firstOrNull {
+ physicalCameraId == it.cameraPhysicalId
+ }
+ trySendWithFailureLogging(
+ cameraInfo?.cameraLocation,
+ TAG,
+ "Update face sensor location to $cameraInfo."
+ )
+ }
+
+ // This callback will only be called when there is more than one front
+ // camera on the device (e.g. foldable device with cameras on both outer &
+ // inner display).
+ //
+ // By default, all cameras are available which means there will be no
+ // onPhysicalCameraAvailable() invoked and depending on the device state
+ // (Fold or unfold), only the onPhysicalCameraUnavailable() for another
+ // camera will be invoke. So we need to use this method to decide the
+ // initial physical ID for foldable devices.
+ override fun onPhysicalCameraUnavailable(
+ cameraId: String,
+ physicalCameraId: String
+ ) {
+ if (currentPhysicalCameraId == null) {
+ val cameraInfo =
+ cameraInfoList.firstOrNull {
+ physicalCameraId != it.cameraPhysicalId
+ }
+ currentPhysicalCameraId = cameraInfo?.cameraPhysicalId
+ trySendWithFailureLogging(
+ cameraInfo?.cameraLocation,
+ TAG,
+ "Update face sensor location to $cameraInfo."
+ )
+ }
+ }
+ }
+ cameraManager.registerAvailabilityCallback(mainExecutor, callback)
+ awaitClose { cameraManager.unregisterAvailabilityCallback(callback) }
+ }
+ .stateIn(
+ applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue =
+ if (cameraInfoList.isNotEmpty()) cameraInfoList[0].cameraLocation else null
+ )
+
+ override val sensorLocation: StateFlow<Point?> =
+ sensorInfo
+ .flatMapLatest { info ->
+ if (info == null) {
+ flowOf(null)
+ } else {
+ combine(
+ defaultSensorLocation,
+ displayStateRepository.currentRotation,
+ displayStateRepository.currentDisplaySize,
+ configurationRepository.scaleForResolution
+ ) { defaultLocation, displayRotation, displaySize, scaleForResolution ->
+ computeCurrentFaceLocation(
+ defaultLocation,
+ displayRotation,
+ displaySize,
+ scaleForResolution
+ )
+ }
+ }
+ }
+ .stateIn(
+ applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = null
+ )
+
+ private fun computeCurrentFaceLocation(
+ defaultLocation: Point?,
+ rotation: DisplayRotation,
+ displaySize: Size,
+ scaleForResolution: Float,
+ ): Point? {
+ if (defaultLocation == null) {
+ return null
+ }
+
+ return rotateToCurrentOrientation(
+ Point(
+ (defaultLocation.x * scaleForResolution).toInt(),
+ (defaultLocation.y * scaleForResolution).toInt()
+ ),
+ rotation,
+ displaySize
+ )
+ }
+
+ private fun rotateToCurrentOrientation(
+ inOutPoint: Point,
+ rotation: DisplayRotation,
+ displaySize: Size
+ ): Point {
+ RotationUtils.rotatePoint(
+ inOutPoint,
+ rotation.toRotation(),
+ displaySize.width,
+ displaySize.height
+ )
+ return inOutPoint
+ }
override suspend fun getLockoutMode(userId: Int): LockoutMode {
if (sensorInfo.value == null || faceManager == null) {
return LockoutMode.NONE
}
return faceManager.getLockoutModeForUser(sensorInfo.value!!.id, userId).toLockoutMode()
}
+
+ private fun loadCameraInfoList(): List<CameraInfo> {
+ val list = mutableListOf<CameraInfo>()
+
+ val outer =
+ loadCameraInfo(
+ R.string.config_protectedCameraId,
+ R.string.config_protectedPhysicalCameraId,
+ R.array.config_face_auth_props
+ )
+ if (outer != null) {
+ list.add(outer)
+ }
+
+ val inner =
+ loadCameraInfo(
+ R.string.config_protectedInnerCameraId,
+ R.string.config_protectedInnerPhysicalCameraId,
+ R.array.config_inner_face_auth_props
+ )
+ if (inner != null) {
+ list.add(inner)
+ }
+ return list
+ }
+
+ private fun loadCameraInfo(
+ cameraIdRes: Int,
+ cameraPhysicalIdRes: Int,
+ cameraLocationRes: Int
+ ): CameraInfo? {
+ val cameraId = applicationContext.getString(cameraIdRes)
+ if (cameraId.isNullOrEmpty()) {
+ return null
+ }
+ val physicalCameraId = applicationContext.getString(cameraPhysicalIdRes)
+ val cameraLocation: IntArray = applicationContext.resources.getIntArray(cameraLocationRes)
+ val location: Point?
+ if (cameraLocation.size < 2) {
+ location = null
+ } else {
+ location = Point(cameraLocation[0], cameraLocation[1])
+ }
+ return CameraInfo(cameraId, physicalCameraId, location)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
index c320350..348b54e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
@@ -53,6 +53,9 @@
private val logger: SideFpsLogger,
) {
+ private val isProlongedTouchEnabledForDevice =
+ context.resources.getBoolean(R.bool.config_restToUnlockSupported)
+
private val sensorLocationForCurrentDisplay =
combine(
displayStateInteractor.displayChanges,
@@ -82,7 +85,7 @@
.onEach { logger.authDurationChanged(it) }
val isProlongedTouchRequiredForAuthentication: Flow<Boolean> =
- if (fingerprintInteractiveToAuthProvider.isEmpty) {
+ if (fingerprintInteractiveToAuthProvider.isEmpty || !isProlongedTouchEnabledForDevice) {
flowOf(false)
} else {
combine(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 90e4a38..7b8cb82 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -77,6 +77,13 @@
applicationScope: CoroutineScope,
vibratorHelper: VibratorHelper,
): Spaghetti {
+ /**
+ * View is only set visible in BiometricViewSizeBinder once PromptSize is determined that
+ * accounts for iconView size, to prevent prompt resizing being visible to the user.
+ *
+ * TODO(b/288175072): May be able to remove this once constraint layout is implemented
+ */
+ view.visibility = View.INVISIBLE
val accessibilityManager = view.context.getSystemService(AccessibilityManager::class.java)!!
val textColorError =
@@ -102,7 +109,7 @@
iconView,
iconOverlayView,
view.getUpdatedFingerprintAffordanceSize(),
- viewModel.iconViewModel
+ viewModel
)
val indicatorMessageView = view.requireViewById<TextView>(R.id.indicator)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index 7e16d1e..1a7b6c9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -30,7 +30,6 @@
import androidx.core.view.doOnLayout
import androidx.core.view.isGone
import androidx.lifecycle.lifecycleScope
-import com.android.systemui.res.R
import com.android.systemui.biometrics.AuthPanelController
import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.ui.BiometricPromptLayout
@@ -41,6 +40,8 @@
import com.android.systemui.biometrics.ui.viewmodel.isNullOrNotSmall
import com.android.systemui.biometrics.ui.viewmodel.isSmall
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
/** Helper for [BiometricViewBinder] to handle resize transitions. */
@@ -93,7 +94,20 @@
view.repeatWhenAttached {
var currentSize: PromptSize? = null
lifecycleScope.launch {
- viewModel.size.collect { size ->
+ /**
+ * View is only set visible in BiometricViewSizeBinder once PromptSize is
+ * determined that accounts for iconView size, to prevent prompt resizing being
+ * visible to the user.
+ *
+ * TODO(b/288175072): May be able to remove isIconViewLoaded once constraint
+ * layout is implemented
+ */
+ combine(viewModel.isIconViewLoaded, viewModel.size, ::Pair).collect {
+ (isIconViewLoaded, size) ->
+ if (!isIconViewLoaded) {
+ return@collect
+ }
+
// prepare for animated size transitions
for (v in viewsToHideWhenSmall) {
v.showTextOrHide(forceHide = size.isSmall)
@@ -198,6 +212,8 @@
}
currentSize = size
+ view.visibility = View.VISIBLE
+ viewModel.setIsIconViewLoaded(false)
notifyAccessibilityChanged()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
index 475ef18..6e3bcf5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
@@ -26,6 +26,7 @@
import com.android.settingslib.widget.LottieColorUtils
import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel
import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel.AuthType
+import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
import com.android.systemui.util.kotlin.Utils.Companion.toQuint
@@ -45,8 +46,9 @@
iconView: LottieAnimationView,
iconOverlayView: LottieAnimationView,
iconViewLayoutParamSizeOverride: Pair<Int, Int>?,
- viewModel: PromptIconViewModel
+ promptViewModel: PromptViewModel
) {
+ val viewModel = promptViewModel.iconViewModel
iconView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.onConfigurationChanged(iconView.context.resources.configuration)
@@ -71,25 +73,45 @@
}
launch {
+ var width: Int
+ var height: Int
viewModel.activeAuthType.collect { activeAuthType ->
- if (iconViewLayoutParamSizeOverride == null) {
- val width: Int
- val height: Int
- when (activeAuthType) {
- AuthType.Fingerprint,
- AuthType.Coex -> {
- width = viewModel.fingerprintIconWidth
- height = viewModel.fingerprintIconHeight
- }
- AuthType.Face -> {
- width = viewModel.faceIconWidth
- height = viewModel.faceIconHeight
+ when (activeAuthType) {
+ AuthType.Fingerprint,
+ AuthType.Coex -> {
+ width = viewModel.fingerprintIconWidth
+ height = viewModel.fingerprintIconHeight
+
+ /**
+ * View is only set visible in BiometricViewSizeBinder once
+ * PromptSize is determined that accounts for iconView size, to
+ * prevent prompt resizing being visible to the user.
+ *
+ * TODO(b/288175072): May be able to remove this once constraint
+ * layout is implemented
+ */
+ iconView.removeAllLottieOnCompositionLoadedListener()
+ iconView.addLottieOnCompositionLoadedListener {
+ promptViewModel.setIsIconViewLoaded(true)
}
}
+ AuthType.Face -> {
+ width = viewModel.faceIconWidth
+ height = viewModel.faceIconHeight
+ /**
+ * Set to true by default since face icon is a drawable, which
+ * doesn't have a LottieOnCompositionLoadedListener equivalent.
+ *
+ * TODO(b/318569643): To be updated once face assets are updated
+ * from drawables
+ */
+ promptViewModel.setIsIconViewLoaded(true)
+ }
+ }
+ if (iconViewLayoutParamSizeOverride == null) {
iconView.layoutParams.width = width
iconView.layoutParams.height = height
-
iconOverlayView.layoutParams.width = width
iconOverlayView.layoutParams.height = height
}
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 6d0a58e..d899827e 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
@@ -192,6 +192,28 @@
val iconViewModel: PromptIconViewModel =
PromptIconViewModel(this, displayStateInteractor, promptSelectorInteractor)
+ private val _isIconViewLoaded = MutableStateFlow(false)
+
+ /**
+ * For prompts with an iconView, false until the prompt's iconView animation has been loaded in
+ * the view, otherwise true by default. Used for BiometricViewSizeBinder to wait for the icon
+ * asset to be loaded before determining the prompt size.
+ */
+ val isIconViewLoaded: Flow<Boolean> =
+ combine(credentialKind, _isIconViewLoaded.asStateFlow()) { credentialKind, isIconViewLoaded
+ ->
+ if (credentialKind is PromptKind.Biometric) {
+ isIconViewLoaded
+ } else {
+ true
+ }
+ }
+
+ // Sets whether the prompt's iconView animation has been loaded in the view yet.
+ fun setIsIconViewLoaded(iconViewLoaded: Boolean) {
+ _isIconViewLoaded.value = iconViewLoaded
+ }
+
/** Padding for prompt UI elements */
val promptPadding: Flow<Rect> =
combine(size, displayStateInteractor.currentRotation) { size, rotation ->
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index 81d822f..c8ce245 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -29,7 +29,7 @@
import com.android.systemui.classifier.domain.interactor.FalsingInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.res.R
import javax.inject.Inject
@@ -52,7 +52,7 @@
@Application private val applicationContext: Context,
private val repository: BouncerRepository,
private val authenticationInteractor: AuthenticationInteractor,
- private val keyguardFaceAuthInteractor: KeyguardFaceAuthInteractor,
+ private val deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor,
private val falsingInteractor: FalsingInteractor,
private val powerInteractor: PowerInteractor,
private val simBouncerInteractor: SimBouncerInteractor,
@@ -100,7 +100,7 @@
* user's pocket or by the user's face while holding their device up to their ear.
*/
fun onIntentionalUserInput() {
- keyguardFaceAuthInteractor.onPrimaryBouncerUserInput()
+ deviceEntryFaceAuthInteractor.onPrimaryBouncerUserInput()
powerInteractor.onUserTouch()
falsingInteractor.updateFalseConfidence(FalsingClassifier.Result.passed(0.6))
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
index b587ed8..ef4554c 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
@@ -30,9 +30,9 @@
import com.android.systemui.bouncer.shared.model.Message
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
import com.android.systemui.flags.SystemPropertiesHelper
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.TrustRepository
import com.android.systemui.res.R.string.bouncer_face_not_recognized
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
index 621ca5d..654fa22 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
@@ -38,9 +38,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
import com.android.systemui.shared.system.SysUiStatsLog
@@ -77,7 +77,7 @@
private val trustRepository: TrustRepository,
@Application private val applicationScope: CoroutineScope,
private val selectedUserInteractor: SelectedUserInteractor,
- private val keyguardFaceAuthInteractor: KeyguardFaceAuthInteractor,
+ private val deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor,
) {
private val passiveAuthBouncerDelay =
context.resources.getInteger(R.integer.primary_bouncer_passive_auth_delay).toLong()
@@ -429,7 +429,7 @@
keyguardUpdateMonitor.canTriggerActiveUnlockBasedOnDeviceState()
return !needsFullscreenBouncer() &&
- (keyguardFaceAuthInteractor.canFaceAuthRun() || canRunActiveUnlock)
+ (deviceEntryFaceAuthInteractor.canFaceAuthRun() || canRunActiveUnlock)
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
index 27c9b3f..d1c728c 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
@@ -16,6 +16,8 @@
package com.android.systemui.common.data
+import com.android.systemui.common.data.repository.PackageChangeRepository
+import com.android.systemui.common.data.repository.PackageChangeRepositoryImpl
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
import dagger.Binds
@@ -23,5 +25,13 @@
@Module
abstract class CommonDataLayerModule {
- @Binds abstract fun bindRepository(impl: ConfigurationRepositoryImpl): ConfigurationRepository
+ @Binds
+ abstract fun bindConfigurationRepository(
+ impl: ConfigurationRepositoryImpl
+ ): ConfigurationRepository
+
+ @Binds
+ abstract fun bindPackageChangeRepository(
+ impl: PackageChangeRepositoryImpl
+ ): PackageChangeRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepository.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepository.kt
new file mode 100644
index 0000000..7c7b3db
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepository.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.common.data.repository
+
+import android.os.UserHandle
+import com.android.systemui.common.data.shared.model.PackageChangeModel
+import kotlinx.coroutines.flow.Flow
+
+interface PackageChangeRepository {
+ /**
+ * Emits values when packages for the specified user are changed. See supported modifications in
+ * [PackageChangeModel]
+ *
+ * [UserHandle.USER_ALL] may be used to listen to all users.
+ */
+ fun packageChanged(user: UserHandle): Flow<PackageChangeModel>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepositoryImpl.kt
new file mode 100644
index 0000000..b1b348c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepositoryImpl.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.common.data.repository
+
+import android.os.UserHandle
+import com.android.systemui.common.data.shared.model.PackageChangeModel
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+
+@SysUISingleton
+class PackageChangeRepositoryImpl
+@Inject
+constructor(
+ private val monitorFactory: PackageUpdateMonitor.Factory,
+) : PackageChangeRepository {
+ /**
+ * A [PackageUpdateMonitor] which monitors package updates for all users. The per-user filtering
+ * is done by [packageChanged].
+ */
+ private val monitor by lazy { monitorFactory.create(UserHandle.ALL) }
+
+ override fun packageChanged(user: UserHandle): Flow<PackageChangeModel> =
+ monitor.packageChanged.filter {
+ user == UserHandle.ALL || user == UserHandle.getUserHandleForUid(it.packageUid)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageUpdateLogger.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageUpdateLogger.kt
new file mode 100644
index 0000000..adbb37c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageUpdateLogger.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.common.data.repository
+
+import android.os.UserHandle
+import com.android.systemui.common.data.shared.model.PackageChangeModel
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.PackageChangeRepoLog
+import javax.inject.Inject
+
+private fun getChangeString(model: PackageChangeModel) =
+ when (model) {
+ is PackageChangeModel.Installed -> "installed"
+ is PackageChangeModel.Uninstalled -> "uninstalled"
+ is PackageChangeModel.UpdateStarted -> "started updating"
+ is PackageChangeModel.UpdateFinished -> "finished updating"
+ is PackageChangeModel.Changed -> "changed"
+ }
+
+/** A debug logger for [PackageChangeRepository]. */
+@SysUISingleton
+class PackageUpdateLogger @Inject constructor(@PackageChangeRepoLog private val buffer: LogBuffer) {
+
+ fun logChange(model: PackageChangeModel) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = model.packageName
+ str2 = getChangeString(model)
+ int1 = model.packageUid
+ },
+ {
+ val user = UserHandle.getUserHandleForUid(int1)
+ "Package $str1 ($int1) $str2 on user $user"
+ }
+ )
+ }
+}
+
+private const val TAG = "PackageChangeRepoLog"
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageUpdateMonitor.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageUpdateMonitor.kt
new file mode 100644
index 0000000..f7cc344
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageUpdateMonitor.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.common.data.repository
+
+import android.content.Context
+import android.os.Handler
+import android.os.UserHandle
+import com.android.internal.content.PackageMonitor
+import com.android.systemui.common.data.shared.model.PackageChangeModel
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+
+/**
+ * A wrapper around [PackageMonitor] which exposes package updates as a flow.
+ *
+ * External clients should use [PackageChangeRepository] instead to ensure only a single callback is
+ * registered for all of SystemUI.
+ */
+class PackageUpdateMonitor
+@AssistedInject
+constructor(
+ @Assisted private val user: UserHandle,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ @Background private val bgHandler: Handler,
+ @Application private val context: Context,
+ @Application private val scope: CoroutineScope,
+ private val logger: PackageUpdateLogger,
+) : PackageMonitor() {
+
+ @AssistedFactory
+ fun interface Factory {
+ fun create(user: UserHandle): PackageUpdateMonitor
+ }
+
+ var isActive = false
+ private set
+
+ private val _packageChanged =
+ MutableSharedFlow<PackageChangeModel>(replay = 0, extraBufferCapacity = BUFFER_CAPACITY)
+ .apply {
+ // Automatically register/unregister as needed, depending on whether
+ // there are subscribers to this flow.
+ subscriptionCount
+ .map { it > 0 }
+ .distinctUntilChanged()
+ .onEach { active ->
+ if (active) {
+ register(context, user, bgHandler)
+ } else if (isActive) {
+ // Avoid calling unregister if we were not previously active, as this
+ // will cause an IllegalStateException.
+ unregister()
+ }
+ isActive = active
+ }
+ .flowOn(bgDispatcher)
+ .launchIn(scope)
+ }
+
+ val packageChanged: Flow<PackageChangeModel>
+ get() = _packageChanged.onEach(logger::logChange)
+
+ override fun onPackageAdded(packageName: String, uid: Int) {
+ super.onPackageAdded(packageName, uid)
+ _packageChanged.tryEmit(PackageChangeModel.Installed(packageName, uid))
+ }
+
+ override fun onPackageRemoved(packageName: String, uid: Int) {
+ super.onPackageRemoved(packageName, uid)
+ _packageChanged.tryEmit(PackageChangeModel.Uninstalled(packageName, uid))
+ }
+
+ override fun onPackageChanged(
+ packageName: String,
+ uid: Int,
+ components: Array<out String>
+ ): Boolean {
+ super.onPackageChanged(packageName, uid, components)
+ _packageChanged.tryEmit(PackageChangeModel.Changed(packageName, uid))
+ return false
+ }
+
+ override fun onPackageUpdateStarted(packageName: String, uid: Int) {
+ super.onPackageUpdateStarted(packageName, uid)
+ _packageChanged.tryEmit(PackageChangeModel.UpdateStarted(packageName, uid))
+ }
+
+ override fun onPackageUpdateFinished(packageName: String, uid: Int) {
+ super.onPackageUpdateFinished(packageName, uid)
+ _packageChanged.tryEmit(PackageChangeModel.UpdateFinished(packageName, uid))
+ }
+
+ private companion object {
+ // This capacity is the number of package changes that we will keep buffered in the shared
+ // flow. It is unlikely that at any given time there would be this many changes being
+ // processed by consumers, but this is done just in case that many packages are changed at
+ // the same time and there is backflow due to consumers processing the changes more slowly
+ // than they are being emitted.
+ const val BUFFER_CAPACITY = 100
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/shared/model/PackageChangeModel.kt b/packages/SystemUI/src/com/android/systemui/common/data/shared/model/PackageChangeModel.kt
new file mode 100644
index 0000000..853eff7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/data/shared/model/PackageChangeModel.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.common.data.shared.model
+
+import android.content.Intent
+
+/** Represents changes to an installed package. */
+sealed interface PackageChangeModel {
+ val packageName: String
+ val packageUid: Int
+
+ /**
+ * An existing application package was uninstalled.
+ *
+ * Equivalent to receiving the [Intent.ACTION_PACKAGE_REMOVED] broadcast with
+ * [Intent.EXTRA_REPLACING] set to false.
+ */
+ data class Uninstalled(override val packageName: String, override val packageUid: Int) :
+ PackageChangeModel
+
+ /**
+ * A new version of an existing application is going to be installed.
+ *
+ * Equivalent to receiving the [Intent.ACTION_PACKAGE_REMOVED] broadcast with
+ * [Intent.EXTRA_REPLACING] set to true.
+ */
+ data class UpdateStarted(override val packageName: String, override val packageUid: Int) :
+ PackageChangeModel
+
+ /**
+ * A new version of an existing application package has been installed, replacing the old
+ * version.
+ *
+ * Equivalent to receiving the [Intent.ACTION_PACKAGE_ADDED] broadcast with
+ * [Intent.EXTRA_REPLACING] set to true.
+ */
+ data class UpdateFinished(override val packageName: String, override val packageUid: Int) :
+ PackageChangeModel
+
+ /**
+ * A new application package has been installed.
+ *
+ * Equivalent to receiving the [Intent.ACTION_PACKAGE_ADDED] broadcast with
+ * [Intent.EXTRA_REPLACING] set to false.
+ */
+ data class Installed(override val packageName: String, override val packageUid: Int) :
+ PackageChangeModel
+
+ /**
+ * An existing application package has been changed (for example, a component has been enabled
+ * or disabled).
+ *
+ * Equivalent to receiving the [Intent.ACTION_PACKAGE_CHANGED] broadcast.
+ */
+ data class Changed(override val packageName: String, override val packageUid: Int) :
+ PackageChangeModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index 847b98e..10768ea 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -16,7 +16,6 @@
package com.android.systemui.communal.dagger
-import android.content.Context
import com.android.systemui.communal.data.db.CommunalDatabaseModule
import com.android.systemui.communal.data.repository.CommunalMediaRepositoryModule
import com.android.systemui.communal.data.repository.CommunalRepositoryModule
@@ -24,9 +23,8 @@
import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.communal.widgets.EditWidgetsActivityStarterImpl
-import com.android.systemui.dagger.qualifiers.Application
+import dagger.Binds
import dagger.Module
-import dagger.Provides
@Module(
includes =
@@ -38,11 +36,9 @@
CommunalDatabaseModule::class,
]
)
-class CommunalModule {
- @Provides
- fun provideEditWidgetsActivityStarter(
- @Application context: Context
- ): EditWidgetsActivityStarter {
- return EditWidgetsActivityStarterImpl(context)
- }
+interface CommunalModule {
+ @Binds
+ fun bindEditWidgetsActivityStarter(
+ starter: EditWidgetsActivityStarterImpl
+ ): EditWidgetsActivityStarter
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
new file mode 100644
index 0000000..cf2e33c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.data.model
+
+/** Data model of media on the communal hub. */
+data class CommunalMediaModel(
+ val hasAnyMediaOrRecommendation: Boolean,
+ val createdTimestampMillis: Long = 0L,
+) {
+ companion object {
+ val INACTIVE =
+ CommunalMediaModel(
+ hasAnyMediaOrRecommendation = false,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
index e41c322..e8a561b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
@@ -16,18 +16,17 @@
package com.android.systemui.communal.data.repository
+import com.android.systemui.communal.data.model.CommunalMediaModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.media.controls.models.player.MediaData
import com.android.systemui.media.controls.pipeline.MediaDataManager
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.onCompletion
-import kotlinx.coroutines.flow.onStart
/** Encapsulates the state of smartspace in communal. */
interface CommunalMediaRepository {
- val mediaPlaying: Flow<Boolean>
+ val mediaModel: Flow<CommunalMediaModel>
}
@SysUISingleton
@@ -47,27 +46,32 @@
receivedSmartspaceCardLatency: Int,
isSsReactivated: Boolean
) {
- if (!mediaDataManager.hasAnyMediaOrRecommendation()) {
- return
- }
- _mediaPlaying.value = true
+ updateMediaModel(data)
}
override fun onMediaDataRemoved(key: String) {
- if (mediaDataManager.hasAnyMediaOrRecommendation()) {
- return
- }
- _mediaPlaying.value = false
+ updateMediaModel()
}
}
- private val _mediaPlaying: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ init {
+ mediaDataManager.addListener(mediaDataListener)
+ }
- override val mediaPlaying: Flow<Boolean> =
- _mediaPlaying
- .onStart {
- mediaDataManager.addListener(mediaDataListener)
- _mediaPlaying.value = mediaDataManager.hasAnyMediaOrRecommendation()
- }
- .onCompletion { mediaDataManager.removeListener(mediaDataListener) }
+ private val _mediaModel: MutableStateFlow<CommunalMediaModel> =
+ MutableStateFlow(CommunalMediaModel.INACTIVE)
+
+ override val mediaModel: Flow<CommunalMediaModel> = _mediaModel
+
+ private fun updateMediaModel(data: MediaData? = null) {
+ if (mediaDataManager.hasAnyMediaOrRecommendation()) {
+ _mediaModel.value =
+ CommunalMediaModel(
+ hasAnyMediaOrRecommendation = true,
+ createdTimestampMillis = data?.createdTimestampMillis ?: 0L,
+ )
+ } else {
+ _mediaModel.value = CommunalMediaModel.INACTIVE
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
index 1f4be40..553b3eb 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
@@ -56,6 +56,9 @@
/** Exposes the transition state of the communal [SceneTransitionLayout]. */
val transitionState: StateFlow<ObservableCommunalTransitionState>
+ /** Whether the CTA tile is visible in the hub under view mode. */
+ val isCtaTileInViewModeVisible: Flow<Boolean>
+
/** Updates the requested scene. */
fun setDesiredScene(desiredScene: CommunalSceneKey)
@@ -65,6 +68,9 @@
* Note that you must call is with `null` when the UI is done or risk a memory leak.
*/
fun setTransitionState(transitionState: Flow<ObservableCommunalTransitionState>?)
+
+ /** Updates whether to display the CTA tile in the hub under view mode. */
+ fun setCtaTileInViewModeVisibility(isVisible: Boolean)
}
@OptIn(ExperimentalCoroutinesApi::class)
@@ -96,6 +102,16 @@
initialValue = defaultTransitionState,
)
+ // TODO(b/313462210) - persist the value in local storage, so the tile won't show up again
+ // once dismissed.
+ private val _isCtaTileInViewModeVisible: MutableStateFlow<Boolean> = MutableStateFlow(true)
+ override val isCtaTileInViewModeVisible: Flow<Boolean> =
+ _isCtaTileInViewModeVisible.asStateFlow()
+
+ override fun setCtaTileInViewModeVisibility(isVisible: Boolean) {
+ _isCtaTileInViewModeVisible.value = isVisible
+ }
+
override fun setDesiredScene(desiredScene: CommunalSceneKey) {
_desiredScene.value = desiredScene
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index d1bbe57..cab8adf 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -38,6 +38,7 @@
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
import com.android.systemui.settings.UserTracker
+import java.util.Optional
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -75,7 +76,7 @@
class CommunalWidgetRepositoryImpl
@Inject
constructor(
- private val appWidgetManager: AppWidgetManager,
+ private val appWidgetManager: Optional<AppWidgetManager>,
private val appWidgetHost: AppWidgetHost,
@Application private val applicationScope: CoroutineScope,
@Background private val bgDispatcher: CoroutineDispatcher,
@@ -144,7 +145,7 @@
override val communalWidgets: Flow<List<CommunalWidgetContentModel>> =
isHostActive.flatMapLatest { isHostActive ->
- if (!isHostActive) {
+ if (!isHostActive || !appWidgetManager.isPresent) {
return@flatMapLatest flowOf(emptyList())
}
communalWidgetDao.getWidgets().map { it.map(::mapToContentModel) }
@@ -187,7 +188,7 @@
val (_, widgetId) = entry.value
return CommunalWidgetContentModel(
appWidgetId = widgetId,
- providerInfo = appWidgetManager.getAppWidgetInfo(widgetId),
+ providerInfo = appWidgetManager.get().getAppWidgetInfo(widgetId),
priority = entry.key.rank,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
index 5793f10..d0d9e3f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
@@ -31,6 +31,7 @@
import dagger.Binds
import dagger.Module
import dagger.Provides
+import java.util.Optional
import javax.inject.Named
@Module
@@ -41,8 +42,8 @@
@SysUISingleton
@Provides
- fun provideAppWidgetManager(@Application context: Context): AppWidgetManager {
- return AppWidgetManager.getInstance(context)
+ fun provideAppWidgetManager(@Application context: Context): Optional<AppWidgetManager> {
+ return Optional.ofNullable(AppWidgetManager.getInstance(context))
}
@SysUISingleton
@@ -54,7 +55,7 @@
@SysUISingleton
@Provides
fun provideCommunalWidgetHost(
- appWidgetManager: AppWidgetManager,
+ appWidgetManager: Optional<AppWidgetManager>,
appWidgetHost: AppWidgetHost,
@CommunalLog logBuffer: LogBuffer,
): CommunalWidgetHost {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 18fb895..8271238 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -34,15 +34,13 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.smartspace.data.repository.SmartspaceRepository
import javax.inject.Inject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
/** Encapsulates business-logic related to communal mode. */
-@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class CommunalInteractor
@Inject
@@ -98,6 +96,9 @@
editWidgetsActivityStarter.startActivity()
}
+ /** Dismiss the CTA tile from the hub in view mode. */
+ fun dismissCtaTile() = communalRepository.setCtaTileInViewModeVisibility(isVisible = false)
+
/** Add a widget at the specified position. */
fun addWidget(componentName: ComponentName, priority: Int) =
widgetRepository.addWidget(componentName, priority)
@@ -125,27 +126,25 @@
}
}
- /** A flow of available smartspace content. Currently only showing timer targets. */
- val smartspaceContent: Flow<List<CommunalContentModel.Smartspace>> =
+ /** A flow of available smartspace targets. Currently only showing timers. */
+ private val smartspaceTargets: Flow<List<SmartspaceTarget>> =
if (!smartspaceRepository.isSmartspaceRemoteViewsEnabled) {
flowOf(emptyList())
} else {
smartspaceRepository.communalSmartspaceTargets.map { targets ->
- targets
- .filter { target ->
- target.featureType == SmartspaceTarget.FEATURE_TIMER &&
- target.remoteViews != null
- }
- .mapIndexed Target@{ index, target ->
- return@Target CommunalContentModel.Smartspace(
- smartspaceTargetId = target.smartspaceTargetId,
- remoteViews = target.remoteViews!!,
- size = dynamicContentSize(targets.size, index),
- )
- }
+ targets.filter { target ->
+ target.featureType == SmartspaceTarget.FEATURE_TIMER &&
+ target.remoteViews != null
+ }
}
}
+ /** CTA tile to be displayed in the glanceable hub (view mode). */
+ val ctaTileContent: Flow<List<CommunalContentModel.CtaTileInViewMode>> =
+ communalRepository.isCtaTileInViewModeVisible.map { visible ->
+ if (visible) listOf(CommunalContentModel.CtaTileInViewMode()) else emptyList()
+ }
+
/** A list of tutorial content to be displayed in the communal hub in tutorial mode. */
val tutorialContent: List<CommunalContentModel.Tutorial> =
listOf(
@@ -159,14 +158,43 @@
CommunalContentModel.Tutorial(id = 7, HALF),
)
- val umoContent: Flow<List<CommunalContentModel.Umo>> =
- mediaRepository.mediaPlaying.flatMapLatest { mediaPlaying ->
- if (mediaPlaying) {
- // TODO(b/310254801): support HALF and FULL layouts
- flowOf(listOf(CommunalContentModel.Umo(THIRD)))
- } else {
- flowOf(emptyList())
+ /**
+ * A flow of ongoing content, including smartspace timers and umo, ordered by creation time and
+ * sized dynamically.
+ */
+ val ongoingContent: Flow<List<CommunalContentModel.Ongoing>> =
+ combine(smartspaceTargets, mediaRepository.mediaModel) { smartspace, media ->
+ val ongoingContent = mutableListOf<CommunalContentModel.Ongoing>()
+
+ // Add smartspace
+ ongoingContent.addAll(
+ smartspace.map { target ->
+ CommunalContentModel.Smartspace(
+ smartspaceTargetId = target.smartspaceTargetId,
+ remoteViews = target.remoteViews!!,
+ createdTimestampMillis = target.creationTimeMillis,
+ )
+ }
+ )
+
+ // Add UMO
+ if (media.hasAnyMediaOrRecommendation) {
+ ongoingContent.add(
+ CommunalContentModel.Umo(
+ createdTimestampMillis = media.createdTimestampMillis,
+ )
+ )
}
+
+ // Order by creation time descending
+ ongoingContent.sortByDescending { it.createdTimestampMillis }
+
+ // Dynamic sizing
+ ongoingContent.forEachIndexed { index, model ->
+ model.size = dynamicContentSize(ongoingContent.size, index)
+ }
+
+ return@combine ongoingContent
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index 3ae5229..46f957f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -30,46 +30,95 @@
/** Size to be rendered in the grid. */
val size: CommunalContentSize
+ /**
+ * A type of communal content is ongoing / live / ephemeral, and can be sized and ordered
+ * dynamically.
+ */
+ sealed interface Ongoing : CommunalContentModel {
+ override var size: CommunalContentSize
+
+ /** Timestamp in milliseconds of when the content was created. */
+ val createdTimestampMillis: Long
+ }
+
class Widget(
val appWidgetId: Int,
val providerInfo: AppWidgetProviderInfo,
val appWidgetHost: AppWidgetHost,
) : CommunalContentModel {
- override val key = "widget_$appWidgetId"
+ override val key = KEY.widget(appWidgetId)
// Widget size is always half.
override val size = CommunalContentSize.HALF
}
/** A placeholder item representing a new widget being added */
class WidgetPlaceholder : CommunalContentModel {
- override val key: String = "widget_placeholder_${UUID.randomUUID()}"
+ override val key: String = KEY.widgetPlaceholder()
+ // Same as widget size.
+ override val size = CommunalContentSize.HALF
+ }
+
+ /** A CTA tile in the glanceable hub view mode which can be dismissed. */
+ class CtaTileInViewMode : CommunalContentModel {
+ override val key: String = KEY.CTA_TILE_IN_VIEW_MODE_KEY
+ // Same as widget size.
+ override val size = CommunalContentSize.HALF
+ }
+
+ /** A CTA tile in the glanceable hub edit model which remains visible in the grid. */
+ class CtaTileInEditMode : CommunalContentModel {
+ override val key: String = KEY.CTA_TILE_IN_EDIT_MODE_KEY
// Same as widget size.
override val size = CommunalContentSize.HALF
}
class Tutorial(
id: Int,
- override val size: CommunalContentSize,
+ override var size: CommunalContentSize,
) : CommunalContentModel {
- override val key = "tutorial_$id"
+ override val key = KEY.tutorial(id)
}
class Smartspace(
smartspaceTargetId: String,
val remoteViews: RemoteViews,
- override val size: CommunalContentSize,
- ) : CommunalContentModel {
- override val key = "smartspace_$smartspaceTargetId"
+ override val createdTimestampMillis: Long,
+ override var size: CommunalContentSize = CommunalContentSize.HALF,
+ ) : Ongoing {
+ override val key = KEY.smartspace(smartspaceTargetId)
}
class Umo(
- override val size: CommunalContentSize,
- ) : CommunalContentModel {
- override val key = UMO_KEY
+ override val createdTimestampMillis: Long,
+ override var size: CommunalContentSize = CommunalContentSize.HALF,
+ ) : Ongoing {
+ override val key = KEY.umo()
}
- companion object {
- /** Key for the [Umo] in CommunalContentModel. There should only ever be one UMO. */
- const val UMO_KEY = "umo"
+ class KEY {
+ companion object {
+ const val CTA_TILE_IN_VIEW_MODE_KEY = "cta_tile_in_view_mode"
+ const val CTA_TILE_IN_EDIT_MODE_KEY = "cta_tile_in_edit_mode"
+
+ fun widget(id: Int): String {
+ return "widget_$id"
+ }
+
+ fun widgetPlaceholder(): String {
+ return "widget_placeholder_${UUID.randomUUID()}"
+ }
+
+ fun tutorial(id: Int): String {
+ return "tutorial_$id"
+ }
+
+ fun smartspace(id: String): String {
+ return "smartspace_$id"
+ }
+
+ fun umo(): String {
+ return "umo"
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt
index 086d729..155de32 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt
@@ -22,6 +22,7 @@
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
+import java.util.Optional
import javax.inject.Inject
/**
@@ -31,7 +32,7 @@
class CommunalWidgetHost
@Inject
constructor(
- private val appWidgetManager: AppWidgetManager,
+ private val appWidgetManager: Optional<AppWidgetManager>,
private val appWidgetHost: AppWidgetHost,
@CommunalLog logBuffer: LogBuffer,
) {
@@ -56,6 +57,10 @@
return null
}
- private fun bindWidget(widgetId: Int, provider: ComponentName): Boolean =
- appWidgetManager.bindAppWidgetIdIfAllowed(widgetId, provider)
+ private fun bindWidget(widgetId: Int, provider: ComponentName): Boolean {
+ if (appWidgetManager.isPresent) {
+ return appWidgetManager.get().bindAppWidgetIdIfAllowed(widgetId, provider)
+ }
+ return false
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt
new file mode 100644
index 0000000..e167f3e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.shared.log
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger.UiEventEnum
+
+/** UI events for the Communal Hub. */
+enum class CommunalUiEvent(private val id: Int) : UiEventEnum {
+ @UiEvent(doc = "Communal Hub is fully shown") COMMUNAL_HUB_SHOWN(1566),
+ @UiEvent(doc = "Communal Hub starts entering") COMMUNAL_HUB_ENTERING(1575),
+ @UiEvent(doc = "Communal Hub starts exiting") COMMUNAL_HUB_EXITING(1576),
+ @UiEvent(doc = "Communal Hub is fully gone") COMMUNAL_HUB_GONE(1577),
+ @UiEvent(doc = "Communal Hub times out") COMMUNAL_HUB_TIMEOUT(1578),
+ @UiEvent(doc = "The visible content in the Communal Hub is fully loaded and rendered")
+ COMMUNAL_HUB_LOADED(1579),
+ @UiEvent(doc = "User starts the swipe gesture to enter the Communal Hub")
+ COMMUNAL_HUB_SWIPE_TO_ENTER_START(1580),
+ @UiEvent(doc = "User finishes the swipe gesture to enter the Communal Hub")
+ COMMUNAL_HUB_SWIPE_TO_ENTER_FINISH(1581),
+ @UiEvent(doc = "User cancels the swipe gesture to enter the Communal Hub")
+ COMMUNAL_HUB_SWIPE_TO_ENTER_CANCEL(1582),
+ @UiEvent(doc = "User starts the swipe gesture to exit the Communal Hub")
+ COMMUNAL_HUB_SWIPE_TO_EXIT_START(1583),
+ @UiEvent(doc = "User finishes the swipe gesture to exit the Communal Hub")
+ COMMUNAL_HUB_SWIPE_TO_EXIT_FINISH(1584),
+ @UiEvent(doc = "User cancels the swipe gesture to exit the Communal Hub")
+ COMMUNAL_HUB_SWIPE_TO_EXIT_CANCEL(1585),
+ @UiEvent(doc = "User starts the drag gesture to reorder a widget")
+ COMMUNAL_HUB_REORDER_WIDGET_START(1586),
+ @UiEvent(doc = "User finishes the drag gesture to reorder a widget")
+ COMMUNAL_HUB_REORDER_WIDGET_FINISH(1587),
+ @UiEvent(doc = "User cancels the drag gesture to reorder a widget")
+ COMMUNAL_HUB_REORDER_WIDGET_CANCEL(1588),
+ @UiEvent(doc = "Edit mode for the Communal Hub is shown") COMMUNAL_HUB_EDIT_MODE_SHOWN(1569),
+ @UiEvent(doc = "Edit mode for the Communal Hub is gone") COMMUNAL_HUB_EDIT_MODE_GONE(1589),
+ @UiEvent(doc = "Widget picker for the Communal Hub is shown")
+ COMMUNAL_HUB_WIDGET_PICKER_SHOWN(1590),
+ @UiEvent(doc = "Widget picker for the Communal Hub is gone")
+ COMMUNAL_HUB_WIDGET_PICKER_GONE(1591),
+ @UiEvent(doc = "User performs a swipe up gesture from bottom to enter bouncer")
+ COMMUNAL_HUB_SWIPE_UP_TO_BOUNCER(1573),
+ @UiEvent(doc = "User performs a swipe down gesture from top to enter shade")
+ COMMUNAL_HUB_SWIPE_DOWN_TO_SHADE(1574);
+
+ override fun getId(): Int {
+ return id
+ }
+}
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 577e404..4fabd97 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
@@ -20,6 +20,7 @@
import android.os.PowerManager
import android.os.SystemClock
import android.view.MotionEvent
+import android.widget.RemoteViews
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalSceneKey
@@ -101,4 +102,10 @@
/** Called as the UI requests opening the widget editor. */
open fun onOpenWidgetEditor() {}
+
+ /** Called as the UI requests to dismiss the CTA tile. */
+ open fun onDismissCtaTile() {}
+
+ /** Gets the interaction handler used to handle taps on a remote view */
+ abstract fun getInteractionHandler(): RemoteViews.InteractionHandler
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 368df9e..d8e831c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.communal.ui.viewmodel
import android.os.PowerManager
+import android.widget.RemoteViews
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.dagger.SysUISingleton
@@ -27,6 +28,7 @@
import javax.inject.Named
import javax.inject.Provider
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
/** The view model for communal hub in edit mode. */
@SysUISingleton
@@ -41,12 +43,19 @@
override val isEditMode = true
- // Only widgets are editable.
+ // Only widgets are editable. The CTA tile comes last in the list and remains visible.
override val communalContent: Flow<List<CommunalContentModel>> =
- communalInteractor.widgetContent
+ communalInteractor.widgetContent.map { widgets ->
+ widgets + listOf(CommunalContentModel.CtaTileInEditMode())
+ }
override fun onDeleteWidget(id: Int) = communalInteractor.deleteWidget(id)
override fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) =
communalInteractor.updateWidgetOrder(widgetIdToPriorityMap)
+
+ override fun getInteractionHandler(): RemoteViews.InteractionHandler {
+ // Ignore all interactions in edit mode.
+ return RemoteViews.InteractionHandler { _, _, _ -> false }
+ }
}
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 abf1986..066e897 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
@@ -17,9 +17,11 @@
package com.android.systemui.communal.ui.viewmodel
import android.os.PowerManager
+import android.widget.RemoteViews
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.widgets.WidgetInteractionHandler
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.media.dagger.MediaModule
@@ -39,6 +41,7 @@
@Inject
constructor(
private val communalInteractor: CommunalInteractor,
+ private val interactionHandler: WidgetInteractionHandler,
tutorialInteractor: CommunalTutorialInteractor,
shadeViewController: Provider<ShadeViewController>,
powerManager: PowerManager,
@@ -51,13 +54,18 @@
return@flatMapLatest flowOf(communalInteractor.tutorialContent)
}
combine(
- communalInteractor.smartspaceContent,
- communalInteractor.umoContent,
+ communalInteractor.ongoingContent,
communalInteractor.widgetContent,
- ) { smartspace, umo, widgets ->
- smartspace + umo + widgets
+ communalInteractor.ctaTileContent,
+ ) { ongoing, widgets, ctaTile,
+ ->
+ ongoing + widgets + ctaTile
}
}
override fun onOpenWidgetEditor() = communalInteractor.showWidgetEditor()
+
+ override fun onDismissCtaTile() = communalInteractor.dismissCtaTile()
+
+ override fun getInteractionHandler(): RemoteViews.InteractionHandler = interactionHandler
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index c936c63..0f94a92 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -23,6 +23,7 @@
import android.os.RemoteException
import android.util.Log
import android.view.IWindowManager
+import android.view.WindowInsets
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
@@ -79,6 +80,10 @@
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ val windowInsetsController = window.decorView.windowInsetsController
+ windowInsetsController?.hide(WindowInsets.Type.systemBars())
+ window.setDecorFitsSystemWindows(false)
+
setCommunalEditWidgetActivityContent(
activity = this,
viewModel = communalViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt
index 846e300..55acad0 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt
@@ -19,17 +19,26 @@
import android.content.Context
import android.content.Intent
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.plugins.ActivityStarter
+import javax.inject.Inject
interface EditWidgetsActivityStarter {
fun startActivity()
}
-class EditWidgetsActivityStarterImpl(@Application private val applicationContext: Context) :
- EditWidgetsActivityStarter {
+class EditWidgetsActivityStarterImpl
+@Inject
+constructor(
+ @Application private val applicationContext: Context,
+ private val activityStarter: ActivityStarter,
+) : EditWidgetsActivityStarter {
+
override fun startActivity() {
- applicationContext.startActivity(
+ activityStarter.startActivityDismissingKeyguard(
Intent(applicationContext, EditWidgetsActivity::class.java)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK),
+ /* onlyProvisioned = */ true,
+ /* dismissShade = */ true,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
new file mode 100644
index 0000000..c8db70b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.widgets
+
+import android.app.PendingIntent
+import android.view.View
+import android.widget.RemoteViews
+import com.android.systemui.plugins.ActivityStarter
+import javax.inject.Inject
+
+class WidgetInteractionHandler
+@Inject
+constructor(
+ private val activityStarter: ActivityStarter,
+) : RemoteViews.InteractionHandler {
+ override fun onInteraction(
+ view: View,
+ pendingIntent: PendingIntent,
+ response: RemoteViews.RemoteResponse
+ ): Boolean =
+ when {
+ pendingIntent.isActivity -> startActivity(pendingIntent)
+ else ->
+ RemoteViews.startPendingIntent(view, pendingIntent, response.getLaunchOptions(view))
+ }
+
+ private fun startActivity(pendingIntent: PendingIntent): Boolean {
+ activityStarter.startPendingIntentMaybeDismissingKeyguard(
+ /* intent = */ pendingIntent,
+ /* intentSentUiThreadCallback = */ null,
+ // TODO(b/318758390): Properly animate activities started from widgets.
+ /* animationController = */ null
+ )
+ return true
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt b/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt
index 0218f45..20bfbc9 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt
@@ -26,6 +26,8 @@
import androidx.annotation.WorkerThread
import com.android.systemui.CoreStartable
import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.data.shared.model.PackageChangeModel
+import com.android.systemui.common.data.repository.PackageChangeRepository
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
@@ -33,8 +35,16 @@
import com.android.systemui.controls.panels.SelectedComponentRepository
import com.android.systemui.controls.ui.SelectedItem
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.settings.UserTracker
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -53,13 +63,16 @@
class ControlsStartable
@Inject
constructor(
- @Background private val executor: Executor,
- private val controlsComponent: ControlsComponent,
- private val userTracker: UserTracker,
- private val authorizedPanelsRepository: AuthorizedPanelsRepository,
- private val selectedComponentRepository: SelectedComponentRepository,
- private val userManager: UserManager,
- private val broadcastDispatcher: BroadcastDispatcher,
+ @Application private val scope: CoroutineScope,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ @Background private val executor: Executor,
+ private val controlsComponent: ControlsComponent,
+ private val userTracker: UserTracker,
+ private val authorizedPanelsRepository: AuthorizedPanelsRepository,
+ private val selectedComponentRepository: SelectedComponentRepository,
+ private val packageChangeRepository: PackageChangeRepository,
+ private val userManager: UserManager,
+ private val broadcastDispatcher: BroadcastDispatcher,
) : CoreStartable {
// These two controllers can only be accessed after `start` method once we've checked if the
@@ -78,6 +91,8 @@
}
}
+ private var packageJob: Job? = null
+
override fun start() {}
override fun onBootCompleted() {
@@ -94,6 +109,21 @@
controlsListingController.forceReload()
selectDefaultPanelIfNecessary()
bindToPanel()
+ monitorPackageUninstall()
+ }
+
+ private fun monitorPackageUninstall() {
+ packageJob?.cancel()
+ packageJob = packageChangeRepository.packageChanged(userTracker.userHandle)
+ .filter {
+ val selectedPackage =
+ selectedComponentRepository.getSelectedComponent()?.componentName?.packageName
+ // Selected package was uninstalled
+ (it is PackageChangeModel.Uninstalled) && (it.packageName == selectedPackage)
+ }
+ .onEach { selectedComponentRepository.removeSelectedComponent() }
+ .flowOn(bgDispatcher)
+ .launchIn(scope)
}
private fun selectDefaultPanelIfNecessary() {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index e71007b..8831dc6 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -484,7 +484,9 @@
}
},
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
- null,
+ ActivityOptions.makeBasic()
+ .setPendingIntentCreatorBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED).toBundle(),
userTracker.userHandle
)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index ac71664..87a736d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -54,6 +54,7 @@
import com.android.systemui.stylus.StylusUsiPowerStartable
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.theme.ThemeOverlayController
+import com.android.systemui.unfold.DisplaySwitchLatencyTracker
import com.android.systemui.usb.StorageNotification
import com.android.systemui.util.NotificationChannels
import com.android.systemui.util.StartBinderLoggerModule
@@ -141,6 +142,12 @@
@ClassKey(LatencyTester::class)
abstract fun bindLatencyTester(sysui: LatencyTester): CoreStartable
+ /** Inject into DisplaySwitchLatencyTracker. */
+ @Binds
+ @IntoMap
+ @ClassKey(DisplaySwitchLatencyTracker::class)
+ abstract fun bindDisplaySwitchLatencyTracker(sysui: DisplaySwitchLatencyTracker): CoreStartable
+
/** Inject into NotificationChannels. */
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
index 615b503..3bc4f34 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
@@ -32,6 +32,7 @@
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.FaceScanningOverlay
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.data.repository.FacePropertyRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.log.ScreenDecorationsLogger
@@ -41,19 +42,20 @@
@SysUISingleton
class FaceScanningProviderFactory @Inject constructor(
- private val authController: AuthController,
- private val context: Context,
- private val statusBarStateController: StatusBarStateController,
- private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- @Main private val mainExecutor: Executor,
- private val logger: ScreenDecorationsLogger,
+ private val authController: AuthController,
+ private val context: Context,
+ private val statusBarStateController: StatusBarStateController,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ @Main private val mainExecutor: Executor,
+ private val logger: ScreenDecorationsLogger,
+ private val facePropertyRepository: FacePropertyRepository,
) : DecorProviderFactory() {
private val display = context.display
private val displayInfo = DisplayInfo()
override val hasProviders: Boolean
get() {
- if (authController.faceSensorLocation == null) {
+ if (facePropertyRepository.sensorLocation.value == null) {
return false
}
@@ -86,6 +88,7 @@
keyguardUpdateMonitor,
mainExecutor,
logger,
+ facePropertyRepository,
)
)
}
@@ -104,12 +107,13 @@
}
class FaceScanningOverlayProviderImpl(
- override val alignedBound: Int,
- private val authController: AuthController,
- private val statusBarStateController: StatusBarStateController,
- private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- private val mainExecutor: Executor,
- private val logger: ScreenDecorationsLogger,
+ override val alignedBound: Int,
+ private val authController: AuthController,
+ private val statusBarStateController: StatusBarStateController,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val mainExecutor: Executor,
+ private val logger: ScreenDecorationsLogger,
+ private val facePropertyRepository: FacePropertyRepository,
) : BoundDecorProvider() {
override val viewId: Int = com.android.systemui.res.R.id.face_scanning_anim
@@ -162,8 +166,9 @@
layoutParams.let { lp ->
lp.width = ViewGroup.LayoutParams.MATCH_PARENT
lp.height = ViewGroup.LayoutParams.MATCH_PARENT
- logger.faceSensorLocation(authController.faceSensorLocation)
- authController.faceSensorLocation?.y?.let { faceAuthSensorHeight ->
+ logger.faceSensorLocation(facePropertyRepository.sensorLocation.value)
+ facePropertyRepository.sensorLocation.value?.y?.let {
+ faceAuthSensorHeight ->
val faceScanningHeight = (faceAuthSensorHeight * 2)
when (rotation) {
Surface.ROTATION_0, Surface.ROTATION_180 ->
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
index b915418..71b5ab2 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
@@ -1,6 +1,7 @@
package com.android.systemui.deviceentry
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepositoryModule
+import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigModule
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import dagger.Module
import dagger.multibindings.Multibinds
@@ -9,6 +10,7 @@
includes =
[
DeviceEntryRepositoryModule::class,
+ FaceWakeUpTriggersConfigModule::class,
],
)
abstract class DeviceEntryModule {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
rename to packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
index 17d7836..7a70c4a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.data.repository
+package com.android.systemui.deviceentry.data.repository
import android.app.StatusBarManager
import android.content.Context
@@ -22,7 +22,6 @@
import android.os.CancellationSignal
import com.android.internal.logging.InstanceId
import com.android.internal.logging.UiEventLogger
-import com.android.keyguard.FaceAuthUiEvent
import com.android.systemui.Dumpable
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
@@ -32,19 +31,27 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
+import com.android.systemui.deviceentry.shared.model.AcquiredFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus
+import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.BiometricType
+import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.FaceAuthTableLog
+import com.android.systemui.keyguard.data.repository.FaceDetectTableLog
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.data.repository.TrustRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AcquiredFaceAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
-import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.log.FaceAuthenticationLogger
diff --git a/packages/SystemUI/src/com/android/keyguard/FaceWakeUpTriggersConfig.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfig.kt
similarity index 83%
rename from packages/SystemUI/src/com/android/keyguard/FaceWakeUpTriggersConfig.kt
rename to packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfig.kt
index 84a6b09..9d6718b 100644
--- a/packages/SystemUI/src/com/android/keyguard/FaceWakeUpTriggersConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfig.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -14,28 +14,35 @@
* limitations under the License.
*/
-package com.android.keyguard
+package com.android.systemui.deviceentry.data.repository
import android.content.res.Resources
import android.os.Build
import android.os.PowerManager
import com.android.systemui.Dumpable
-import com.android.systemui.res.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.res.R
import com.android.systemui.util.settings.GlobalSettings
+import dagger.Binds
+import dagger.Module
import java.io.PrintWriter
import java.util.stream.Collectors
import javax.inject.Inject
/** Determines which device wake-ups should trigger passive authentication. */
+interface FaceWakeUpTriggersConfig {
+ fun shouldTriggerFaceAuthOnWakeUpFrom(@PowerManager.WakeReason pmWakeReason: Int): Boolean
+ fun shouldTriggerFaceAuthOnWakeUpFrom(wakeReason: WakeSleepReason): Boolean
+}
+
@SysUISingleton
-class FaceWakeUpTriggersConfig
+class FaceWakeUpTriggersConfigImpl
@Inject
constructor(@Main resources: Resources, globalSettings: GlobalSettings, dumpManager: DumpManager) :
- Dumpable {
+ Dumpable, FaceWakeUpTriggersConfig {
private val defaultTriggerFaceAuthOnWakeUpFrom: Set<Int> =
resources.getIntArray(R.array.config_face_auth_wake_up_triggers).toSet()
private val triggerFaceAuthOnWakeUpFrom: Set<Int>
@@ -65,11 +72,13 @@
dumpManager.registerDumpable(this)
}
- fun shouldTriggerFaceAuthOnWakeUpFrom(@PowerManager.WakeReason pmWakeReason: Int): Boolean {
+ override fun shouldTriggerFaceAuthOnWakeUpFrom(
+ @PowerManager.WakeReason pmWakeReason: Int
+ ): Boolean {
return triggerFaceAuthOnWakeUpFrom.contains(pmWakeReason)
}
- fun shouldTriggerFaceAuthOnWakeUpFrom(wakeReason: WakeSleepReason): Boolean =
+ override fun shouldTriggerFaceAuthOnWakeUpFrom(wakeReason: WakeSleepReason): Boolean =
wakeSleepReasonsToTriggerFaceAuth.contains(wakeReason)
override fun dump(pw: PrintWriter, args: Array<out String>) {
@@ -87,3 +96,8 @@
?: default
}
}
+
+@Module
+interface FaceWakeUpTriggersConfigModule {
+ @Binds fun repository(impl: FaceWakeUpTriggersConfigImpl): FaceWakeUpTriggersConfig
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/NoopDeviceEntryFaceAuthRepository.kt
similarity index 68%
rename from packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
rename to packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/NoopDeviceEntryFaceAuthRepository.kt
index f4a74f0..6695b182 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/NoopDeviceEntryFaceAuthRepository.kt
@@ -1,25 +1,25 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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
+ * 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
+ * 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.
+ * 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.data.repository
+package com.android.systemui.deviceentry.data.repository
-import com.android.keyguard.FaceAuthUiEvent
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
+import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
+import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt
index 1a6bd04..5699176 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt
@@ -18,13 +18,14 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.shared.DeviceEntryBiometricMode
+import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
-import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
@@ -62,7 +63,9 @@
val faceOnlyFaceFailure: Flow<FailedFaceAuthenticationStatus> =
faceOnly.flatMapLatest { faceOnly ->
if (faceOnly) {
- deviceEntryFaceAuthInteractor.faceFailure
+ deviceEntryFaceAuthInteractor.authenticationStatus.filterIsInstance<
+ FailedFaceAuthenticationStatus
+ >()
} else {
emptyFlow()
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
index 70716c6..99bd25b 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -16,19 +16,80 @@
package com.android.systemui.deviceentry.domain.interactor
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus
-import javax.inject.Inject
+import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filterIsInstance
-@SysUISingleton
-class DeviceEntryFaceAuthInteractor
-@Inject
-constructor(
- repository: DeviceEntryFaceAuthRepository,
-) {
- val faceFailure: Flow<FailedFaceAuthenticationStatus> =
- repository.authenticationStatus.filterIsInstance<FailedFaceAuthenticationStatus>()
+/**
+ * Interactor that exposes API to get the face authentication status and handle any events that can
+ * cause face authentication to run for device entry.
+ */
+interface DeviceEntryFaceAuthInteractor {
+
+ /** Current authentication status */
+ val authenticationStatus: Flow<FaceAuthenticationStatus>
+
+ /** Current detection status */
+ val detectionStatus: Flow<FaceDetectionStatus>
+
+ /** Can face auth be run right now */
+ fun canFaceAuthRun(): Boolean
+
+ /** Whether face auth is currently running or not. */
+ fun isRunning(): Boolean
+
+ /** Whether face auth is in lock out state. */
+ fun isLockedOut(): Boolean
+
+ /** Whether face auth is enrolled and enabled for the current user */
+ fun isFaceAuthEnabledAndEnrolled(): Boolean
+
+ /** Whether the current user is authenticated successfully with face auth */
+ fun isAuthenticated(): Boolean
+ /**
+ * Register listener for use from code that cannot use [authenticationStatus] or
+ * [detectionStatus]
+ */
+ fun registerListener(listener: FaceAuthenticationListener)
+
+ /** Unregister previously registered listener */
+ fun unregisterListener(listener: FaceAuthenticationListener)
+
+ fun onUdfpsSensorTouched()
+ fun onAssistantTriggeredOnLockScreen()
+ fun onDeviceLifted()
+ fun onQsExpansionStared()
+ fun onNotificationPanelClicked()
+ fun onSwipeUpOnBouncer()
+ fun onPrimaryBouncerUserInput()
+ fun onAccessibilityAction()
+ fun onWalletLaunched()
+
+ /** Whether face auth is considered class 3 */
+ fun isFaceAuthStrong(): Boolean
+}
+
+/**
+ * Listener that can be registered with the [DeviceEntryFaceAuthInteractor] to receive updates about
+ * face authentication & detection updates.
+ *
+ * This is present to make it easier for use the new face auth API for code that cannot use
+ * [DeviceEntryFaceAuthInteractor.authenticationStatus] or
+ * [DeviceEntryFaceAuthInteractor.detectionStatus] flows.
+ */
+interface FaceAuthenticationListener {
+ /** Receive face isAuthenticated updates */
+ fun onAuthenticatedChanged(isAuthenticated: Boolean)
+
+ /** Receive face authentication status updates */
+ fun onAuthenticationStatusChanged(status: FaceAuthenticationStatus)
+
+ /** Receive status updates whenever face detection runs */
+ fun onDetectionStatusChanged(status: FaceDetectionStatus)
+
+ fun onLockoutStateChanged(isLockedOut: Boolean)
+
+ fun onRunningStateChanged(isRunning: Boolean)
+
+ fun onAuthEnrollmentStateChanged(enrolled: Boolean)
}
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 f6a9570..2680328 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
@@ -20,8 +20,8 @@
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
-import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.TrustRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.scene.domain.interactor.SceneInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
new file mode 100644
index 0000000..3b94166
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.deviceentry.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+
+/**
+ * Implementation of the interactor that noops all face auth operations.
+ *
+ * This is required for SystemUI variants that do not support face authentication but still inject
+ * other SysUI components that depend on [DeviceEntryFaceAuthInteractor]
+ */
+@SysUISingleton
+class NoopDeviceEntryFaceAuthInteractor @Inject constructor() : DeviceEntryFaceAuthInteractor {
+ override val authenticationStatus: Flow<FaceAuthenticationStatus>
+ get() = emptyFlow()
+ override val detectionStatus: Flow<FaceDetectionStatus>
+ get() = emptyFlow()
+
+ override fun canFaceAuthRun(): Boolean = false
+
+ override fun isRunning(): Boolean = false
+
+ override fun isLockedOut(): Boolean = false
+
+ override fun isFaceAuthEnabledAndEnrolled(): Boolean = false
+
+ override fun isFaceAuthStrong(): Boolean = false
+
+ override fun isAuthenticated(): Boolean = false
+
+ override fun registerListener(listener: FaceAuthenticationListener) {}
+
+ override fun unregisterListener(listener: FaceAuthenticationListener) {}
+
+ override fun onUdfpsSensorTouched() {}
+
+ override fun onAssistantTriggeredOnLockScreen() {}
+
+ override fun onDeviceLifted() {}
+
+ override fun onQsExpansionStared() {}
+
+ override fun onNotificationPanelClicked() {}
+
+ override fun onSwipeUpOnBouncer() {}
+ override fun onPrimaryBouncerUserInput() {}
+ override fun onAccessibilityAction() {}
+ override fun onWalletLaunched() = Unit
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
index e3f4739..98130eb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
@@ -14,14 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.domain.interactor
+package com.android.systemui.deviceentry.domain.interactor
import android.app.trust.TrustManager
import android.content.Context
import android.hardware.biometrics.BiometricFaceConstants
import android.hardware.biometrics.BiometricSourceType
-import com.android.keyguard.FaceAuthUiEvent
-import com.android.keyguard.FaceWakeUpTriggersConfig
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.CoreStartable
import com.android.systemui.biometrics.data.repository.FacePropertyRepository
@@ -32,11 +30,14 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfig
+import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
+import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -65,7 +66,7 @@
* SystemUI Keyguard.
*/
@SysUISingleton
-class SystemUIKeyguardFaceAuthInteractor
+class SystemUIDeviceEntryFaceAuthInteractor
@Inject
constructor(
private val context: Context,
@@ -84,7 +85,7 @@
private val powerInteractor: PowerInteractor,
private val biometricSettingsRepository: BiometricSettingsRepository,
private val trustManager: TrustManager,
-) : CoreStartable, KeyguardFaceAuthInteractor {
+) : CoreStartable, DeviceEntryFaceAuthInteractor {
private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf()
@@ -310,7 +311,7 @@
}
companion object {
- const val TAG = "KeyguardFaceAuthInteractor"
+ const val TAG = "DeviceEntryFaceAuthInteractor"
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/FaceAuthReason.kt
similarity index 73%
rename from packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
rename to packages/SystemUI/src/com/android/systemui/deviceentry/shared/FaceAuthReason.kt
index 2abb7a4..ee220d5 100644
--- a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/FaceAuthReason.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -14,58 +14,55 @@
* limitations under the License.
*/
-package com.android.keyguard
+package com.android.systemui.deviceentry.shared
import android.annotation.StringDef
import android.os.PowerManager
import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
-import com.android.keyguard.FaceAuthApiRequestReason.Companion.ACCESSIBILITY_ACTION
-import com.android.keyguard.FaceAuthApiRequestReason.Companion.NOTIFICATION_PANEL_CLICKED
-import com.android.keyguard.FaceAuthApiRequestReason.Companion.PICK_UP_GESTURE_TRIGGERED
-import com.android.keyguard.FaceAuthApiRequestReason.Companion.QS_EXPANDED
-import com.android.keyguard.FaceAuthApiRequestReason.Companion.SWIPE_UP_ON_BOUNCER
-import com.android.keyguard.FaceAuthApiRequestReason.Companion.UDFPS_POINTER_DOWN
-import com.android.keyguard.InternalFaceAuthReasons.ALL_AUTHENTICATORS_REGISTERED
-import com.android.keyguard.InternalFaceAuthReasons.ALTERNATE_BIOMETRIC_BOUNCER_SHOWN
-import com.android.keyguard.InternalFaceAuthReasons.ASSISTANT_VISIBILITY_CHANGED
-import com.android.keyguard.InternalFaceAuthReasons.AUTH_REQUEST_DURING_CANCELLATION
-import com.android.keyguard.InternalFaceAuthReasons.BIOMETRIC_ENABLED
-import com.android.keyguard.InternalFaceAuthReasons.CAMERA_LAUNCHED
-import com.android.keyguard.InternalFaceAuthReasons.DEVICE_WOKEN_UP_ON_REACH_GESTURE
-import com.android.keyguard.InternalFaceAuthReasons.DISPLAY_OFF
-import com.android.keyguard.InternalFaceAuthReasons.DREAM_STARTED
-import com.android.keyguard.InternalFaceAuthReasons.DREAM_STOPPED
-import com.android.keyguard.InternalFaceAuthReasons.ENROLLMENTS_CHANGED
-import com.android.keyguard.InternalFaceAuthReasons.FACE_AUTHENTICATED
-import com.android.keyguard.InternalFaceAuthReasons.FACE_AUTH_STOPPED_ON_USER_INPUT
-import com.android.keyguard.InternalFaceAuthReasons.FACE_CANCEL_NOT_RECEIVED
-import com.android.keyguard.InternalFaceAuthReasons.FACE_LOCKOUT_RESET
-import com.android.keyguard.InternalFaceAuthReasons.FINISHED_GOING_TO_SLEEP
-import com.android.keyguard.InternalFaceAuthReasons.FP_AUTHENTICATED
-import com.android.keyguard.InternalFaceAuthReasons.FP_LOCKED_OUT
-import com.android.keyguard.InternalFaceAuthReasons.GOING_TO_SLEEP
-import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_GOING_AWAY
-import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_INIT
-import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_OCCLUSION_CHANGED
-import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_RESET
-import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_VISIBILITY_CHANGED
-import com.android.keyguard.InternalFaceAuthReasons.NON_STRONG_BIOMETRIC_ALLOWED_CHANGED
-import com.android.keyguard.InternalFaceAuthReasons.OCCLUDING_APP_REQUESTED
-import com.android.keyguard.InternalFaceAuthReasons.POSTURE_CHANGED
-import com.android.keyguard.InternalFaceAuthReasons.PRIMARY_BOUNCER_SHOWN
-import com.android.keyguard.InternalFaceAuthReasons.PRIMARY_BOUNCER_SHOWN_OR_WILL_BE_SHOWN
-import com.android.keyguard.InternalFaceAuthReasons.RETRY_AFTER_HW_UNAVAILABLE
-import com.android.keyguard.InternalFaceAuthReasons.STARTED_WAKING_UP
-import com.android.keyguard.InternalFaceAuthReasons.STRONG_AUTH_ALLOWED_CHANGED
-import com.android.keyguard.InternalFaceAuthReasons.TRUST_DISABLED
-import com.android.keyguard.InternalFaceAuthReasons.TRUST_ENABLED
-import com.android.keyguard.InternalFaceAuthReasons.USER_SWITCHING
+import com.android.systemui.deviceentry.shared.FaceAuthApiRequestReason.Companion.ACCESSIBILITY_ACTION
+import com.android.systemui.deviceentry.shared.FaceAuthApiRequestReason.Companion.NOTIFICATION_PANEL_CLICKED
+import com.android.systemui.deviceentry.shared.FaceAuthApiRequestReason.Companion.PICK_UP_GESTURE_TRIGGERED
+import com.android.systemui.deviceentry.shared.FaceAuthApiRequestReason.Companion.QS_EXPANDED
+import com.android.systemui.deviceentry.shared.FaceAuthApiRequestReason.Companion.SWIPE_UP_ON_BOUNCER
+import com.android.systemui.deviceentry.shared.FaceAuthApiRequestReason.Companion.UDFPS_POINTER_DOWN
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.ALL_AUTHENTICATORS_REGISTERED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.ALTERNATE_BIOMETRIC_BOUNCER_SHOWN
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.ASSISTANT_VISIBILITY_CHANGED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.AUTH_REQUEST_DURING_CANCELLATION
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.BIOMETRIC_ENABLED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.CAMERA_LAUNCHED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.DEVICE_WOKEN_UP_ON_REACH_GESTURE
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.DISPLAY_OFF
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.DREAM_STARTED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.DREAM_STOPPED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.ENROLLMENTS_CHANGED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.FACE_AUTHENTICATED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.FACE_AUTH_STOPPED_ON_USER_INPUT
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.FACE_CANCEL_NOT_RECEIVED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.FACE_LOCKOUT_RESET
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.FINISHED_GOING_TO_SLEEP
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.FP_AUTHENTICATED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.FP_LOCKED_OUT
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.GOING_TO_SLEEP
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.KEYGUARD_GOING_AWAY
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.KEYGUARD_INIT
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.KEYGUARD_OCCLUSION_CHANGED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.KEYGUARD_RESET
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.KEYGUARD_VISIBILITY_CHANGED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.NON_STRONG_BIOMETRIC_ALLOWED_CHANGED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.OCCLUDING_APP_REQUESTED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.POSTURE_CHANGED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.PRIMARY_BOUNCER_SHOWN
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.PRIMARY_BOUNCER_SHOWN_OR_WILL_BE_SHOWN
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.RETRY_AFTER_HW_UNAVAILABLE
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.STARTED_WAKING_UP
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.STRONG_AUTH_ALLOWED_CHANGED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.TRUST_DISABLED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.TRUST_ENABLED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.USER_SWITCHING
-/**
- * List of reasons why face auth is requested by clients through
- * [KeyguardUpdateMonitor.requestFaceAuth].
- */
+/** List of reasons why face auth is requested by clients. */
@Retention(AnnotationRetention.SOURCE)
@StringDef(
SWIPE_UP_ON_BOUNCER,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/FaceAuthenticationModels.kt
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
rename to packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/FaceAuthenticationModels.kt
index 3de3666..f006b34 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/FaceAuthenticationModels.kt
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.shared.model
+package com.android.systemui.deviceentry.shared.model
import android.hardware.face.FaceManager
import android.os.SystemClock.elapsedRealtime
/**
* Authentication status provided by
- * [com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository]
+ * [com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository]
*/
sealed class FaceAuthenticationStatus
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index bb0c273..699532c 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -444,34 +444,10 @@
// TODO(b/254512728): Tracking Bug
@JvmField val NEW_BACK_AFFORDANCE = releasedFlag("new_back_affordance")
- // TODO(b/255854141): Tracking Bug
- @JvmField
- val WM_ENABLE_PREDICTIVE_BACK_SYSUI =
- unreleasedFlag("persist.wm.debug.predictive_back_sysui_enable", teamfood = true)
// TODO(b/270987164): Tracking Bug
@JvmField val TRACKPAD_GESTURE_FEATURES = releasedFlag("trackpad_gesture_features")
- // TODO(b/263826204): Tracking Bug
- @JvmField
- val WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM =
- unreleasedFlag("persist.wm.debug.predictive_back_bouncer_anim", teamfood = true)
-
- // TODO(b/238475428): Tracking Bug
- @JvmField
- val WM_SHADE_ALLOW_BACK_GESTURE =
- sysPropBooleanFlag("persist.wm.debug.shade_allow_back_gesture", default = false)
-
- // TODO(b/238475428): Tracking Bug
- @JvmField
- val WM_SHADE_ANIMATE_BACK_GESTURE =
- unreleasedFlag("persist.wm.debug.shade_animate_back_gesture", teamfood = false)
-
- // TODO(b/265639042): Tracking Bug
- @JvmField
- val WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM =
- unreleasedFlag("persist.wm.debug.predictive_back_qs_dialog_anim", teamfood = true)
-
// TODO(b/273800936): Tracking Bug
@JvmField val TRACKPAD_GESTURE_COMMON = releasedFlag("trackpad_gesture_common")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
index aa4c88a..e23ec89 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
@@ -403,6 +403,7 @@
public static final int INDICATION_TYPE_REVERSE_CHARGING = 10;
public static final int INDICATION_TYPE_BIOMETRIC_MESSAGE = 11;
public static final int INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP = 12;
+ public static final int INDICATION_IS_DISMISSIBLE = 13;
@IntDef({
INDICATION_TYPE_NONE,
@@ -417,7 +418,8 @@
INDICATION_TYPE_USER_LOCKED,
INDICATION_TYPE_REVERSE_CHARGING,
INDICATION_TYPE_BIOMETRIC_MESSAGE,
- INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP
+ INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+ INDICATION_IS_DISMISSIBLE
})
@Retention(RetentionPolicy.SOURCE)
public @interface IndicationType{}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index edf9648..e2ab20e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -593,6 +593,13 @@
mKeyguardViewMediator.doKeyguardTimeout(options);
}
+ // Binder interface
+ public void showDismissibleKeyguard() {
+ trace("showDismissibleKeyguard");
+ checkPermission();
+ mKeyguardViewMediator.showDismissibleKeyguard();
+ }
+
@Override // Binder interface
public void setSwitchingUser(boolean switching) {
trace("setSwitchingUser switching=" + switching);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index d7a1906..a34730e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -107,6 +107,7 @@
import androidx.annotation.VisibleForTesting;
import com.android.app.animation.Interpolators;
+import com.android.internal.foldables.FoldGracePeriodProvider;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.jank.InteractionJankMonitor.Configuration;
import com.android.internal.logging.UiEventLogger;
@@ -311,6 +312,11 @@
*/
public static final String OPTION_FORCE_SHOW = "force_show";
public static final String SYS_BOOT_REASON_PROP = "sys.boot.reason.last";
+ /**
+ * Boolean option for showKeyguard, when set to true, can show the keyguard without immediately
+ * locking.
+ */
+ public static final String OPTION_SHOW_DISMISSIBLE = "show_dismissible";
public static final String REBOOT_MAINLINE_UPDATE = "reboot,mainline_update";
private final DreamOverlayStateController mDreamOverlayStateController;
private final JavaAdapter mJavaAdapter;
@@ -1321,7 +1327,9 @@
private DozeParameters mDozeParameters;
private SelectedUserInteractor mSelectedUserInteractor;
private KeyguardInteractor mKeyguardInteractor;
-
+ @VisibleForTesting
+ protected FoldGracePeriodProvider mFoldGracePeriodProvider =
+ new FoldGracePeriodProvider();
private final KeyguardStateController mKeyguardStateController;
private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
new KeyguardStateController.Callback() {
@@ -1995,7 +2003,7 @@
mNeedToReshowWhenReenabled = false;
updateInputRestrictedLocked();
- showLocked(null);
+ showKeyguard(null);
// block until we know the keyguard is done drawing (and post a message
// to unblock us after a timeout, so we don't risk blocking too long
@@ -2170,6 +2178,24 @@
}
/**
+ * Only available if the fold grace period feature is enabled.
+ * Used by PhoneWindowManager to show the keyguard immediately without locking the device.
+ * This method shows the keyguard whether there's a screen lock configured or not (including
+ * screen lock SWIPE or NONE).
+ * This must be safe to call from any thread and with any window manager locks held.
+ */
+ public void showDismissibleKeyguard() {
+ if (mFoldGracePeriodProvider.isEnabled()) {
+ Bundle showKeyguardUnlocked = new Bundle();
+ showKeyguardUnlocked.putBoolean(OPTION_SHOW_DISMISSIBLE, true);
+ showKeyguard(showKeyguardUnlocked);
+ } else {
+ Log.e(TAG, "fold grace period feature isn't enabled, but showKeyguard() method is"
+ + " being called", new Throwable());
+ }
+ }
+
+ /**
* Given the state of the keyguard, is the input restricted?
* Input is restricted when the keyguard is showing, or when the keyguard
* was suppressed by an app that disabled the keyguard or we haven't been provisioned yet.
@@ -2273,7 +2299,7 @@
}
if (DEBUG) Log.d(TAG, "doKeyguard: showing the lock screen");
- showLocked(options);
+ showKeyguard(options);
}
private void lockProfile(int userId) {
@@ -2335,18 +2361,18 @@
}
/**
- * Send message to keyguard telling it to show itself
+ * Send message to keyguard telling it to show itself.
* @see #handleShow
*/
- private void showLocked(Bundle options) {
- Trace.beginSection("KeyguardViewMediator#showLocked acquiring mShowKeyguardWakeLock");
- if (DEBUG) Log.d(TAG, "showLocked");
+ private void showKeyguard(Bundle options) {
+ Trace.beginSection("KeyguardViewMediator#showKeyguard acquiring mShowKeyguardWakeLock");
+ if (DEBUG) Log.d(TAG, "showKeyguard");
// ensure we stay awake until we are finished displaying the keyguard
mShowKeyguardWakeLock.acquire();
Message msg = mHandler.obtainMessage(SHOW, options);
// Treat these messages with priority - This call can originate from #doKeyguardTimeout,
- // meaning the device should lock as soon as possible and not wait for other messages on
- // the thread to process first.
+ // meaning the device may lock, so it shouldn't wait for other messages on the thread to
+ // process first.
mHandler.sendMessageAtFrontOfQueue(msg);
Trace.endSection();
}
@@ -2724,13 +2750,18 @@
}
/**
- * Handle message sent by {@link #showLocked}.
+ * Handle message sent by {@link #showKeyguard}.
* @see #SHOW
*/
private void handleShow(Bundle options) {
Trace.beginSection("KeyguardViewMediator#handleShow");
+ final boolean showUnlocked = options != null
+ && options.getBoolean(OPTION_SHOW_DISMISSIBLE, false);
final int currentUser = mSelectedUserInteractor.getSelectedUserId();
- if (mLockPatternUtils.isSecure(currentUser)) {
+ if (showUnlocked) {
+ // tell KeyguardUpdateMonitor to keep the device unlocked until the next lock signal
+ mUpdateMonitor.tryForceIsDismissibleKeyguard();
+ } else if (mLockPatternUtils.isSecure(currentUser)) {
mLockPatternUtils.getDevicePolicyManager().reportKeyguardSecured(currentUser);
}
synchronized (KeyguardViewMediator.this) {
@@ -2755,7 +2786,7 @@
+ ", which means we're showing in the middle of hiding.");
}
- // Force if we we're showing in the middle of unlocking, to ensure we end up in the
+ // Force if we're showing in the middle of unlocking, to ensure we end up in the
// correct state.
setShowingLocked(true, hidingOrGoingAway /* force */);
mHiding = false;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt
index d8eb81c..f29b657 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt
@@ -16,10 +16,10 @@
package com.android.systemui.keyguard.dagger
-import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.data.repository.NoopDeviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
-import com.android.systemui.keyguard.domain.interactor.NoopKeyguardFaceAuthInteractor
+import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.deviceentry.data.repository.NoopDeviceEntryFaceAuthRepository
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.NoopDeviceEntryFaceAuthInteractor
import dagger.Binds
import dagger.Module
@@ -33,7 +33,9 @@
@Module
interface KeyguardFaceAuthNotSupportedModule {
@Binds
- fun keyguardFaceAuthInteractor(impl: NoopKeyguardFaceAuthInteractor): KeyguardFaceAuthInteractor
+ fun keyguardFaceAuthInteractor(
+ impl: NoopDeviceEntryFaceAuthInteractor
+ ): DeviceEntryFaceAuthInteractor
@Binds
fun deviceEntryFaceAuthRepository(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
index ee0eb2d..b373f85 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
@@ -19,8 +19,10 @@
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
-import com.android.systemui.keyguard.domain.interactor.SystemUIKeyguardFaceAuthInteractor
+import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepositoryImpl
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.SystemUIDeviceEntryFaceAuthInteractor
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
import dagger.Binds
@@ -38,13 +40,13 @@
@Binds
@IntoMap
- @ClassKey(SystemUIKeyguardFaceAuthInteractor::class)
- fun bind(impl: SystemUIKeyguardFaceAuthInteractor): CoreStartable
+ @ClassKey(SystemUIDeviceEntryFaceAuthInteractor::class)
+ fun bind(impl: SystemUIDeviceEntryFaceAuthInteractor): CoreStartable
@Binds
fun keyguardFaceAuthInteractor(
- impl: SystemUIKeyguardFaceAuthInteractor
- ): KeyguardFaceAuthInteractor
+ impl: SystemUIDeviceEntryFaceAuthInteractor
+ ): DeviceEntryFaceAuthInteractor
companion object {
@Provides
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 31ef100..704ebdd 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
@@ -21,6 +21,7 @@
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.data.repository.FacePropertyRepository
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.common.shared.model.Position
@@ -110,7 +111,7 @@
val isKeyguardGoingAway: Flow<Boolean>
/** Is the always-on display available to be used? */
- val isAodAvailable: Flow<Boolean>
+ val isAodAvailable: StateFlow<Boolean>
fun setAodAvailable(value: Boolean)
@@ -277,6 +278,7 @@
@Main private val mainDispatcher: CoroutineDispatcher,
@Application private val scope: CoroutineScope,
private val systemClock: SystemClock,
+ facePropertyRepository: FacePropertyRepository,
) : KeyguardRepository {
private val _dismissAction: MutableStateFlow<DismissAction> =
MutableStateFlow(DismissAction.None)
@@ -338,7 +340,7 @@
.distinctUntilChanged()
private val _isAodAvailable = MutableStateFlow(false)
- override val isAodAvailable: Flow<Boolean> = _isAodAvailable.asStateFlow()
+ override val isAodAvailable: StateFlow<Boolean> = _isAodAvailable.asStateFlow()
override fun setAodAvailable(value: Boolean) {
_isAodAvailable.value = value
@@ -599,27 +601,7 @@
awaitClose { authController.removeCallback(callback) }
}
- override val faceSensorLocation: Flow<Point?> = conflatedCallbackFlow {
- fun sendSensorLocation() {
- trySendWithFailureLogging(
- authController.faceSensorLocation,
- TAG,
- "AuthController.Callback#onFingerprintLocationChanged"
- )
- }
-
- val callback =
- object : AuthController.Callback {
- override fun onFaceSensorLocationChanged() {
- sendSensorLocation()
- }
- }
-
- authController.addCallback(callback)
- sendSensorLocation()
-
- awaitClose { authController.removeCallback(callback) }
- }
+ override val faceSensorLocation: Flow<Point?> = facePropertyRepository.sensorLocation
override val biometricUnlockSource: Flow<BiometricUnlockSource?> = conflatedCallbackFlow {
val callback =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
deleted file mode 100644
index 046916a..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
+++ /dev/null
@@ -1,95 +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.systemui.keyguard.domain.interactor
-
-import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
-import kotlinx.coroutines.flow.Flow
-
-/**
- * Interactor that exposes API to get the face authentication status and handle any events that can
- * cause face authentication to run.
- */
-interface KeyguardFaceAuthInteractor {
-
- /** Current authentication status */
- val authenticationStatus: Flow<FaceAuthenticationStatus>
-
- /** Current detection status */
- val detectionStatus: Flow<FaceDetectionStatus>
-
- /** Can face auth be run right now */
- fun canFaceAuthRun(): Boolean
-
- /** Whether face auth is currently running or not. */
- fun isRunning(): Boolean
-
- /** Whether face auth is in lock out state. */
- fun isLockedOut(): Boolean
-
- /** Whether face auth is enrolled and enabled for the current user */
- fun isFaceAuthEnabledAndEnrolled(): Boolean
-
- /** Whether the current user is authenticated successfully with face auth */
- fun isAuthenticated(): Boolean
- /**
- * Register listener for use from code that cannot use [authenticationStatus] or
- * [detectionStatus]
- */
- fun registerListener(listener: FaceAuthenticationListener)
-
- /** Unregister previously registered listener */
- fun unregisterListener(listener: FaceAuthenticationListener)
-
- fun onUdfpsSensorTouched()
- fun onAssistantTriggeredOnLockScreen()
- fun onDeviceLifted()
- fun onQsExpansionStared()
- fun onNotificationPanelClicked()
- fun onSwipeUpOnBouncer()
- fun onPrimaryBouncerUserInput()
- fun onAccessibilityAction()
- fun onWalletLaunched()
-
- /** Whether face auth is considered class 3 */
- fun isFaceAuthStrong(): Boolean
-}
-
-/**
- * Listener that can be registered with the [KeyguardFaceAuthInteractor] to receive updates about
- * face authentication & detection updates.
- *
- * This is present to make it easier for use the new face auth API for code that cannot use
- * [KeyguardFaceAuthInteractor.authenticationStatus] or [KeyguardFaceAuthInteractor.detectionStatus]
- * flows.
- */
-interface FaceAuthenticationListener {
- /** Receive face isAuthenticated updates */
- fun onAuthenticatedChanged(isAuthenticated: Boolean)
-
- /** Receive face authentication status updates */
- fun onAuthenticationStatusChanged(status: FaceAuthenticationStatus)
-
- /** Receive status updates whenever face detection runs */
- fun onDetectionStatusChanged(status: FaceDetectionStatus)
-
- fun onLockoutStateChanged(isLockedOut: Boolean)
-
- fun onRunningStateChanged(isRunning: Boolean)
-
- fun onAuthEnrollmentStateChanged(enrolled: Boolean)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 21651ba2..6eb3b64 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -104,7 +104,7 @@
val dozeTimeTick: Flow<Long> = repository.dozeTimeTick
/** Whether Always-on Display mode is available. */
- val isAodAvailable: Flow<Boolean> = repository.isAodAvailable
+ val isAodAvailable: StateFlow<Boolean> = repository.isAodAvailable
/** Doze transition information. */
val dozeTransitionModel: Flow<DozeTransitionModel> = repository.dozeTransitionModel
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
deleted file mode 100644
index cd6ab31..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
+++ /dev/null
@@ -1,69 +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.systemui.keyguard.domain.interactor
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.emptyFlow
-
-/**
- * Implementation of the interactor that noops all face auth operations.
- *
- * This is required for SystemUI variants that do not support face authentication but still inject
- * other SysUI components that depend on [KeyguardFaceAuthInteractor]
- */
-@SysUISingleton
-class NoopKeyguardFaceAuthInteractor @Inject constructor() : KeyguardFaceAuthInteractor {
- override val authenticationStatus: Flow<FaceAuthenticationStatus>
- get() = emptyFlow()
- override val detectionStatus: Flow<FaceDetectionStatus>
- get() = emptyFlow()
-
- override fun canFaceAuthRun(): Boolean = false
-
- override fun isRunning(): Boolean = false
-
- override fun isLockedOut(): Boolean = false
-
- override fun isFaceAuthEnabledAndEnrolled(): Boolean = false
-
- override fun isFaceAuthStrong(): Boolean = false
-
- override fun isAuthenticated(): Boolean = false
-
- override fun registerListener(listener: FaceAuthenticationListener) {}
-
- override fun unregisterListener(listener: FaceAuthenticationListener) {}
-
- override fun onUdfpsSensorTouched() {}
-
- override fun onAssistantTriggeredOnLockScreen() {}
-
- override fun onDeviceLifted() {}
-
- override fun onQsExpansionStared() {}
-
- override fun onNotificationPanelClicked() {}
-
- override fun onSwipeUpOnBouncer() {}
- override fun onPrimaryBouncerUserInput() {}
- override fun onAccessibilityAction() {}
- override fun onWalletLaunched() = Unit
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SysUiFaceAuthenticateOptions.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SysUiFaceAuthenticateOptions.kt
index 942cd60..b3d0f918 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SysUiFaceAuthenticateOptions.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SysUiFaceAuthenticateOptions.kt
@@ -32,7 +32,7 @@
import android.os.PowerManager.WAKE_REASON_UNKNOWN
import android.util.Log
import com.android.internal.logging.UiEventLogger
-import com.android.keyguard.FaceAuthUiEvent
+import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
/**
* Wrapper for [FaceAuthenticateOptions] to convert SystemUI values to their corresponding value in
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
index 5344696b..24d0602 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
@@ -30,11 +30,11 @@
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule
-import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeClockSection
import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeMediaSection
import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeNotificationStackScrollLayoutSection
+import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeSmartspaceSection
import com.android.systemui.util.kotlin.getOrNull
import java.util.Optional
import javax.inject.Inject
@@ -62,7 +62,7 @@
aodNotificationIconsSection: AodNotificationIconsSection,
aodBurnInSection: AodBurnInSection,
communalTutorialIndicatorSection: CommunalTutorialIndicatorSection,
- smartspaceSection: SmartspaceSection,
+ smartspaceSection: SplitShadeSmartspaceSection,
clockSection: SplitShadeClockSection,
mediaSection: SplitShadeMediaSection,
) : KeyguardBlueprint {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeSmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeSmartspaceSection.kt
new file mode 100644
index 0000000..8728ada
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeSmartspaceSection.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.view.layout.sections
+
+import android.content.Context
+import com.android.systemui.keyguard.KeyguardUnlockAnimationController
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
+import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
+import javax.inject.Inject
+
+/*
+ * We need this class for the splitShadeBlueprint so `addViews` and `removeViews` will be called
+ * when switching to and from splitShade.
+ */
+class SplitShadeSmartspaceSection
+@Inject
+constructor(
+ keyguardClockViewModel: KeyguardClockViewModel,
+ keyguardSmartspaceViewModel: KeyguardSmartspaceViewModel,
+ context: Context,
+ smartspaceController: LockscreenSmartspaceController,
+ keyguardUnlockAnimationController: KeyguardUnlockAnimationController,
+) :
+ SmartspaceSection(
+ keyguardClockViewModel,
+ keyguardSmartspaceViewModel,
+ context,
+ smartspaceController,
+ keyguardUnlockAnimationController,
+ )
diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
index 3c2facb..9e6c552 100644
--- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
@@ -2,9 +2,9 @@
import android.hardware.face.FaceManager
import android.hardware.face.FaceSensorPropertiesInternal
-import com.android.keyguard.FaceAuthUiEvent
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
+import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
import com.android.systemui.log.core.LogLevel.DEBUG
import com.android.systemui.log.dagger.FaceAuthLog
import com.android.systemui.power.shared.model.WakeSleepReason
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 0d5ba64..24cb8ff 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -18,7 +18,9 @@
import android.os.Build;
+import com.android.systemui.common.data.repository.PackageChangeRepository;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepositoryImpl;
import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.LogBufferFactory;
import com.android.systemui.log.LogcatEchoTracker;
@@ -461,7 +463,7 @@
/**
* Provides a {@link LogBuffer} for use by
- * {@link com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepositoryImpl}.
+ * {@link DeviceEntryFaceAuthRepositoryImpl}.
*/
@Provides
@SysUISingleton
@@ -600,4 +602,12 @@
public static LogBuffer provideQBluetoothTileDialogLogBuffer(LogBufferFactory factory) {
return factory.create("BluetoothTileDialogLog", 50);
}
+
+ /** Provides a {@link LogBuffer} for {@link PackageChangeRepository} */
+ @Provides
+ @SysUISingleton
+ @PackageChangeRepoLog
+ public static LogBuffer providePackageChangeRepoLogBuffer(LogBufferFactory factory) {
+ return factory.create("PackageChangeRepo", 50);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/PackageChangeRepoLog.kt
similarity index 73%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
rename to packages/SystemUI/src/com/android/systemui/log/dagger/PackageChangeRepoLog.kt
index efc7431..93b776c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/PackageChangeRepoLog.kt
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.impl.custom.di.bound
+package com.android.systemui.log.dagger
+import com.android.systemui.common.data.repository.PackageChangeRepository
+import com.android.systemui.log.LogBuffer
import javax.inject.Qualifier
-/** User associated with current custom tile binding. */
+/** A [LogBuffer] for [PackageChangeRepository]. */
@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
-annotation class CustomTileUser
+annotation class PackageChangeRepoLog
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
index b98e9c2..5caa27f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
@@ -81,9 +81,12 @@
/** Set from the notification and used as fallback when PlaybackState cannot be determined */
val isClearable: Boolean = true,
- /** Timestamp when this player was last active. */
+ /** Milliseconds since boot when this player was last active. */
var lastActive: Long = 0L,
+ /** Timestamp in milliseconds when this player was created. */
+ var createdTimestampMillis: Long = 0L,
+
/** Instance ID for logging purposes */
val instanceId: InstanceId,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 3e8b49d..47df3b7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -400,7 +400,12 @@
val oldKey = findExistingEntry(key, sbn.packageName)
if (oldKey == null) {
val instanceId = logger.getNewInstanceId()
- val temp = LOADING.copy(packageName = sbn.packageName, instanceId = instanceId)
+ val temp =
+ LOADING.copy(
+ packageName = sbn.packageName,
+ instanceId = instanceId,
+ createdTimestampMillis = systemClock.currentTimeMillis(),
+ )
mediaEntries.put(key, temp)
isNewlyActiveEntry = true
} else if (oldKey != key) {
@@ -454,7 +459,8 @@
resumeAction = action,
hasCheckedForResume = true,
instanceId = instanceId,
- appUid = appUid
+ appUid = appUid,
+ createdTimestampMillis = systemClock.currentTimeMillis(),
)
mediaEntries.put(packageName, resumeData)
logSingleVsMultipleMediaAdded(appUid, packageName, instanceId)
@@ -732,6 +738,7 @@
val mediaAction = getResumeMediaAction(resumeAction)
val lastActive = systemClock.elapsedRealtime()
+ val createdTimestampMillis = currentEntry?.createdTimestampMillis ?: 0L
foregroundExecutor.execute {
onMediaDataLoaded(
packageName,
@@ -757,6 +764,7 @@
notificationKey = packageName,
hasCheckedForResume = true,
lastActive = lastActive,
+ createdTimestampMillis = createdTimestampMillis,
instanceId = instanceId,
appUid = appUid,
isExplicit = isExplicit,
@@ -907,6 +915,7 @@
}
val lastActive = systemClock.elapsedRealtime()
+ val createdTimestampMillis = currentEntry?.createdTimestampMillis ?: 0L
foregroundExecutor.execute {
val resumeAction: Runnable? = mediaEntries[key]?.resumeAction
val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true
@@ -937,6 +946,7 @@
isPlaying = isPlaying,
isClearable = !sbn.isOngoing,
lastActive = lastActive,
+ createdTimestampMillis = createdTimestampMillis,
instanceId = instanceId,
appUid = appUid,
isExplicit = isExplicit,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
index 6ec46f6..df6843d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
@@ -61,6 +61,7 @@
import com.android.systemui.assist.AssistManager;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.res.R;
+import com.android.systemui.shared.navigationbar.KeyButtonRipple;
import com.android.systemui.shared.system.QuickStepContract;
public class KeyButtonView extends ImageView implements ButtonInterface {
diff --git a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakeSleepReason.kt b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakeSleepReason.kt
index faf9fbe..75055668 100644
--- a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakeSleepReason.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakeSleepReason.kt
@@ -51,7 +51,10 @@
BIOMETRIC(isTouch = false, PowerManager.WAKE_REASON_BIOMETRIC),
/** Something else happened to wake up or sleep the device. */
- OTHER(isTouch = false, PowerManager.WAKE_REASON_UNKNOWN);
+ OTHER(isTouch = false, PowerManager.WAKE_REASON_UNKNOWN),
+
+ /** Device goes to sleep due to folding of a foldable device. */
+ FOLD(isTouch = false, PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD);
companion object {
fun fromPowerManagerWakeReason(reason: Int): WakeSleepReason {
@@ -72,6 +75,7 @@
fun fromPowerManagerSleepReason(reason: Int): WakeSleepReason {
return when (reason) {
PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON -> POWER_BUTTON
+ PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD -> FOLD
else -> OTHER
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 3e50dd3..ac0bd29 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -49,6 +49,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -231,32 +232,39 @@
if (!collapsedView && mQsTileRevealController != null) {
mQsTileRevealController.updateRevealedTiles(tiles);
}
- boolean shouldChange = false;
+ boolean shouldChangeAll = false;
+ // If the new tiles are a prefix of the old tiles, we delete the extra tiles (from the old).
+ // If not (even if they share a prefix) we remove all and add all the new ones.
if (tiles.size() <= mRecords.size()) {
int i = 0;
+ // Iterate through the requested tiles and check if they are the same as the existing
+ // tiles.
for (QSTile tile : tiles) {
if (tile != mRecords.get(i).tile) {
- shouldChange = true;
+ shouldChangeAll = true;
break;
}
i++;
}
- // If the first tiles are the same as the new ones, remove any extras.
- if (!shouldChange) {
- while (i < mRecords.size()) {
- QSPanelControllerBase.TileRecord record = mRecords.get(i);
+ // If the first tiles are the same as the new ones, we reuse them and remove any extra
+ // tiles.
+ if (!shouldChangeAll && i < mRecords.size()) {
+ List<TileRecord> extraRecords = mRecords.subList(i, mRecords.size());
+ for (QSPanelControllerBase.TileRecord record : extraRecords) {
mView.removeTile(record);
record.tile.removeCallback(record.callback);
- i++;
}
+ extraRecords.clear();
mCachedSpecs = getTilesSpecs();
}
} else {
- shouldChange = true;
+ shouldChangeAll = true;
}
- if (shouldChange) {
+ // If we detected that the existing tiles are different than the requested tiles, clear them
+ // and add the new tiles.
+ if (shouldChangeAll) {
for (QSPanelControllerBase.TileRecord record : mRecords) {
mView.removeTile(record);
record.tile.removeCallback(record.callback);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java
index 36dc743..a01d658 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java
@@ -67,9 +67,7 @@
synchronized (mListeners) {
if (setting != null && mListeners.size() != 0) {
if (setting.equals(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED)) {
- for (Listener listener : mListeners) {
- listener.onActivated(mManager.isReduceBrightColorsActivated());
- }
+ dispatchOnActivated(mManager.isReduceBrightColorsActivated());
}
}
}
@@ -125,6 +123,13 @@
mManager.setReduceBrightColorsActivated(activated);
}
+ private void dispatchOnActivated(boolean activated) {
+ ArrayList<Listener> copy = new ArrayList<>(mListeners);
+ for (Listener l : copy) {
+ l.onActivated(activated);
+ }
+ }
+
/**
* Listener invoked whenever the Reduce Bright Colors settings are changed.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index 47b0624..a45d6f6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -259,7 +259,11 @@
private State getState(Collection<QSTile> tiles, String spec) {
for (QSTile tile : tiles) {
if (spec.equals(tile.getTileSpec())) {
- return tile.getState().copy();
+ if (tile.isTileReady()) {
+ return tile.getState().copy();
+ } else {
+ return null;
+ }
}
}
return null;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
index 5d28c8c..957cb1e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
@@ -319,7 +319,7 @@
override fun dumpProto(systemUIProtoDump: SystemUIProtoDump, args: Array<String>) {
val data =
- currentTiles.value.map { it.tile.state }.mapNotNull { it.toProto() }.toTypedArray()
+ currentTiles.value.map { it.tile.state }.mapNotNull { it?.toProto() }.toTypedArray()
systemUIProtoDump.tiles = data
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
index 840db26..fc06090 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
@@ -63,6 +63,10 @@
InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
)
}
- activityStarter.postStartActivityDismissingKeyguard(pendingIntent, animationController)
+ activityStarter.startPendingIntentMaybeDismissingKeyguard(
+ pendingIntent,
+ null,
+ animationController
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
index 0a9a6d3..bc016bd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
@@ -158,6 +158,33 @@
)
}
+ fun logError(
+ tileSpec: TileSpec,
+ message: String,
+ error: Throwable,
+ ) {
+ tileSpec
+ .getLogBuffer()
+ .log(
+ tileSpec.getLogTag(),
+ LogLevel.ERROR,
+ {},
+ { message },
+ error,
+ )
+ }
+
+ fun logCustomTileUserActionDelivered(tileSpec: TileSpec) {
+ tileSpec
+ .getLogBuffer()
+ .log(
+ tileSpec.getLogTag(),
+ LogLevel.DEBUG,
+ {},
+ { "user action delivered to the service" },
+ )
+ }
+
private fun TileSpec.getLogTag(): String = "${TAG_FORMAT_PREFIX}_${this.spec}"
private fun TileSpec.getLogBuffer(): LogBuffer =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
index 382cfe2..6c9a8a4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
@@ -56,7 +56,7 @@
override fun createTile(tileSpec: String): QSTile? {
val viewModel: QSTileViewModel =
when (val spec = TileSpec.create(tileSpec)) {
- is TileSpec.CustomTileSpec -> null
+ is TileSpec.CustomTileSpec -> createCustomTileViewModel(spec)
is TileSpec.PlatformTileSpec -> tileMap[tileSpec]?.get()
is TileSpec.Invalid -> null
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
index e075e76..2b8c335 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
@@ -24,6 +24,7 @@
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
+import com.android.systemui.util.time.SystemClock
import java.time.Instant
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
@@ -36,10 +37,12 @@
constructor(
@Main private val resources: Resources,
private val theme: Theme,
+ private val clock: SystemClock,
) : QSTileDataToStateMapper<AlarmTileModel> {
companion object {
val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("E hh:mm a")
val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("E HH:mm")
+ val formatterDateOnly: DateTimeFormatter = DateTimeFormatter.ofPattern("E MMM d")
}
override fun map(config: QSTileConfig, data: AlarmTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
@@ -47,14 +50,32 @@
is AlarmTileModel.NextAlarmSet -> {
activationState = QSTileState.ActivationState.ACTIVE
- val localDateTime =
+ val alarmDateTime =
LocalDateTime.ofInstant(
Instant.ofEpochMilli(data.alarmClockInfo.triggerTime),
TimeZone.getDefault().toZoneId()
)
- secondaryLabel =
- if (data.is24HourFormat) formatter24Hour.format(localDateTime)
- else formatter12Hour.format(localDateTime)
+
+ val nowDateTime =
+ LocalDateTime.ofInstant(
+ Instant.ofEpochMilli(clock.currentTimeMillis()),
+ TimeZone.getDefault().toZoneId()
+ )
+
+ // Edge case: If it's 8:00:30 right now and alarm is requested for next week at
+ // 8:00:29, we still want to show the date. Same at nanosecond level.
+ val nextWeekThisTime = nowDateTime.plusWeeks(1).withSecond(0).withNano(0)
+
+ // is the alarm over a week away?
+ val shouldShowDateAndHideTime = alarmDateTime >= nextWeekThisTime
+
+ if (shouldShowDateAndHideTime) {
+ secondaryLabel = formatterDateOnly.format(alarmDateTime)
+ } else {
+ secondaryLabel =
+ if (data.is24HourFormat) formatter24Hour.format(alarmDateTime)
+ else formatter12Hour.format(alarmDateTime)
+ }
}
is AlarmTileModel.NoAlarmSet -> {
activationState = QSTileState.ActivationState.INACTIVE
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileInteractor.kt
deleted file mode 100644
index 14bf25d..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileInteractor.kt
+++ /dev/null
@@ -1,40 +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.systemui.qs.tiles.impl.custom
-
-import android.os.UserHandle
-import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
-import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
-import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
-import com.android.systemui.qs.tiles.impl.di.QSTileScope
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-@QSTileScope
-class CustomTileInteractor @Inject constructor() : QSTileDataInteractor<CustomTileDataModel> {
-
- override fun tileData(
- user: UserHandle,
- triggers: Flow<DataUpdateTrigger>
- ): Flow<CustomTileDataModel> {
- TODO("Not yet implemented")
- }
-
- override fun availability(user: UserHandle): Flow<Boolean> {
- TODO("Not yet implemented")
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileMapper.kt
deleted file mode 100644
index e23a5c2..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileMapper.kt
+++ /dev/null
@@ -1,32 +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.systemui.qs.tiles.impl.custom
-
-import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
-import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
-import com.android.systemui.qs.tiles.impl.di.QSTileScope
-import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
-import com.android.systemui.qs.tiles.viewmodel.QSTileState
-import javax.inject.Inject
-
-@QSTileScope
-class CustomTileMapper @Inject constructor() : QSTileDataToStateMapper<CustomTileDataModel> {
-
- override fun map(config: QSTileConfig, data: CustomTileDataModel): QSTileState {
- TODO("Not yet implemented")
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileUserActionInteractor.kt
deleted file mode 100644
index f34704b..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileUserActionInteractor.kt
+++ /dev/null
@@ -1,32 +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.systemui.qs.tiles.impl.custom
-
-import com.android.systemui.qs.tiles.base.interactor.QSTileInput
-import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
-import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
-import com.android.systemui.qs.tiles.impl.di.QSTileScope
-import javax.inject.Inject
-
-@QSTileScope
-class CustomTileUserActionInteractor @Inject constructor() :
- QSTileUserActionInteractor<CustomTileDataModel> {
-
- override suspend fun handleInput(input: QSTileInput<CustomTileDataModel>) {
- TODO("Not yet implemented")
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileComponent.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileComponent.kt
index 88bc8fa..7b099c2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileComponent.kt
@@ -16,7 +16,10 @@
package com.android.systemui.qs.tiles.impl.custom.di
+import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTilePackageUpdatesRepository
import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
+import com.android.systemui.qs.tiles.impl.custom.domain.interactor.CustomTileInteractor
+import com.android.systemui.qs.tiles.impl.custom.domain.interactor.CustomTileServiceInteractor
import com.android.systemui.qs.tiles.impl.di.QSTileComponent
import com.android.systemui.qs.tiles.impl.di.QSTileScope
import dagger.Subcomponent
@@ -25,6 +28,12 @@
@Subcomponent(modules = [QSTileConfigModule::class, CustomTileModule::class])
interface CustomTileComponent : QSTileComponent<CustomTileDataModel> {
+ fun customTileInterfaceInteractor(): CustomTileServiceInteractor
+
+ fun customTileInteractor(): CustomTileInteractor
+
+ fun customTilePackageUpdatesRepository(): CustomTilePackageUpdatesRepository
+
@Subcomponent.Builder
interface Builder {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt
index ba8b23a..196fa12 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt
@@ -20,14 +20,16 @@
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
import com.android.systemui.qs.tiles.base.viewmodel.QSTileCoroutineScopeFactory
-import com.android.systemui.qs.tiles.impl.custom.CustomTileInteractor
-import com.android.systemui.qs.tiles.impl.custom.CustomTileMapper
-import com.android.systemui.qs.tiles.impl.custom.CustomTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepository
import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepositoryImpl
+import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTilePackageUpdatesRepository
+import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTilePackageUpdatesRepositoryImpl
import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileRepository
import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileRepositoryImpl
+import com.android.systemui.qs.tiles.impl.custom.domain.CustomTileMapper
import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
+import com.android.systemui.qs.tiles.impl.custom.domain.interactor.CustomTileDataInteractor
+import com.android.systemui.qs.tiles.impl.custom.domain.interactor.CustomTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.di.QSTileScope
import dagger.Binds
import dagger.Module
@@ -40,7 +42,7 @@
@Binds
fun bindDataInteractor(
- dataInteractor: CustomTileInteractor
+ dataInteractor: CustomTileDataInteractor
): QSTileDataInteractor<CustomTileDataModel>
@Binds
@@ -58,6 +60,11 @@
@Binds fun bindCustomTileRepository(impl: CustomTileRepositoryImpl): CustomTileRepository
+ @Binds
+ abstract fun bindCustomTilePackageUpdatesRepository(
+ impl: CustomTilePackageUpdatesRepositoryImpl
+ ): CustomTilePackageUpdatesRepository
+
companion object {
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundComponent.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundComponent.kt
deleted file mode 100644
index d382d20..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundComponent.kt
+++ /dev/null
@@ -1,36 +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.systemui.qs.tiles.impl.custom.di.bound
-
-import android.os.UserHandle
-import dagger.BindsInstance
-import dagger.Subcomponent
-import kotlinx.coroutines.CoroutineScope
-
-/** @see CustomTileBoundScope */
-@CustomTileBoundScope
-@Subcomponent(modules = [CustomTileBoundModule::class])
-interface CustomTileBoundComponent {
-
- @Subcomponent.Builder
- interface Builder {
- @BindsInstance fun user(@CustomTileUser user: UserHandle): Builder
- @BindsInstance fun coroutineScope(@CustomTileBoundScope scope: CoroutineScope): Builder
-
- fun build(): CustomTileBoundComponent
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundModule.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundModule.kt
deleted file mode 100644
index 889424a..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundModule.kt
+++ /dev/null
@@ -1,31 +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.systemui.qs.tiles.impl.custom.di.bound
-
-import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTilePackageUpdatesRepository
-import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTilePackageUpdatesRepositoryImpl
-import dagger.Binds
-import dagger.Module
-
-@Module
-interface CustomTileBoundModule {
-
- @Binds
- fun bindCustomTilePackageUpdatesRepository(
- impl: CustomTilePackageUpdatesRepositoryImpl
- ): CustomTilePackageUpdatesRepository
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundScope.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundScope.kt
deleted file mode 100644
index 4a4ba2b..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundScope.kt
+++ /dev/null
@@ -1,29 +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.systemui.qs.tiles.impl.custom.di.bound
-
-import javax.inject.Scope
-
-/**
- * Scope annotation for bound custom tile scope. This scope lives when a particular
- * [com.android.systemui.qs.external.CustomTile] is listening and bound to the
- * [android.service.quicksettings.TileService].
- */
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-@Scope
-annotation class CustomTileBoundScope
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt
new file mode 100644
index 0000000..875079c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.custom.domain
+
+import android.annotation.SuppressLint
+import android.app.IUriGrantsManager
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import android.service.quicksettings.Tile
+import android.widget.Button
+import android.widget.Switch
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import javax.inject.Inject
+
+@SysUISingleton
+class CustomTileMapper
+@Inject
+constructor(
+ private val context: Context,
+ private val uriGrantsManager: IUriGrantsManager,
+) : QSTileDataToStateMapper<CustomTileDataModel> {
+
+ override fun map(config: QSTileConfig, data: CustomTileDataModel): QSTileState {
+ val userContext = context.createContextAsUser(UserHandle(data.user.identifier), 0)
+
+ val iconResult =
+ getIconProvider(
+ userContext = userContext,
+ icon = data.tile.icon,
+ callingAppUid = data.callingAppUid,
+ packageName = data.componentName.packageName,
+ defaultIcon = data.defaultTileIcon,
+ )
+
+ return QSTileState.build(iconResult.iconProvider, data.tile.label) {
+ var tileState: Int = data.tile.state
+ if (data.hasPendingBind) {
+ tileState = Tile.STATE_UNAVAILABLE
+ }
+
+ icon = iconResult.iconProvider
+ activationState =
+ if (iconResult.failedToLoad) {
+ QSTileState.ActivationState.INACTIVE
+ } else {
+ QSTileState.ActivationState.valueOf(tileState)
+ }
+
+ if (!data.tile.subtitle.isNullOrEmpty()) {
+ secondaryLabel = data.tile.subtitle
+ }
+
+ contentDescription = data.tile.contentDescription
+ stateDescription = data.tile.stateDescription
+
+ if (!data.isToggleable) {
+ sideViewIcon = QSTileState.SideViewIcon.Chevron
+ }
+
+ supportedActions =
+ if (tileState == Tile.STATE_UNAVAILABLE) {
+ setOf(QSTileState.UserAction.LONG_CLICK)
+ } else {
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ }
+ expandedAccessibilityClass =
+ if (data.isToggleable) {
+ Switch::class
+ } else {
+ Button::class
+ }
+ }
+ }
+
+ @SuppressLint("MissingPermission") // android.permission.INTERACT_ACROSS_USERS_FULL
+ private fun getIconProvider(
+ userContext: Context,
+ icon: android.graphics.drawable.Icon?,
+ callingAppUid: Int,
+ packageName: String,
+ defaultIcon: android.graphics.drawable.Icon?,
+ ): IconResult {
+ var failedToLoad = false
+ val drawable: Drawable? =
+ try {
+ icon?.loadDrawableCheckingUriGrant(
+ userContext,
+ uriGrantsManager,
+ callingAppUid,
+ packageName,
+ )
+ } catch (e: Exception) {
+ failedToLoad = true
+ null
+ } ?: defaultIcon?.loadDrawable(userContext)
+ return IconResult(
+ {
+ drawable?.constantState?.newDrawable()?.let {
+ Icon.Loaded(it, contentDescription = null)
+ }
+ },
+ failedToLoad,
+ )
+ }
+
+ class IconResult(
+ val iconProvider: () -> Icon?,
+ val failedToLoad: Boolean,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/entity/CustomTileDataModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/entity/CustomTileDataModel.kt
index f095c01..5b6ff1e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/entity/CustomTileDataModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/entity/CustomTileDataModel.kt
@@ -20,16 +20,14 @@
import android.graphics.drawable.Icon
import android.os.UserHandle
import android.service.quicksettings.Tile
-import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileBoundComponent
data class CustomTileDataModel(
val user: UserHandle,
val componentName: ComponentName,
val tile: Tile,
+ val isToggleable: Boolean,
val callingAppUid: Int,
val hasPendingBind: Boolean,
- val shouldShowChevron: Boolean,
- val defaultTileLabel: CharSequence?,
- val defaultTileIcon: Icon?,
- val component: CustomTileBoundComponent,
+ val defaultTileLabel: CharSequence,
+ val defaultTileIcon: Icon,
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt
new file mode 100644
index 0000000..cff95d8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.custom.domain.interactor
+
+import android.os.UserHandle
+import android.service.quicksettings.Tile
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
+import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepository
+import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTilePackageUpdatesRepository
+import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
+import com.android.systemui.qs.tiles.impl.di.QSTileScope
+import com.android.systemui.user.data.repository.UserRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.launch
+
+@QSTileScope
+@OptIn(ExperimentalCoroutinesApi::class)
+class CustomTileDataInteractor
+@Inject
+constructor(
+ private val tileSpec: TileSpec.CustomTileSpec,
+ private val defaultsRepository: CustomTileDefaultsRepository,
+ private val serviceInteractor: CustomTileServiceInteractor,
+ private val customTileInteractor: CustomTileInteractor,
+ private val packageUpdatesRepository: CustomTilePackageUpdatesRepository,
+ userRepository: UserRepository,
+ @QSTileScope private val tileScope: CoroutineScope,
+) : QSTileDataInteractor<CustomTileDataModel> {
+
+ private val mutableUserFlow = MutableStateFlow(userRepository.getSelectedUserInfo().userHandle)
+ private val bindingFlow =
+ mutableUserFlow
+ .flatMapLatest { user ->
+ ConflatedCallbackFlow.conflatedCallbackFlow {
+ serviceInteractor.setUser(user)
+
+ // Wait for the CustomTileInteractor to become initialized first, because
+ // binding
+ // the service might access it
+ customTileInteractor.initForUser(user)
+ // Bind the TileService for not active tile
+ serviceInteractor.bindOnStart()
+
+ packageUpdatesRepository
+ .getPackageChangesForUser(user)
+ .onEach {
+ defaultsRepository.requestNewDefaults(
+ user,
+ tileSpec.componentName,
+ true
+ )
+ }
+ .launchIn(this)
+
+ send(Unit)
+ awaitClose { serviceInteractor.unbind() }
+ }
+ }
+ .shareIn(tileScope, SharingStarted.WhileSubscribed())
+
+ init {
+ // Initialize binding once to flush all the pending messages inside
+ // CustomTileServiceInteractor and then unbind if the tile data isn't observed. This ensures
+ // that all the interactors are loaded and warmed up before binding.
+ tileScope.launch { bindingFlow.first() }
+ }
+
+ override fun tileData(
+ user: UserHandle,
+ triggers: Flow<DataUpdateTrigger>
+ ): Flow<CustomTileDataModel> {
+ tileScope.launch { mutableUserFlow.emit(user) }
+ return bindingFlow.combine(triggers) { _, _ -> }.flatMapLatest { dataFlow(user) }
+ }
+
+ private fun dataFlow(user: UserHandle): Flow<CustomTileDataModel> =
+ combine(
+ serviceInteractor.refreshEvents.onStart { emit(Unit) },
+ serviceInteractor.callingAppIds,
+ customTileInteractor.getTiles(user),
+ defaultsRepository.defaults(user).mapNotNull { it as? CustomTileDefaults.Result },
+ ) { _: Unit, callingAppId: Int, tile: Tile, defaults: CustomTileDefaults.Result ->
+ CustomTileDataModel(
+ user = user,
+ componentName = tileSpec.componentName,
+ tile = tile,
+ callingAppUid = callingAppId,
+ hasPendingBind = serviceInteractor.hasPendingBind(),
+ defaultTileLabel = defaults.label,
+ defaultTileIcon = defaults.icon,
+ isToggleable = customTileInteractor.isTileToggleable(),
+ )
+ }
+
+ override fun availability(user: UserHandle): Flow<Boolean> =
+ with(defaultsRepository) {
+ requestNewDefaults(user, tileSpec.componentName)
+ return defaults(user).map { it is CustomTileDefaults.Result }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt
index 10b012d..fd96fc5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt
@@ -19,12 +19,14 @@
import android.os.UserHandle
import android.service.quicksettings.Tile
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepository
import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileRepository
import com.android.systemui.qs.tiles.impl.di.QSTileScope
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
@@ -32,21 +34,29 @@
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
/** Manages updates of the [Tile] assigned for the current custom tile. */
@QSTileScope
class CustomTileInteractor
@Inject
constructor(
+ private val tileSpec: TileSpec.CustomTileSpec,
private val defaultsRepository: CustomTileDefaultsRepository,
private val customTileRepository: CustomTileRepository,
@QSTileScope private val tileScope: CoroutineScope,
@Background private val backgroundContext: CoroutineContext,
) {
+ private val userMutex = Mutex()
private val tileUpdates =
MutableSharedFlow<Tile>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+ private var currentUser: UserHandle? = null
+ private var updatesJob: Job? = null
+
/** [Tile] updates. [updateTile] to emit a new one. */
fun getTiles(user: UserHandle): Flow<Tile> = customTileRepository.getTiles(user)
@@ -55,7 +65,7 @@
*
* @throws IllegalStateException when the repository stores a tile for another user. This means
* the tile hasn't been updated for the current user. Can happen when this is accessed before
- * [init] returns.
+ * [initForUser] returns.
*/
fun getTile(user: UserHandle): Tile =
customTileRepository.getTile(user)
@@ -67,45 +77,60 @@
suspend fun isTileToggleable(): Boolean = customTileRepository.isTileToggleable()
/**
- * Initializes the repository for the current user. Suspends until it's safe to call [tile]
+ * Initializes the repository for the current user. Suspends until it's safe to call [getTile]
* which needs at least one of the following:
* - defaults are loaded;
* - receive tile update in [updateTile];
* - restoration happened for a persisted tile.
*/
suspend fun initForUser(user: UserHandle) {
- launchUpdates(user)
- customTileRepository.restoreForTheUserIfNeeded(user, customTileRepository.isTileActive())
- // Suspend to make sure it gets the tile from one of the sources: restoration, defaults, or
- // tile update.
- customTileRepository.getTiles(user).firstOrNull()
+ userMutex.withLock {
+ if (currentUser == user) {
+ return
+ }
+ updatesJob?.cancel()
+ defaultsRepository.requestNewDefaults(user, tileSpec.componentName)
+ launchUpdates(user)
+ customTileRepository.restoreForTheUserIfNeeded(
+ user,
+ customTileRepository.isTileActive()
+ )
+ // Suspend to make sure it gets the tile from one of the sources: restoration, defaults,
+ // or
+ // tile update.
+ customTileRepository.getTiles(user).firstOrNull()
+ currentUser = user
+ }
}
private fun launchUpdates(user: UserHandle) {
- tileUpdates
- .onEach {
- customTileRepository.updateWithTile(
- user,
- it,
- customTileRepository.isTileActive(),
- )
+ updatesJob =
+ tileScope.launch {
+ tileUpdates
+ .onEach {
+ customTileRepository.updateWithTile(
+ user,
+ it,
+ customTileRepository.isTileActive(),
+ )
+ }
+ .flowOn(backgroundContext)
+ .launchIn(this)
+ defaultsRepository
+ .defaults(user)
+ .onEach {
+ customTileRepository.updateWithDefaults(
+ user,
+ it,
+ customTileRepository.isTileActive(),
+ )
+ }
+ .flowOn(backgroundContext)
+ .launchIn(this)
}
- .flowOn(backgroundContext)
- .launchIn(tileScope)
- defaultsRepository
- .defaults(user)
- .onEach {
- customTileRepository.updateWithDefaults(
- user,
- it,
- customTileRepository.isTileActive(),
- )
- }
- .flowOn(backgroundContext)
- .launchIn(tileScope)
}
- /** Updates current [Tile]. Emits a new event in [tiles]. */
+ /** Updates current [Tile]. Emits a new event in [getTiles]. */
fun updateTile(newTile: Tile) {
tileUpdates.tryEmit(newTile)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileServiceInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileServiceInteractor.kt
new file mode 100644
index 0000000..acff40f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileServiceInteractor.kt
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.custom.domain.interactor
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.os.IBinder
+import android.os.Process
+import android.os.RemoteException
+import android.os.UserHandle
+import android.service.quicksettings.IQSTileService
+import android.service.quicksettings.Tile
+import android.service.quicksettings.TileService
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.external.CustomTileInterface
+import com.android.systemui.qs.external.TileServiceManager
+import com.android.systemui.qs.external.TileServices
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.logging.QSTileLogger
+import com.android.systemui.qs.tiles.impl.di.QSTileScope
+import com.android.systemui.user.data.repository.UserRepository
+import dagger.Lazy
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.channels.produce
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.launch
+
+/**
+ * Communicates with [TileService] via [TileServiceManager] and [IQSTileService]. This interactor is
+ * also responsible for the binding to the [TileService].
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@QSTileScope
+class CustomTileServiceInteractor
+@Inject
+constructor(
+ private val tileSpec: TileSpec.CustomTileSpec,
+ private val activityStarter: ActivityStarter,
+ private val userActionInteractor: Lazy<CustomTileUserActionInteractor>,
+ private val customTileInteractor: CustomTileInteractor,
+ private val userRepository: UserRepository,
+ private val qsTileLogger: QSTileLogger,
+ private val tileServices: TileServices,
+ @QSTileScope private val tileScope: CoroutineScope,
+) {
+
+ private val tileReceivingInterface = ReceivingInterface()
+ private var tileServiceManager: TileServiceManager? = null
+ private val tileServiceInterface: IQSTileService
+ get() = getTileServiceManager().tileService
+
+ private var currentUser: UserHandle = userRepository.getSelectedUserInfo().userHandle
+ private var destructionJob: Job? = null
+
+ val callingAppIds: Flow<Int>
+ get() = tileReceivingInterface.mutableCallingAppIds
+ val refreshEvents: Flow<Unit>
+ get() = tileReceivingInterface.mutableRefreshEvents
+
+ /** Clears all pending binding for an active tile and binds not active one. */
+ fun bindOnStart() {
+ try {
+ with(getTileServiceManager()) {
+ if (isActiveTile) {
+ clearPendingBind()
+ } else {
+ setBindRequested(true)
+ tileServiceInterface.onStartListening()
+ }
+ }
+ } catch (e: RemoteException) {
+ qsTileLogger.logError(tileSpec, "Binding to the service failed", e)
+ }
+ }
+
+ /** Binds active tile WITHOUT CLEARING pending binds. */
+ fun bindOnClick() {
+ try {
+ with(getTileServiceManager()) {
+ if (isActiveTile) {
+ setBindRequested(true)
+ tileServiceInterface.onStartListening()
+ }
+ }
+ } catch (e: RemoteException) {
+ qsTileLogger.logError(tileSpec, "Binding to the service on click failed", e)
+ }
+ }
+
+ /** Releases resources held by the binding and prepares the interactor to be collected */
+ fun unbind() {
+ try {
+ with(userActionInteractor.get()) {
+ clearLastClickedView()
+ tileServiceInterface.onStopListening()
+ revokeToken(false)
+ setShowingDialog(false)
+ }
+ getTileServiceManager().setBindRequested(false)
+ } catch (e: RemoteException) {
+ qsTileLogger.logError(tileSpec, "Unbinding failed", e)
+ }
+ }
+
+ /**
+ * Checks if [TileServiceManager] has a pending [android.service.quicksettings.TileService]
+ * bind.
+ */
+ fun hasPendingBind(): Boolean = getTileServiceManager().hasPendingBind()
+
+ /** Sets a [user] for the custom tile to use. User change triggers service rebinding. */
+ fun setUser(user: UserHandle) {
+ if (user == currentUser) {
+ return
+ }
+ currentUser = user
+ destructionJob?.cancel()
+
+ tileServiceManager = null
+ }
+
+ /** Sends click event to [TileService] using [IQSTileService.onClick]. */
+ fun onClick(token: IBinder) {
+ tileServiceInterface.onClick(token)
+ }
+
+ private fun getTileServiceManager(): TileServiceManager =
+ synchronized(tileServices) {
+ if (tileServiceManager == null) {
+ tileServices
+ .getTileWrapper(tileReceivingInterface)
+ .also { destructionJob = createDestructionJob() }
+ .also { tileServiceManager = it }
+ } else {
+ tileServiceManager!!
+ }
+ }
+
+ /**
+ * This job used to free the resources when the [QSTileScope] coroutine scope gets cancelled by
+ * the View Model.
+ */
+ private fun createDestructionJob(): Job =
+ tileScope.launch {
+ produce<Unit> {
+ awaitClose {
+ userActionInteractor.get().revokeToken(true)
+ tileServices.freeService(tileReceivingInterface, getTileServiceManager())
+ destructionJob = null
+ }
+ }
+ }
+
+ private inner class ReceivingInterface : CustomTileInterface {
+
+ override val user: Int
+ get() = currentUser.identifier
+ override val qsTile: Tile
+ get() = customTileInteractor.getTile(currentUser)
+ override val component: ComponentName = tileSpec.componentName
+
+ val mutableCallingAppIds = MutableStateFlow(Process.INVALID_UID)
+ val mutableRefreshEvents = MutableSharedFlow<Unit>()
+
+ override fun getTileSpec(): String = tileSpec.spec
+
+ override fun refreshState() {
+ tileScope.launch { mutableRefreshEvents.emit(Unit) }
+ }
+
+ override fun updateTileState(tile: Tile, uid: Int) {
+ customTileInteractor.updateTile(tile)
+ mutableCallingAppIds.tryEmit(uid)
+ }
+
+ override fun onDialogShown() {
+ userActionInteractor.get().setShowingDialog(true)
+ }
+
+ override fun onDialogHidden() =
+ with(userActionInteractor.get()) {
+ setShowingDialog(false)
+ revokeToken(true)
+ }
+
+ override fun startActivityAndCollapse(pendingIntent: PendingIntent) {
+ userActionInteractor.get().startActivityAndCollapse(pendingIntent)
+ }
+
+ override fun startUnlockAndRun() {
+ activityStarter.postQSRunnableDismissingKeyguard {
+ tileServiceInterface.onUnlockComplete()
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt
new file mode 100644
index 0000000..c3e1fea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.custom.domain.interactor
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.net.Uri
+import android.os.Binder
+import android.os.IBinder
+import android.os.RemoteException
+import android.os.UserHandle
+import android.provider.Settings
+import android.service.quicksettings.TileService
+import android.view.IWindowManager
+import android.view.View
+import android.view.WindowManager
+import androidx.annotation.GuardedBy
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.base.logging.QSTileLogger
+import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
+import com.android.systemui.qs.tiles.impl.di.QSTileScope
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.settings.DisplayTracker
+import java.util.concurrent.atomic.AtomicReference
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.withContext
+
+@QSTileScope
+class CustomTileUserActionInteractor
+@Inject
+constructor(
+ private val context: Context,
+ private val tileSpec: TileSpec,
+ private val qsTileLogger: QSTileLogger,
+ private val windowManager: IWindowManager,
+ private val displayTracker: DisplayTracker,
+ private val qsTileIntentUserInputHandler: QSTileIntentUserInputHandler,
+ @Background private val backgroundContext: CoroutineContext,
+ private val serviceInteractor: CustomTileServiceInteractor,
+) : QSTileUserActionInteractor<CustomTileDataModel> {
+
+ private val token: IBinder = Binder()
+
+ @GuardedBy("token") private var isTokenGranted: Boolean = false
+ @GuardedBy("token") private var isShowingDialog: Boolean = false
+ private val lastClickedView: AtomicReference<View> = AtomicReference<View>()
+
+ override suspend fun handleInput(input: QSTileInput<CustomTileDataModel>) =
+ with(input) {
+ when (action) {
+ is QSTileUserAction.Click -> click(action.view, data.tile.activityLaunchForClick)
+ is QSTileUserAction.LongClick ->
+ longClick(user, action.view, data.componentName, data.tile.state)
+ }
+ qsTileLogger.logCustomTileUserActionDelivered(tileSpec)
+ }
+
+ private fun click(
+ view: View?,
+ activityLaunchForClick: PendingIntent?,
+ ) {
+ grantToken()
+ try {
+ // Bind active tile to deliver user action
+ serviceInteractor.bindOnClick()
+ if (activityLaunchForClick == null) {
+ lastClickedView.set(view)
+ serviceInteractor.onClick(token)
+ } else {
+ qsTileIntentUserInputHandler.handle(view, activityLaunchForClick)
+ }
+ } catch (e: RemoteException) {
+ qsTileLogger.logError(tileSpec, "Failed to deliver click", e)
+ }
+ }
+
+ fun revokeToken(ignoreShownDialog: Boolean) {
+ synchronized(token) {
+ if (isTokenGranted && (ignoreShownDialog || !isShowingDialog)) {
+ try {
+ windowManager.removeWindowToken(token, displayTracker.defaultDisplayId)
+ } catch (e: RemoteException) {
+ qsTileLogger.logError(tileSpec, "Failed to remove a window token", e)
+ }
+ isTokenGranted = false
+ }
+ }
+ }
+
+ fun setShowingDialog(isShowingDialog: Boolean) {
+ synchronized(token) { this.isShowingDialog = isShowingDialog }
+ }
+
+ fun startActivityAndCollapse(pendingIntent: PendingIntent) {
+ if (!pendingIntent.isActivity) {
+ return
+ }
+ if (!isTokenGranted) {
+ return
+ }
+ qsTileIntentUserInputHandler.handle(lastClickedView.getAndSet(null), pendingIntent)
+ }
+
+ fun clearLastClickedView() = lastClickedView.set(null)
+
+ private fun grantToken() {
+ synchronized(token) {
+ if (!isTokenGranted) {
+ try {
+ windowManager.addWindowToken(
+ token,
+ WindowManager.LayoutParams.TYPE_QS_DIALOG,
+ displayTracker.defaultDisplayId,
+ null /* options */
+ )
+ } catch (e: RemoteException) {
+ qsTileLogger.logError(tileSpec, "Failed to grant a window token", e)
+ }
+ isTokenGranted = true
+ }
+ }
+ }
+
+ private suspend fun longClick(
+ user: UserHandle,
+ view: View?,
+ componentName: ComponentName,
+ state: Int
+ ) {
+ val resolvedIntent: Intent? =
+ resolveIntent(
+ Intent(TileService.ACTION_QS_TILE_PREFERENCES).apply {
+ setPackage(componentName.packageName)
+ },
+ user,
+ )
+ ?.apply {
+ putExtra(Intent.EXTRA_COMPONENT_NAME, componentName)
+ putExtra(TileService.EXTRA_STATE, state)
+ }
+ if (resolvedIntent == null) {
+ qsTileIntentUserInputHandler.handle(
+ view,
+ Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
+ .setData(
+ Uri.fromParts(IntentFilter.SCHEME_PACKAGE, componentName.packageName, null)
+ )
+ )
+ } else {
+ qsTileIntentUserInputHandler.handle(view, resolvedIntent)
+ }
+ }
+
+ /**
+ * Returns an intent resolved by [android.content.pm.PackageManager.resolveActivityAsUser] or
+ * null.
+ */
+ private suspend fun resolveIntent(intent: Intent, user: UserHandle): Intent? =
+ withContext(backgroundContext) {
+ val activityInfo =
+ context.packageManager
+ .resolveActivityAsUser(intent, 0, user.identifier)
+ ?.activityInfo
+ activityInfo ?: return@withContext null
+ with(activityInfo) {
+ Intent(TileService.ACTION_QS_TILE_PREFERENCES).apply {
+ setClassName(packageName, name)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
index be1b740..b927e41 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
@@ -32,7 +32,7 @@
* // TODO(b/http://b/299909989): Clean up legacy mappings after the transition
*/
data class QSTileState(
- val icon: () -> Icon,
+ val icon: () -> Icon?,
val label: CharSequence,
val activationState: ActivationState,
val secondaryLabel: CharSequence?,
@@ -60,7 +60,7 @@
)
}
- fun build(icon: () -> Icon, label: CharSequence, build: Builder.() -> Unit): QSTileState =
+ fun build(icon: () -> Icon?, label: CharSequence, build: Builder.() -> Unit): QSTileState =
Builder(icon, label).apply(build).build()
}
@@ -108,7 +108,7 @@
}
class Builder(
- var icon: () -> Icon,
+ var icon: () -> Icon?,
var label: CharSequence,
) {
var activationState: ActivationState = ActivationState.INACTIVE
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
index ef3df48..226e2fa0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
@@ -45,7 +45,10 @@
*/
fun onUserChanged(user: UserHandle)
- /** Triggers the emission of the new [QSTileState] in a [state]. */
+ /**
+ * Triggers the emission of the new [QSTileState] in a [state]. The new value can still be
+ * skipped if there is no change.
+ */
fun forceUpdate()
/** Notifies underlying logic about user input. */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
index 977df81..4780a2e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
@@ -37,6 +37,7 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collectIndexed
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
@@ -60,27 +61,34 @@
private val listeningClients: MutableCollection<Any> = mutableSetOf()
// Cancels the jobs when the adapter is no longer alive
- private var availabilityJob: Job? = null
+ private var tileAdapterJob: Job? = null
// Cancels the jobs when clients stop listening
private var stateJob: Job? = null
init {
- availabilityJob =
+ tileAdapterJob =
applicationScope.launch {
- qsTileViewModel.isAvailable.collectIndexed { index, isAvailable ->
- if (!isAvailable) {
- qsHost.removeTile(tileSpec)
- }
- // qsTileViewModel.isAvailable flow often starts with isAvailable == true.
- // That's
- // why we only allow isAvailable == true once and throw an exception afterwards.
- if (index > 0 && isAvailable) {
- // See com.android.systemui.qs.pipeline.domain.model.AutoAddable for
- // additional
- // guidance on how to auto add your tile
- throw UnsupportedOperationException("Turning on tile is not supported now")
+ launch {
+ qsTileViewModel.isAvailable.collectIndexed { index, isAvailable ->
+ if (!isAvailable) {
+ qsHost.removeTile(tileSpec)
+ }
+ // qsTileViewModel.isAvailable flow often starts with isAvailable == true.
+ // That's
+ // why we only allow isAvailable == true once and throw an exception
+ // afterwards.
+ if (index > 0 && isAvailable) {
+ // See com.android.systemui.qs.pipeline.domain.model.AutoAddable for
+ // additional
+ // guidance on how to auto add your tile
+ throw UnsupportedOperationException(
+ "Turning on tile is not supported now"
+ )
+ }
}
}
+ // Warm up tile with some initial state
+ launch { qsTileViewModel.state.first() }
}
// QSTileHost doesn't call this when userId is initialized
@@ -185,7 +193,7 @@
override fun destroy() {
stateJob?.cancel()
- availabilityJob?.cancel()
+ tileAdapterJob?.cancel()
qsTileViewModel.destroy()
}
@@ -222,8 +230,9 @@
QSTile.BooleanState().apply {
spec = config.tileSpec.spec
label = viewModelState.label
- // This value is synthetic and doesn't have any meaning
- value = false
+ // This value is synthetic and doesn't have any meaning. It's only needed to satisfy
+ // CTS tests.
+ value = viewModelState.activationState == QSTileState.ActivationState.ACTIVE
secondaryLabel = viewModelState.secondaryLabel
handlesLongClick =
@@ -233,6 +242,7 @@
when (val stateIcon = viewModelState.icon()) {
is Icon.Loaded -> DrawableIcon(stateIcon.drawable)
is Icon.Resource -> ResourceIcon.get(stateIcon.res)
+ null -> null
}
}
state = viewModelState.activationState.legacyState
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 5abb4dd..c96651c 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
@@ -44,6 +44,7 @@
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
+import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
import com.android.systemui.util.asIndenting
import com.android.systemui.util.printSection
@@ -83,6 +84,7 @@
private val powerInteractor: PowerInteractor,
private val simBouncerInteractor: Lazy<SimBouncerInteractor>,
private val authenticationInteractor: Lazy<AuthenticationInteractor>,
+ private val windowController: NotificationShadeWindowController,
) : CoreStartable {
override fun start() {
@@ -92,6 +94,7 @@
automaticallySwitchScenes()
hydrateSystemUiState()
collectFalsingSignals()
+ hydrateWindowFocus()
} else {
sceneLogger.logFrameworkEnabled(
isEnabled = false,
@@ -348,6 +351,20 @@
}
}
+ /** Keeps the focus state of the window view up-to-date. */
+ private fun hydrateWindowFocus() {
+ applicationScope.launch {
+ sceneInteractor.transitionState
+ .mapNotNull { transitionState ->
+ (transitionState as? ObservableTransitionState.Idle)?.scene
+ }
+ .distinctUntilChanged()
+ .collect { sceneKey ->
+ windowController.setNotificationShadeFocusable(sceneKey != SceneKey.Gone)
+ }
+ }
+ }
+
private fun switchToScene(targetSceneKey: SceneKey, loggingReason: String) {
sceneInteractor.changeScene(
scene = SceneModel(targetSceneKey),
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index fb6bc38..a5c1cf1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -26,6 +26,7 @@
import static com.android.keyguard.KeyguardClockSwitch.SMALL;
import static com.android.systemui.Flags.keyguardBottomAreaRefactor;
import static com.android.systemui.Flags.migrateClocksToBlueprint;
+import static com.android.systemui.Flags.predictiveBackAnimateShade;
import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
import static com.android.systemui.classifier.Classifier.GENERIC;
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
@@ -121,6 +122,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.dump.DumpsysTableLogger;
@@ -131,7 +133,6 @@
import com.android.systemui.keyguard.KeyguardViewConfigurator;
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver;
@@ -327,7 +328,7 @@
private final PulseExpansionHandler mPulseExpansionHandler;
private final KeyguardBypassController mKeyguardBypassController;
private final KeyguardUpdateMonitor mUpdateMonitor;
- private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
+ private final DeviceEntryFaceAuthInteractor mDeviceEntryFaceAuthInteractor;
private final ConversationNotificationManager mConversationNotificationManager;
private final AuthController mAuthController;
private final MediaHierarchyManager mMediaHierarchyManager;
@@ -779,7 +780,7 @@
ActiveNotificationsInteractor activeNotificationsInteractor,
ShadeAnimationInteractor shadeAnimationInteractor,
KeyguardViewConfigurator keyguardViewConfigurator,
- KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
+ DeviceEntryFaceAuthInteractor deviceEntryFaceAuthInteractor,
SplitShadeStateController splitShadeStateController,
PowerInteractor powerInteractor,
KeyguardClockPositionAlgorithm keyguardClockPositionAlgorithm,
@@ -891,7 +892,7 @@
mShadeHeaderController = shadeHeaderController;
mLayoutInflater = layoutInflater;
mFeatureFlags = featureFlags;
- mAnimateBack = mFeatureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE);
+ mAnimateBack = predictiveBackAnimateShade();
mTrackpadGestureFeaturesEnabled = mFeatureFlags.isEnabled(Flags.TRACKPAD_GESTURE_FEATURES);
mFalsingCollector = falsingCollector;
mWakeUpCoordinator = coordinator;
@@ -936,7 +937,7 @@
mScreenOffAnimationController = screenOffAnimationController;
mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
mLastDownEvents = new NPVCDownEventState.Buffer(MAX_DOWN_EVENT_BUFFER_SIZE);
- mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
+ mDeviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor;
int currentMode = navigationModeController.addListener(
mode -> mIsGestureNavigation = QuickStepContract.isGesturalMode(mode));
@@ -2978,9 +2979,9 @@
mShadeLog.v("onMiddleClicked on Keyguard, mDozingOnDown: false");
// Try triggering face auth, this "might" run. Check
// KeyguardUpdateMonitor#shouldListenForFace to see when face auth won't run.
- mKeyguardFaceAuthInteractor.onNotificationPanelClicked();
+ mDeviceEntryFaceAuthInteractor.onNotificationPanelClicked();
- if (mKeyguardFaceAuthInteractor.canFaceAuthRun()) {
+ if (mDeviceEntryFaceAuthInteractor.canFaceAuthRun()) {
mUpdateMonitor.requestActiveUnlock(
ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT,
"lockScreenEmptySpaceTap");
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 8397caa..1dff99d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -66,9 +66,9 @@
import com.android.systemui.Dumpable;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.fragments.FragmentHostManager;
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
import com.android.systemui.media.controls.pipeline.MediaDataManager;
import com.android.systemui.media.controls.ui.MediaHierarchyManager;
@@ -152,7 +152,7 @@
private final RecordingController mRecordingController;
private final LockscreenGestureLogger mLockscreenGestureLogger;
private final ShadeLogger mShadeLog;
- private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
+ private final DeviceEntryFaceAuthInteractor mDeviceEntryFaceAuthInteractor;
private final CastController mCastController;
private final SplitShadeStateController mSplitShadeStateController;
private final InteractionJankMonitor mInteractionJankMonitor;
@@ -338,7 +338,7 @@
InteractionJankMonitor interactionJankMonitor,
ShadeLogger shadeLog,
DumpManager dumpManager,
- KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
+ DeviceEntryFaceAuthInteractor deviceEntryFaceAuthInteractor,
ShadeRepository shadeRepository,
ShadeInteractor shadeInteractor,
ActiveNotificationsInteractor activeNotificationsInteractor,
@@ -384,7 +384,7 @@
mLockscreenGestureLogger = lockscreenGestureLogger;
mMetricsLogger = metricsLogger;
mShadeLog = shadeLog;
- mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
+ mDeviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor;
mCastController = castController;
mInteractionJankMonitor = interactionJankMonitor;
mShadeRepository = shadeRepository;
@@ -972,7 +972,7 @@
// When expanding QS, let's authenticate the user if possible,
// this will speed up notification actions.
if (height == 0 && !mKeyguardStateController.canDismissLockScreen()) {
- mKeyguardFaceAuthInteractor.onQsExpansionStared();
+ mDeviceEntryFaceAuthInteractor.onQsExpansionStared();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 08415cb..d6d3e67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -31,6 +31,7 @@
import static com.android.systemui.DejankUtils.whitelistIpcs;
import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.IMPORTANT_MSG_MIN_DURATION;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_IS_DISMISSIBLE;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE;
@@ -178,6 +179,7 @@
private KeyguardInteractor mKeyguardInteractor;
private String mPersistentUnlockMessage;
private String mAlignmentIndication;
+ private boolean mForceIsDismissible;
private CharSequence mTrustGrantedIndication;
private CharSequence mTransientIndication;
private CharSequence mBiometricMessage;
@@ -442,6 +444,7 @@
// Update persistent messages. The following methods should only be called if we're on the
// lock screen:
+ updateForceIsDimissibileChanged();
updateLockScreenDisclosureMsg();
updateLockScreenOwnerInfo();
updateLockScreenBatteryMsg(animate);
@@ -458,6 +461,22 @@
updateDeviceEntryIndication(false);
}
+ private void updateForceIsDimissibileChanged() {
+ if (mForceIsDismissible) {
+ mRotateTextViewController.updateIndication(
+ INDICATION_IS_DISMISSIBLE,
+ new KeyguardIndication.Builder()
+ .setMessage(mContext.getResources().getString(
+ com.android.systemui.res.R.string.dismissible_keyguard_swipe)
+ )
+ .setTextColor(mInitialTextColorState)
+ .build(),
+ /* updateImmediately */ true);
+ } else {
+ mRotateTextViewController.hideIndication(INDICATION_IS_DISMISSIBLE);
+ }
+ }
+
private void updateLockScreenDisclosureMsg() {
if (mOrganizationOwnedDevice) {
mBackgroundExecutor.execute(() -> {
@@ -1311,6 +1330,12 @@
}
@Override
+ public void onForceIsDismissibleChanged(boolean forceIsDismissible) {
+ mForceIsDismissible = forceIsDismissible;
+ updateDeviceEntryIndication(false);
+ }
+
+ @Override
public void onTrustGrantedForCurrentUser(
boolean dismissKeyguard,
boolean newlyUnlocked,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index c9df317..9b8dd0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -275,7 +275,12 @@
updateLockscreenNotificationSetting();
updatePublicMode();
- mPresenter.onUserSwitched(mCurrentUserId);
+ if (mPresenter != null) {
+ mPresenter.onUserSwitched(mCurrentUserId);
+ } else {
+ Log.w(TAG, "user switch before setup with presenter",
+ new Exception());
+ }
for (UserChangedListener listener : mListeners) {
listener.onUserChanged(mCurrentUserId);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index a957095..32cd56c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.dagger;
+import static com.android.systemui.Flags.predictiveBackAnimateDialogs;
+
import android.content.Context;
import android.os.RemoteException;
import android.service.dreams.IDreamManager;
@@ -31,8 +33,6 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpHandler;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.media.controls.pipeline.MediaDataManager;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.settings.DisplayTracker;
@@ -230,11 +230,11 @@
/** */
@Provides
@SysUISingleton
- static AnimationFeatureFlags provideAnimationFeatureFlags(FeatureFlags featureFlags) {
+ static AnimationFeatureFlags provideAnimationFeatureFlags() {
return new AnimationFeatureFlags() {
@Override
public boolean isPredictiveBackQsDialogAnim() {
- return featureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM);
+ return predictiveBackAnimateDialogs();
}
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
index 4b89615..9fdd0bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
@@ -18,7 +18,7 @@
import android.os.PowerManager
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.statusbar.LockscreenShadeTransitionController
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 6f5058c..2e54512 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
@@ -366,8 +366,7 @@
@Override
public void onStatePostChange() {
- mView.updateSensitiveness(mStatusBarStateController.goingToFullShade(),
- mLockscreenUserManager.isAnyProfilePublicMode());
+ updateSensitivenessWithAnimation(mStatusBarStateController.goingToFullShade());
mView.onStatePostChange(mStatusBarStateController.fromShadeLocked());
if (!FooterViewRefactor.isEnabled()) {
updateImportantForAccessibility();
@@ -378,7 +377,7 @@
private final UserChangedListener mLockscreenUserChangeListener = new UserChangedListener() {
@Override
public void onUserChanged(int userId) {
- mView.updateSensitiveness(false, mLockscreenUserManager.isAnyProfilePublicMode());
+ updateSensitivenessWithAnimation(false);
mHistoryEnabled = null;
updateFooter();
}
@@ -388,7 +387,11 @@
* Recalculate sensitiveness without animation; called when waking up while keyguard occluded.
*/
public void updateSensitivenessForOccludedWakeup() {
- mView.updateSensitiveness(false, mLockscreenUserManager.isAnyProfilePublicMode());
+ updateSensitivenessWithAnimation(false);
+ }
+
+ private void updateSensitivenessWithAnimation(boolean animate) {
+ mView.updateSensitiveness(animate, mLockscreenUserManager.isAnyProfilePublicMode());
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 57d49b2..6e3aabf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -31,6 +31,7 @@
import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
+import static com.android.systemui.Flags.predictiveBackSysui;
import android.annotation.Nullable;
import android.app.ActivityOptions;
@@ -836,7 +837,7 @@
mLightRevealScrim = lightRevealScrim;
// Based on teamfood flag, turn predictive back dispatch on at runtime.
- if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI)) {
+ if (predictiveBackSysui()) {
mContext.getApplicationInfo().setEnableOnBackInvokedCallback(true);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ComponentSystemUIDialog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ComponentSystemUIDialog.kt
index 38a6d39..13d7924 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ComponentSystemUIDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ComponentSystemUIDialog.kt
@@ -34,7 +34,6 @@
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.flags.FeatureFlags
import com.android.systemui.model.SysUiState
/**
@@ -53,7 +52,6 @@
context: Context,
theme: Int,
dismissOnDeviceLock: Boolean,
- featureFlags: FeatureFlags,
dialogManager: SystemUIDialogManager,
sysUiState: SysUiState,
broadcastDispatcher: BroadcastDispatcher,
@@ -63,7 +61,6 @@
context,
theme,
dismissOnDeviceLock,
- featureFlags,
dialogManager,
sysUiState,
broadcastDispatcher,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index 32b3ac2..9f08633 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -28,7 +28,7 @@
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.Assert
@@ -43,13 +43,13 @@
*/
@SysUISingleton
class KeyguardLiftController @Inject constructor(
- private val context: Context,
- private val statusBarStateController: StatusBarStateController,
- private val asyncSensorManager: AsyncSensorManager,
- private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- private val keyguardFaceAuthInteractor: KeyguardFaceAuthInteractor,
- private val dumpManager: DumpManager,
- private val selectedUserInteractor: SelectedUserInteractor,
+ private val context: Context,
+ private val statusBarStateController: StatusBarStateController,
+ private val asyncSensorManager: AsyncSensorManager,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor,
+ private val dumpManager: DumpManager,
+ private val selectedUserInteractor: SelectedUserInteractor,
) : Dumpable, CoreStartable {
private val pickupSensor = asyncSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE)
@@ -75,7 +75,7 @@
// Not listening anymore since trigger events unregister themselves
isListening = false
updateListeningState()
- keyguardFaceAuthInteractor.onDeviceLifted()
+ deviceEntryFaceAuthInteractor.onDeviceLifted()
keyguardUpdateMonitor.requestActiveUnlock(
ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE,
"KeyguardLiftController")
@@ -113,7 +113,7 @@
val onKeyguard = keyguardUpdateMonitor.isKeyguardVisible &&
!statusBarStateController.isDozing
- val isFaceEnabled = keyguardFaceAuthInteractor.isFaceAuthEnabledAndEnrolled()
+ val isFaceEnabled = deviceEntryFaceAuthInteractor.isFaceAuthEnabledAndEnrolled()
val shouldListen = (onKeyguard || bouncerVisible) && isFaceEnabled
if (shouldListen != isListening) {
isListening = shouldListen
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
index abdf827..56ea00c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
@@ -104,7 +104,8 @@
}
private void notifyManagedProfileRemoved() {
- for (Callback callback : mCallbacks) {
+ ArrayList<Callback> copy = new ArrayList<>(mCallbacks);
+ for (Callback callback : copy) {
callback.onManagedProfileRemoved();
}
}
@@ -148,7 +149,8 @@
@Override
public void onUserChanged(int newUser, @NonNull Context userContext) {
reloadManagedProfiles();
- for (Callback callback : mCallbacks) {
+ ArrayList<Callback> copy = new ArrayList<>(mCallbacks);
+ for (Callback callback : copy) {
callback.onManagedProfileChanged();
}
}
@@ -156,7 +158,8 @@
@Override
public void onProfilesChanged(@NonNull List<UserInfo> profiles) {
reloadManagedProfiles();
- for (Callback callback : mCallbacks) {
+ ArrayList<Callback> copy = new ArrayList<>(mCallbacks);
+ for (Callback callback : copy) {
callback.onManagedProfileChanged();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 4999123..88347ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -18,6 +18,7 @@
import static android.view.WindowInsets.Type.navigationBars;
+import static com.android.systemui.Flags.predictiveBackAnimateBouncer;
import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN;
import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
@@ -400,8 +401,7 @@
mFoldAodAnimationController = sysUIUnfoldComponent
.map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null);
mAlternateBouncerInteractor = alternateBouncerInteractor;
- mIsBackAnimationEnabled =
- featureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM);
+ mIsBackAnimationEnabled = predictiveBackAnimateBouncer();
mUdfpsOverlayInteractor = udfpsOverlayInteractor;
mActivityStarter = activityStarter;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 3394eac..390d2c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.Flags.predictiveBackAnimateDialogs;
+
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.BroadcastReceiver;
@@ -45,8 +47,6 @@
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Application;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.model.SysUiState;
import com.android.systemui.res.R;
import com.android.systemui.shared.system.QuickStepContract;
@@ -78,7 +78,6 @@
public static final boolean DEFAULT_DISMISS_ON_DEVICE_LOCK = true;
private final Context mContext;
- private final FeatureFlags mFeatureFlags;
private final DialogDelegate<SystemUIDialog> mDelegate;
@Nullable private final DismissReceiver mDismissReceiver;
private final Handler mHandler = new Handler();
@@ -110,7 +109,6 @@
// SystemUIDialogFactory and make all other dialogs create a SystemUIDialog to which we set
// the content and attach listeners.
this(context, theme, dismissOnDeviceLock,
- Dependency.get(FeatureFlags.class),
Dependency.get(SystemUIDialogManager.class),
Dependency.get(SysUiState.class),
Dependency.get(BroadcastDispatcher.class),
@@ -119,7 +117,6 @@
public static class Factory {
private final Context mContext;
- private final FeatureFlags mFeatureFlags;
private final SystemUIDialogManager mSystemUIDialogManager;
private final SysUiState mSysUiState;
private final BroadcastDispatcher mBroadcastDispatcher;
@@ -128,13 +125,11 @@
@Inject
public Factory(
@Application Context context,
- FeatureFlags featureFlags,
SystemUIDialogManager systemUIDialogManager,
SysUiState sysUiState,
BroadcastDispatcher broadcastDispatcher,
DialogLaunchAnimator dialogLaunchAnimator) {
mContext = context;
- mFeatureFlags = featureFlags;
mSystemUIDialogManager = systemUIDialogManager;
mSysUiState = sysUiState;
mBroadcastDispatcher = broadcastDispatcher;
@@ -177,7 +172,6 @@
context,
DEFAULT_THEME,
DEFAULT_DISMISS_ON_DEVICE_LOCK,
- mFeatureFlags,
mSystemUIDialogManager,
mSysUiState,
mBroadcastDispatcher,
@@ -190,7 +184,6 @@
Context context,
int theme,
boolean dismissOnDeviceLock,
- FeatureFlags featureFlags,
SystemUIDialogManager dialogManager,
SysUiState sysUiState,
BroadcastDispatcher broadcastDispatcher,
@@ -199,7 +192,6 @@
context,
theme,
dismissOnDeviceLock,
- featureFlags,
dialogManager,
sysUiState,
broadcastDispatcher,
@@ -211,7 +203,6 @@
Context context,
int theme,
boolean dismissOnDeviceLock,
- FeatureFlags featureFlags,
SystemUIDialogManager dialogManager,
SysUiState sysUiState,
BroadcastDispatcher broadcastDispatcher,
@@ -221,7 +212,6 @@
context,
theme,
dismissOnDeviceLock,
- featureFlags,
dialogManager,
sysUiState,
broadcastDispatcher,
@@ -233,7 +223,6 @@
Context context,
int theme,
boolean dismissOnDeviceLock,
- FeatureFlags featureFlags,
SystemUIDialogManager dialogManager,
SysUiState sysUiState,
BroadcastDispatcher broadcastDispatcher,
@@ -241,7 +230,6 @@
DialogDelegate<SystemUIDialog> delegate) {
super(context, theme);
mContext = context;
- mFeatureFlags = featureFlags;
mDelegate = delegate;
applyFlags(this);
@@ -269,7 +257,7 @@
for (int i = 0; i < mOnCreateRunnables.size(); i++) {
mOnCreateRunnables.get(i).run();
}
- if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM)) {
+ if (predictiveBackAnimateDialogs()) {
DialogKt.registerAnimationOnBackInvoked(
/* dialog = */ this,
/* targetView = */ getWindow().getDecorView()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogFactory.kt
index d91ca92..f3e8f62d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogFactory.kt
@@ -20,7 +20,6 @@
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.model.SysUiState
import com.android.systemui.util.Assert
import javax.inject.Inject
@@ -30,7 +29,6 @@
@Inject
constructor(
@Application val applicationContext: Context,
- private val featureFlags: FeatureFlagsClassic,
private val dialogManager: SystemUIDialogManager,
private val sysUiState: SysUiState,
private val broadcastDispatcher: BroadcastDispatcher,
@@ -57,7 +55,6 @@
context,
theme,
dismissOnDeviceLock,
- featureFlags,
dialogManager,
sysUiState,
broadcastDispatcher,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 41ed76d..45078e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -20,6 +20,7 @@
import static android.os.BatteryManager.CHARGING_POLICY_DEFAULT;
import static android.os.BatteryManager.EXTRA_CHARGING_STATUS;
import static android.os.BatteryManager.EXTRA_PRESENT;
+
import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS;
import static com.android.systemui.util.DumpUtilsKt.asIndenting;
@@ -61,6 +62,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
import javax.annotation.concurrent.GuardedBy;
@@ -448,50 +450,38 @@
firePowerSaveChanged();
}
+ protected final void dispatchSafeChange(Consumer<BatteryStateChangeCallback> action) {
+ ArrayList<BatteryStateChangeCallback> copy;
+ synchronized (mChangeCallbacks) {
+ copy = new ArrayList<>(mChangeCallbacks);
+ }
+ final int n = copy.size();
+ for (int i = 0; i < n; i++) {
+ action.accept(copy.get(i));
+ }
+ }
+
protected void fireBatteryLevelChanged() {
mLogger.logBatteryLevelChangedCallback(mLevel, mPluggedIn, mCharging);
- synchronized (mChangeCallbacks) {
- final int N = mChangeCallbacks.size();
- for (int i = 0; i < N; i++) {
- mChangeCallbacks.get(i).onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
- }
- }
+ dispatchSafeChange(
+ (callback) -> callback.onBatteryLevelChanged(mLevel, mPluggedIn, mCharging));
}
private void fireBatteryUnknownStateChanged() {
- synchronized (mChangeCallbacks) {
- final int n = mChangeCallbacks.size();
- for (int i = 0; i < n; i++) {
- mChangeCallbacks.get(i).onBatteryUnknownStateChanged(mStateUnknown);
- }
- }
+ dispatchSafeChange((callback) -> callback.onBatteryUnknownStateChanged(mStateUnknown));
}
private void firePowerSaveChanged() {
- synchronized (mChangeCallbacks) {
- final int N = mChangeCallbacks.size();
- for (int i = 0; i < N; i++) {
- mChangeCallbacks.get(i).onPowerSaveChanged(mPowerSave);
- }
- }
+ dispatchSafeChange((callback) -> callback.onPowerSaveChanged(mPowerSave));
}
private void fireIsBatteryDefenderChanged() {
- synchronized (mChangeCallbacks) {
- final int n = mChangeCallbacks.size();
- for (int i = 0; i < n; i++) {
- mChangeCallbacks.get(i).onIsBatteryDefenderChanged(mIsBatteryDefender);
- }
- }
+ dispatchSafeChange((callback) -> callback.onIsBatteryDefenderChanged(mIsBatteryDefender));
}
private void fireIsIncompatibleChargingChanged() {
- synchronized (mChangeCallbacks) {
- final int n = mChangeCallbacks.size();
- for (int i = 0; i < n; i++) {
- mChangeCallbacks.get(i).onIsIncompatibleChargingChanged(mIsIncompatibleCharging);
- }
- }
+ dispatchSafeChange(
+ (callback) -> callback.onIsIncompatibleChargingChanged(mIsIncompatibleCharging));
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 53b343c..fc2f6e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -436,6 +436,8 @@
@Override
public void onServiceDisconnected() {}
+ // IMPORTANT: This handler guarantees that any operations on the list of callbacks is
+ // sequential, so no concurrent exceptions
private final class H extends Handler {
private final ArrayList<BluetoothController.Callback> mCallbacks = new ArrayList<>();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
index b06ebe9..149c8fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
@@ -35,9 +35,9 @@
import androidx.annotation.VisibleForTesting;
import com.android.internal.annotations.GuardedBy;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.res.R;
import com.android.systemui.util.Utils;
import java.io.PrintWriter;
@@ -291,11 +291,12 @@
@VisibleForTesting
void fireOnCastDevicesChanged() {
+ final ArrayList<Callback> callbacks;
synchronized (mCallbacks) {
- for (Callback callback : mCallbacks) {
- fireOnCastDevicesChanged(callback);
- }
-
+ callbacks = new ArrayList<>(mCallbacks);
+ }
+ for (Callback callback : callbacks) {
+ fireOnCastDevicesChanged(callback);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java
index 8207012..6319781 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java
@@ -36,10 +36,12 @@
}
private void handleRestrictBackgroundChanged(boolean isDataSaving) {
+ ArrayList<DataSaverController.Listener> copy;
synchronized (mListeners) {
- for (int i = 0; i < mListeners.size(); i++) {
- mListeners.get(i).onDataSaverChanged(isDataSaving);
- }
+ copy = new ArrayList<>(mListeners);
+ }
+ for (int i = 0; i < copy.size(); i++) {
+ copy.get(i).onDataSaverChanged(isDataSaving);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
index 5dcafb3..b98eff8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
@@ -202,10 +202,12 @@
private void dispatchListeners(int message, boolean argument) {
synchronized (mListeners) {
- final int N = mListeners.size();
+ final ArrayList<WeakReference<FlashlightController.FlashlightListener>> copy =
+ new ArrayList<>(mListeners);
+ final int n = copy.size();
boolean cleanup = false;
- for (int i = 0; i < N; i++) {
- FlashlightListener l = mListeners.get(i).get();
+ for (int i = 0; i < n; i++) {
+ FlashlightListener l = copy.get(i).get();
if (l != null) {
if (message == DISPATCH_ERROR) {
l.onFlashlightError();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
index fffd839..87dfc99 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
@@ -119,7 +119,8 @@
mHardwareToggleState.put(sensor, enabled);
}
- for (Callback callback : mCallbacks) {
+ Set<Callback> copy = new ArraySet<>(mCallbacks);
+ for (Callback callback : copy) {
callback.onSensorBlockedChanged(sensor, isSensorBlocked(sensor));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 0c5472f..886010c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -41,15 +41,14 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.log.core.LogLevel;
import com.android.systemui.res.R;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import dagger.Lazy;
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import javax.inject.Inject;
@@ -63,7 +62,8 @@
private static final boolean DEBUG_AUTH_WITH_ADB = false;
private static final String AUTH_BROADCAST_KEY = "debug_trigger_auth";
- private final ArrayList<Callback> mCallbacks = new ArrayList<>();
+ private final ConcurrentHashMap.KeySetView<Callback, Boolean> mCallbacks =
+ ConcurrentHashMap.<Callback>newKeySet();
private final Context mContext;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final LockPatternUtils mLockPatternUtils;
@@ -157,9 +157,7 @@
@Override
public void addCallback(@NonNull Callback callback) {
Objects.requireNonNull(callback, "Callback must not be null. b/128895449");
- if (!mCallbacks.contains(callback)) {
- mCallbacks.add(callback);
- }
+ mCallbacks.add(callback);
}
@Override
@@ -221,18 +219,7 @@
}
private void invokeForEachCallback(Consumer<Callback> consumer) {
- // Copy the list to allow removal during callback.
- ArrayList<Callback> copyOfCallbacks = new ArrayList<>(mCallbacks);
- for (int i = 0; i < copyOfCallbacks.size(); i++) {
- Callback callback = copyOfCallbacks.get(i);
- // Temporary fix for b/315731775, callback is null even though only non-null callbacks
- // are added to the list by addCallback
- if (callback != null) {
- consumer.accept(callback);
- } else {
- mLogger.log("KeyguardStateController callback is null", LogLevel.DEBUG);
- }
- }
+ mCallbacks.forEach(consumer);
}
private void notifyUnlockedChanged() {
@@ -506,5 +493,10 @@
public void onEnabledTrustAgentsChanged(int userId) {
update(false /* updateAlways */);
}
+
+ @Override
+ public void onForceIsDismissibleChanged(boolean keepUnlockedOnFold) {
+ update(false /* updateAlways */);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index e5f72eb..9eee5d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -356,6 +356,8 @@
updateActiveLocationRequests();
}
+ // IMPORTANT: This handler guarantees that any operations on the list of callbacks is
+ // sequential, so no concurrent exceptions
private final class H extends Handler {
private static final int MSG_LOCATION_SETTINGS_CHANGED = 1;
private static final int MSG_LOCATION_ACTIVE_CHANGED = 2;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
index 63b9ff9..b7d8ee3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
@@ -124,9 +124,10 @@
}
private void fireNextAlarmChanged() {
- int n = mChangeCallbacks.size();
+ ArrayList<NextAlarmChangeCallback> copy = new ArrayList<>(mChangeCallbacks);
+ int n = copy.size();
for (int i = 0; i < n; i++) {
- mChangeCallbacks.get(i).onNextAlarmChanged(mNextAlarm);
+ copy.get(i).onNextAlarmChanged(mNextAlarm);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SafetyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SafetyController.java
index f3d183c..0176abd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SafetyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SafetyController.java
@@ -100,10 +100,13 @@
}
private void handleSafetyCenterEnableChange() {
+ final ArrayList<SafetyController.Listener> copy;
synchronized (mListeners) {
- for (int i = 0; i < mListeners.size(); i++) {
- mListeners.get(i).onSafetyCenterEnableChanged(mSafetyCenterEnabled);
- }
+ copy = new ArrayList<>(mListeners);
+ }
+ final int n = copy.size();
+ for (int i = 0; i < n; i++) {
+ copy.get(i).onSafetyCenterEnableChanged(mSafetyCenterEnabled);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 4a4d4e1..5d69f36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -50,12 +50,12 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
-import com.android.systemui.res.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import org.xmlpull.v1.XmlPullParserException;
@@ -429,10 +429,12 @@
}
private void fireCallbacks() {
+ final ArrayList<SecurityControllerCallback> copy;
synchronized (mCallbacks) {
- for (SecurityControllerCallback callback : mCallbacks) {
- callback.onStateChanged();
- }
+ copy = new ArrayList<>(mCallbacks);
+ }
+ for (SecurityControllerCallback callback : copy) {
+ callback.onStateChanged();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index 66bf527..df210b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -48,12 +48,12 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.util.Utils;
import com.android.systemui.util.settings.GlobalSettings;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Objects;
+import java.util.function.Consumer;
import javax.inject.Inject;
@@ -243,46 +243,43 @@
}
private void fireNextAlarmChanged() {
- synchronized (mCallbacksLock) {
- Utils.safeForeach(mCallbacks, c -> c.onNextAlarmChanged());
- }
+ fireSafeChange(Callback::onNextAlarmChanged);
}
private void fireEffectsSuppressorChanged() {
- synchronized (mCallbacksLock) {
- Utils.safeForeach(mCallbacks, c -> c.onEffectsSupressorChanged());
- }
+ fireSafeChange(Callback::onEffectsSupressorChanged);
}
private void fireZenChanged(int zen) {
- synchronized (mCallbacksLock) {
- Utils.safeForeach(mCallbacks, c -> c.onZenChanged(zen));
- }
+ fireSafeChange(c -> c.onZenChanged(zen));
}
private void fireZenAvailableChanged(boolean available) {
- synchronized (mCallbacksLock) {
- Utils.safeForeach(mCallbacks, c -> c.onZenAvailableChanged(available));
- }
+ fireSafeChange(c -> c.onZenAvailableChanged(available));
}
private void fireManualRuleChanged(ZenRule rule) {
- synchronized (mCallbacksLock) {
- Utils.safeForeach(mCallbacks, c -> c.onManualRuleChanged(rule));
- }
+ fireSafeChange(c -> c.onManualRuleChanged(rule));
}
private void fireConsolidatedPolicyChanged(NotificationManager.Policy policy) {
+ fireSafeChange(c -> c.onConsolidatedPolicyChanged(policy));
+ }
+
+ private void fireSafeChange(Consumer<Callback> action) {
+ final ArrayList<Callback> copy;
synchronized (mCallbacksLock) {
- Utils.safeForeach(mCallbacks, c -> c.onConsolidatedPolicyChanged(policy));
+ copy = new ArrayList<>(mCallbacks);
+ }
+ final int n = copy.size();
+ for (int i = 0; i < n; i++) {
+ action.accept(copy.get(i));
}
}
@VisibleForTesting
protected void fireConfigChanged(ZenModeConfig config) {
- synchronized (mCallbacksLock) {
- Utils.safeForeach(mCallbacks, c -> c.onConfigChanged(config));
- }
+ fireSafeChange(c -> c.onConfigChanged(config));
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyLogger.kt b/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyLogger.kt
new file mode 100644
index 0000000..76f7609
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyLogger.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.unfold
+
+import com.android.systemui.shared.system.SysUiStatsLog
+
+class DisplaySwitchLatencyLogger {
+
+ /**
+ * Based on data present in [displaySwitchLatencyEvent], logs metrics for atom
+ * [DisplaySwitchLatencyTracked]
+ */
+ fun log(displaySwitchLatencyEvent: DisplaySwitchLatencyTracker.DisplaySwitchLatencyEvent) {
+ with(displaySwitchLatencyEvent) {
+ SysUiStatsLog.write(
+ SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED,
+ latencyMs,
+ fromFoldableDeviceState,
+ fromState,
+ fromFocusedAppUid,
+ fromPipAppUid,
+ fromVisibleAppsUid.toIntArray(),
+ fromDensityDpi,
+ toState,
+ toFoldableDeviceState,
+ toFocusedAppUid,
+ toPipAppUid,
+ toVisibleAppsUid.toIntArray(),
+ toDensityDpi,
+ notificationCount,
+ externalDisplayCount,
+ throttlingLevel,
+ vskinTemperatureC,
+ hallSensorToFirstHingeAngleChangeMs,
+ hallSensorToDeviceStateChangeMs,
+ onScreenTurningOnToOnDrawnMs,
+ onDrawnToOnScreenTurnedOnMs,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt
new file mode 100644
index 0000000..92a64a6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.unfold
+
+import android.content.Context
+import android.util.Log
+import com.android.app.tracing.TraceUtils.instantForTrack
+import com.android.app.tracing.TraceUtils.traceAsync
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.display.data.repository.DeviceStateRepository
+import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.ScreenPowerState
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.shared.system.SysUiStatsLog
+import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg
+import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
+import com.android.systemui.util.Compile
+import com.android.systemui.util.Utils.isDeviceFoldable
+import com.android.systemui.util.animation.data.repository.AnimationStatusRepository
+import com.android.systemui.util.kotlin.pairwise
+import com.android.systemui.util.time.SystemClock
+import com.android.systemui.util.time.measureTimeMillis
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.launch
+
+/**
+ * [DisplaySwitchLatencyTracker] tracks latency and related fields for display switch of a foldable
+ * device. This class populates [DisplaySwitchLatencyEvent] while an ongoing display switch event
+ */
+@SysUISingleton
+class DisplaySwitchLatencyTracker
+@Inject
+constructor(
+ private val context: Context,
+ private val deviceStateRepository: DeviceStateRepository,
+ private val powerInteractor: PowerInteractor,
+ private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
+ private val animationStatusRepository: AnimationStatusRepository,
+ private val keyguardInteractor: KeyguardInteractor,
+ @UnfoldSingleThreadBg private val singleThreadBgExecutor: Executor,
+ @Application private val applicationScope: CoroutineScope,
+ private val displaySwitchLatencyLogger: DisplaySwitchLatencyLogger,
+ private val systemClock: SystemClock
+) : CoreStartable {
+
+ private val backgroundDispatcher = singleThreadBgExecutor.asCoroutineDispatcher()
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ override fun start() {
+ if (!isDeviceFoldable(context)) {
+ return
+ }
+ applicationScope.launch(backgroundDispatcher) {
+ deviceStateRepository.state
+ .pairwise()
+ .filter {
+ // Start tracking only when the foldable device is
+ //folding(UNFOLDED/HALF_FOLDED -> FOLDED) or
+ //unfolding(FOLDED -> HALF_FOLD/UNFOLDED)
+ foldableDeviceState ->
+ foldableDeviceState.previousValue == DeviceState.FOLDED ||
+ foldableDeviceState.newValue == DeviceState.FOLDED
+ }
+ .flatMapLatest { foldableDeviceState ->
+ flow {
+ var displaySwitchLatencyEvent = DisplaySwitchLatencyEvent()
+ val toFoldableDeviceState = foldableDeviceState.newValue.toStatsInt()
+ displaySwitchLatencyEvent =
+ displaySwitchLatencyEvent.withBeforeFields(
+ foldableDeviceState.previousValue.toStatsInt()
+ )
+
+ val displaySwitchTimeMs =
+ measureTimeMillis(systemClock) {
+ traceAsync(TAG, "displaySwitch") {
+ waitForDisplaySwitch(toFoldableDeviceState)
+ }
+ }
+
+ displaySwitchLatencyEvent =
+ displaySwitchLatencyEvent.withAfterFields(
+ toFoldableDeviceState,
+ displaySwitchTimeMs.toInt(),
+ getCurrentState()
+ )
+ emit(displaySwitchLatencyEvent)
+ }
+ }
+ .collect { displaySwitchLatencyLogger.log(it) }
+ }
+ }
+
+ private fun DeviceState.toStatsInt(): Int =
+ when (this) {
+ DeviceState.FOLDED -> FOLDABLE_DEVICE_STATE_CLOSED
+ DeviceState.HALF_FOLDED -> FOLDABLE_DEVICE_STATE_HALF_OPEN
+ DeviceState.UNFOLDED -> FOLDABLE_DEVICE_STATE_OPEN
+ DeviceState.CONCURRENT_DISPLAY -> FOLDABLE_DEVICE_STATE_FLIPPED
+ else -> FOLDABLE_DEVICE_STATE_UNKNOWN
+ }
+
+ private suspend fun waitForDisplaySwitch(toFoldableDeviceState: Int) {
+ val isTransitionEnabled =
+ unfoldTransitionInteractor.isAvailable &&
+ animationStatusRepository.areAnimationsEnabled().first()
+ if (shouldWaitForScreenOn(toFoldableDeviceState, isTransitionEnabled)) {
+ waitForScreenTurnedOn()
+ } else {
+ traceAsync(TAG, "waitForTransitionStart()") {
+ unfoldTransitionInteractor.waitForTransitionStart()
+ }
+ }
+ }
+
+ private fun shouldWaitForScreenOn(
+ toFoldableDeviceState: Int,
+ isTransitionEnabled: Boolean
+ ): Boolean = (toFoldableDeviceState == FOLDABLE_DEVICE_STATE_CLOSED || !isTransitionEnabled)
+
+ private suspend fun waitForScreenTurnedOn() {
+ traceAsync(TAG, "waitForScreenTurnedOn()") {
+ powerInteractor.screenPowerState.filter { it == ScreenPowerState.SCREEN_ON }.first()
+ }
+ }
+
+ private fun getCurrentState(): Int =
+ when {
+ isStateAod() -> SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__AOD
+ else -> SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__UNKNOWN
+ }
+
+ private fun isStateAod(): Boolean {
+ val lastWakefulnessEvent = powerInteractor.detailedWakefulness.value
+ val isAodEnabled = keyguardInteractor.isAodAvailable.value
+
+ return (lastWakefulnessEvent.isAsleep() &&
+ (lastWakefulnessEvent.lastSleepReason == WakeSleepReason.FOLD) &&
+ isAodEnabled)
+ }
+
+ private inline fun log(msg: () -> String) {
+ if (DEBUG) Log.d(TAG, msg())
+ }
+
+ private fun DisplaySwitchLatencyEvent.withBeforeFields(
+ fromFoldableDeviceState: Int
+ ): DisplaySwitchLatencyEvent {
+ log { "fromFoldableDeviceState=$fromFoldableDeviceState" }
+ instantForTrack(TAG, "fromFoldableDeviceState=$fromFoldableDeviceState")
+
+ return copy(fromFoldableDeviceState = fromFoldableDeviceState)
+ }
+
+ private fun DisplaySwitchLatencyEvent.withAfterFields(
+ toFoldableDeviceState: Int,
+ displaySwitchTimeMs: Int,
+ toState: Int
+ ): DisplaySwitchLatencyEvent {
+ log {
+ "toFoldableDeviceState=$toFoldableDeviceState, " +
+ "toState=$toState, " +
+ "latencyMs=$displaySwitchTimeMs"
+ }
+ instantForTrack(TAG, "toFoldableDeviceState=$toFoldableDeviceState, toState=$toState")
+
+ return copy(
+ toFoldableDeviceState = toFoldableDeviceState,
+ latencyMs = displaySwitchTimeMs,
+ toState = toState
+ )
+ }
+
+ /**
+ * Stores values corresponding to all respective [DisplaySwitchLatencyTrackedField] in a single
+ * event of display switch for foldable devices.
+ *
+ * Once the data is captured in this data class and appropriate to log, it is logged through
+ * [DisplaySwitchLatencyLogger]
+ */
+ data class DisplaySwitchLatencyEvent(
+ val latencyMs: Int = VALUE_UNKNOWN,
+ val fromFoldableDeviceState: Int = FOLDABLE_DEVICE_STATE_UNKNOWN,
+ val fromState: Int = SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_STATE__UNKNOWN,
+ val fromFocusedAppUid: Int = VALUE_UNKNOWN,
+ val fromPipAppUid: Int = VALUE_UNKNOWN,
+ val fromVisibleAppsUid: Set<Int> = setOf(),
+ val fromDensityDpi: Int = VALUE_UNKNOWN,
+ val toFoldableDeviceState: Int = FOLDABLE_DEVICE_STATE_UNKNOWN,
+ val toState: Int = SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_STATE__UNKNOWN,
+ val toFocusedAppUid: Int = VALUE_UNKNOWN,
+ val toPipAppUid: Int = VALUE_UNKNOWN,
+ val toVisibleAppsUid: Set<Int> = setOf(),
+ val toDensityDpi: Int = VALUE_UNKNOWN,
+ val notificationCount: Int = VALUE_UNKNOWN,
+ val externalDisplayCount: Int = VALUE_UNKNOWN,
+ val throttlingLevel: Int =
+ SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__THROTTLING_LEVEL__NONE,
+ val vskinTemperatureC: Int = VALUE_UNKNOWN,
+ val hallSensorToFirstHingeAngleChangeMs: Int = VALUE_UNKNOWN,
+ val hallSensorToDeviceStateChangeMs: Int = VALUE_UNKNOWN,
+ val onScreenTurningOnToOnDrawnMs: Int = VALUE_UNKNOWN,
+ val onDrawnToOnScreenTurnedOnMs: Int = VALUE_UNKNOWN
+ )
+
+ companion object {
+ private const val VALUE_UNKNOWN = -1
+ private const val TAG = "DisplaySwitchLatency"
+ private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE)
+
+ private const val FOLDABLE_DEVICE_STATE_UNKNOWN =
+ SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_UNKNOWN
+ const val FOLDABLE_DEVICE_STATE_CLOSED =
+ SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_CLOSED
+ const val FOLDABLE_DEVICE_STATE_HALF_OPEN =
+ SysUiStatsLog
+ .DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_HALF_OPENED
+ private const val FOLDABLE_DEVICE_STATE_OPEN =
+ SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_OPENED
+ private const val FOLDABLE_DEVICE_STATE_FLIPPED =
+ SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_FLIPPED
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt
index 94912bf8..adf50a1 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt
@@ -22,8 +22,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.unfold.data.repository.FoldStateRepository
import com.android.systemui.unfold.system.DeviceStateRepository
-import com.android.systemui.unfold.updates.FoldStateRepository
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
index 50515da..8bef53c 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
@@ -27,6 +27,8 @@
import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.dagger.UnfoldBgProgressFlag
import com.android.systemui.unfold.dagger.UnfoldMain
+import com.android.systemui.unfold.data.repository.FoldStateRepository
+import com.android.systemui.unfold.data.repository.FoldStateRepositoryImpl
import com.android.systemui.unfold.data.repository.UnfoldTransitionRepository
import com.android.systemui.unfold.data.repository.UnfoldTransitionRepositoryImpl
import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
@@ -168,6 +170,11 @@
@Provides
fun screenStatusProvider(impl: LifecycleScreenStatusProvider): ScreenStatusProvider = impl
+ @Provides
+ @Singleton
+ fun provideDisplaySwitchLatencyLogger(): DisplaySwitchLatencyLogger =
+ DisplaySwitchLatencyLogger()
+
@Module
interface Bindings {
@Binds
@@ -178,6 +185,8 @@
@Binds fun bindRepository(impl: UnfoldTransitionRepositoryImpl): UnfoldTransitionRepository
@Binds fun bindInteractor(impl: UnfoldTransitionInteractorImpl): UnfoldTransitionInteractor
+
+ @Binds fun bindFoldStateRepository(impl: FoldStateRepositoryImpl): FoldStateRepository
}
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateRepository.kt b/packages/SystemUI/src/com/android/systemui/unfold/data/repository/FoldStateRepository.kt
similarity index 84%
rename from packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateRepository.kt
rename to packages/SystemUI/src/com/android/systemui/unfold/data/repository/FoldStateRepository.kt
index 61b0b40..04b00ca 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/data/repository/FoldStateRepository.kt
@@ -13,9 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.unfold.updates
+package com.android.systemui.unfold.data.repository
-import com.android.systemui.unfold.updates.FoldStateRepository.FoldUpdate
+import com.android.systemui.unfold.data.repository.FoldStateRepository.FoldUpdate
+import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED
+import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
+import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN
+import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING
+import com.android.systemui.unfold.updates.FOLD_UPDATE_START_OPENING
+import com.android.systemui.unfold.updates.FoldStateProvider
import javax.inject.Inject
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.awaitClose
@@ -50,7 +56,7 @@
FOLD_UPDATE_FINISH_HALF_OPEN -> FINISH_HALF_OPEN
FOLD_UPDATE_FINISH_FULL_OPEN -> FINISH_FULL_OPEN
FOLD_UPDATE_FINISH_CLOSED -> FINISH_CLOSED
- else -> error("FoldUpdateNotFound")
+ else -> error("Fold update with id $oldId is not supported")
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt
index a2e77af..3e2e564 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt
@@ -15,16 +15,26 @@
*/
package com.android.systemui.unfold.domain.interactor
-import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionFinished
import com.android.systemui.unfold.data.repository.UnfoldTransitionRepository
+import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionFinished
+import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionStarted
import javax.inject.Inject
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
+/**
+ * Contains business-logic related to fold-unfold transitions while interacting with
+ * [UnfoldTransitionRepository]
+ */
interface UnfoldTransitionInteractor {
+ /** Returns availability of fold/unfold transitions on the device */
val isAvailable: Boolean
+ /** Suspends and waits for a fold/unfold transition to finish */
suspend fun waitForTransitionFinish()
+
+ /** Suspends and waits for a fold/unfold transition to start */
+ suspend fun waitForTransitionStart()
}
class UnfoldTransitionInteractorImpl
@@ -37,4 +47,8 @@
override suspend fun waitForTransitionFinish() {
repository.transitionStatus.filter { it is TransitionFinished }.first()
}
+
+ override suspend fun waitForTransitionStart() {
+ repository.transitionStatus.filter { it is TransitionStarted }.first()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java
index fa6d055..7861ded 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java
@@ -82,6 +82,14 @@
}
/**
+ * Returns {@code true} if the device is a foldable device
+ */
+ public static boolean isDeviceFoldable(Context context) {
+ return context.getResources()
+ .getIntArray(com.android.internal.R.array.config_foldedDeviceStates).length != 0;
+ }
+
+ /**
* Allow the media player to be shown in the QS area, controlled by 2 flags.
* On by default, but can be disabled by setting either flag to 0/false.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/util/time/MeasureTimeUtil.kt b/packages/SystemUI/src/com/android/systemui/util/time/MeasureTimeUtil.kt
new file mode 100644
index 0000000..f131968
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/time/MeasureTimeUtil.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.time
+
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
+
+/**
+ * Executes the given [block] and returns elapsed time using provided [systemClock] in milliseconds.
+ */
+@OptIn(ExperimentalContracts::class)
+inline fun measureTimeMillis(systemClock: SystemClock, block: () -> Unit): Long {
+ contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
+ val start = systemClock.currentTimeMillis()
+ block()
+ return systemClock.currentTimeMillis() - start
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index 8d06a8f..497c4cb 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -38,6 +38,8 @@
import com.android.systemui.volume.VolumeDialogImpl;
import com.android.systemui.volume.VolumePanelFactory;
import com.android.systemui.volume.VolumeUI;
+import com.android.systemui.volume.panel.dagger.VolumePanelComponent;
+import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory;
import dagger.Binds;
import dagger.Lazy;
@@ -48,9 +50,13 @@
import dagger.multibindings.IntoSet;
/** Dagger Module for code in the volume package. */
-@Module
+@Module(
+ subcomponents = {
+ VolumePanelComponent.class
+ }
+)
public interface VolumeModule {
- /** Starts VolumeUI. */
+ /** Starts VolumeUI. */
@Binds
@IntoMap
@ClassKey(VolumeUI.class)
@@ -61,11 +67,15 @@
@IntoSet
ConfigurationController.ConfigurationListener bindVolumeUIConfigChanges(VolumeUI impl);
- /** */
+ /** */
@Binds
VolumeComponent provideVolumeComponent(VolumeDialogComponent volumeDialogComponent);
- /** */
+ /** */
+ @Binds
+ VolumePanelComponentFactory bindVolumePanelComponentFactory(VolumePanelComponent.Factory impl);
+
+ /** */
@Provides
static VolumeDialog provideVolumeDialog(
Context context,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
similarity index 70%
copy from packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
copy to packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
index efc7431..22a74d2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
@@ -14,12 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.impl.custom.di.bound
+package com.android.systemui.volume.panel
-import javax.inject.Qualifier
-
-/** User associated with current custom tile binding. */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class CustomTileUser
+/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
+typealias VolumePanelComponentKey = String
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/CoroutineModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/CoroutineModule.kt
new file mode 100644
index 0000000..3ce0bac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/CoroutineModule.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.dagger
+
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import dagger.Module
+import dagger.Provides
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
+
+/** Provides Volume Panel coroutine tools. */
+@Module
+interface CoroutineModule {
+
+ companion object {
+
+ /**
+ * Provides a coroutine scope to use inside [VolumePanelScope].
+ * [com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel] manages the
+ * lifecycle of this scope. It's cancelled when the View Model is destroyed. This helps to
+ * free occupied resources when volume panel is not shown.
+ */
+ @VolumePanelScope
+ @Provides
+ fun provideCoroutineScope(@Application applicationScope: CoroutineScope): CoroutineScope =
+ CoroutineScope(applicationScope.coroutineContext + SupervisorJob())
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/DefaultMultibindsModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/DefaultMultibindsModule.kt
new file mode 100644
index 0000000..3660ac1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/DefaultMultibindsModule.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.dagger
+
+import com.android.systemui.volume.panel.VolumePanelComponentKey
+import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import dagger.Module
+import dagger.multibindings.Multibinds
+
+/**
+ * Provides empty multibinding maps for [ComponentAvailabilityCriteria] and [VolumePanelComponent]
+ */
+@Module
+interface DefaultMultibindsModule {
+
+ @Multibinds fun criteriaMap(): Map<VolumePanelComponentKey, ComponentAvailabilityCriteria>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
new file mode 100644
index 0000000..0a057eb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.dagger
+
+import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.domain.DomainModule
+import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
+import com.android.systemui.volume.panel.ui.UiModule
+import com.android.systemui.volume.panel.ui.viewmodel.ComponentsLayoutManager
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
+import dagger.BindsInstance
+import dagger.Subcomponent
+import kotlinx.coroutines.CoroutineScope
+
+/**
+ * Core Volume Panel dagger component. It's managed by [VolumePanelViewModel] and lives alongside
+ * it.
+ */
+@VolumePanelScope
+@Subcomponent(
+ modules =
+ [
+ // Volume Panel infra modules
+ CoroutineModule::class,
+ DefaultMultibindsModule::class,
+ DomainModule::class,
+ UiModule::class,
+ // Components modules
+ ]
+)
+interface VolumePanelComponent {
+
+ fun coroutineScope(): CoroutineScope
+
+ fun componentsInteractor(): ComponentsInteractor
+
+ fun componentsLayoutManager(): ComponentsLayoutManager
+
+ @Subcomponent.Factory
+ interface Factory : VolumePanelComponentFactory {
+
+ override fun create(@BindsInstance viewModel: VolumePanelViewModel): VolumePanelComponent
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/factory/VolumePanelComponentFactory.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/factory/VolumePanelComponentFactory.kt
new file mode 100644
index 0000000..e470c3f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/factory/VolumePanelComponentFactory.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.volume.panel.dagger.factory
+
+import com.android.systemui.volume.panel.dagger.VolumePanelComponent
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
+import dagger.BindsInstance
+
+/**
+ * Common interface for all [dagger.Subcomponent.Factory] providing [VolumePanelComponent].
+ * [VolumePanelViewModel] uses it to create a new instance of the class.
+ */
+interface VolumePanelComponentFactory {
+
+ fun create(@BindsInstance viewModel: VolumePanelViewModel): VolumePanelComponent
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/scope/VolumePanelScope.kt
similarity index 65%
copy from packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
copy to packages/SystemUI/src/com/android/systemui/volume/panel/dagger/scope/VolumePanelScope.kt
index efc7431..e597d11 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/scope/VolumePanelScope.kt
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.impl.custom.di.bound
+package com.android.systemui.volume.panel.dagger.scope
-import javax.inject.Qualifier
+import javax.inject.Scope
-/** User associated with current custom tile binding. */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class CustomTileUser
+/**
+ * Volume Panel dependency injection scope. This scope is created alongside Volume Panel and
+ * destroyed when it's lo longer present.
+ */
+@MustBeDocumented @Retention(AnnotationRetention.RUNTIME) @Scope annotation class VolumePanelScope
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/ComponentAvailabilityCriteria.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/ComponentAvailabilityCriteria.kt
new file mode 100644
index 0000000..d9702e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/ComponentAvailabilityCriteria.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.domain
+
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+interface ComponentAvailabilityCriteria {
+
+ /**
+ * Checks if the controller is currently available. Can be used to filter out unwanted
+ * components. For example, hide components for the hardware that is temporarily unavailable.
+ */
+ fun isAvailable(): Flow<Boolean>
+}
+
+@VolumePanelScope
+class AlwaysAvailableCriteria @Inject constructor() : ComponentAvailabilityCriteria {
+
+ override fun isAvailable(): Flow<Boolean> = flowOf(true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
new file mode 100644
index 0000000..7817630
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.domain
+
+import com.android.systemui.volume.panel.VolumePanelComponentKey
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
+import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractorImpl
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+
+/** Domain layer bindings module. */
+@Module
+interface DomainModule {
+
+ @Binds fun bindComponentsInteractor(impl: ComponentsInteractorImpl): ComponentsInteractor
+
+ @Binds
+ fun bindDefaultComponentAvailabilityCriteria(
+ impl: AlwaysAvailableCriteria
+ ): ComponentAvailabilityCriteria
+
+ companion object {
+
+ /**
+ * Enabled components collection. These are the components processed by Volume Panel logic
+ * and possibly shown in the UI.
+ *
+ * There should be a binding in [VolumePanelScope] for [ComponentAvailabilityCriteria] and
+ * [com.android.systemui.volume.panel.ui.VolumePanelComponent] for each component from this
+ * collection.
+ */
+ @Provides
+ @VolumePanelScope
+ fun provideEnabledComponents(): Collection<VolumePanelComponentKey> = setOf()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractor.kt
new file mode 100644
index 0000000..e5b52ea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractor.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.domain.interactor
+
+import com.android.systemui.volume.panel.VolumePanelComponentKey
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.domain.model.ComponentModel
+import javax.inject.Inject
+import javax.inject.Provider
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
+
+interface ComponentsInteractor {
+
+ /**
+ * Components collection for the UI layer. Uses [ComponentAvailabilityCriteria] to dynamically
+ * determine each component availability.
+ */
+ val components: Flow<Collection<ComponentModel>>
+}
+
+@VolumePanelScope
+class ComponentsInteractorImpl
+@Inject
+constructor(
+ enabledComponents: Collection<VolumePanelComponentKey>,
+ defaultCriteria: Provider<ComponentAvailabilityCriteria>,
+ @VolumePanelScope coroutineScope: CoroutineScope,
+ private val criteriaByKey:
+ Map<
+ VolumePanelComponentKey,
+ @JvmSuppressWildcards
+ Provider<@JvmSuppressWildcards ComponentAvailabilityCriteria>
+ >,
+) : ComponentsInteractor {
+
+ override val components: Flow<Collection<ComponentModel>> =
+ combine(
+ enabledComponents.map { componentKey ->
+ val componentCriteria = (criteriaByKey[componentKey] ?: defaultCriteria).get()
+ componentCriteria.isAvailable().map { isAvailable ->
+ ComponentModel(componentKey, isAvailable = isAvailable)
+ }
+ }
+ ) {
+ it.asList()
+ }
+ .shareIn(coroutineScope, SharingStarted.Eagerly, replay = 1)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/model/ComponentModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/model/ComponentModel.kt
new file mode 100644
index 0000000..9765713
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/model/ComponentModel.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.domain.model
+
+import com.android.systemui.volume.panel.VolumePanelComponentKey
+
+/**
+ * Represents a current state of the Volume Panel component.
+ *
+ * @property key identifies the component the entity represents.
+ * @property isAvailable is true when the component is supported by the device.
+ */
+data class ComponentModel(
+ val key: VolumePanelComponentKey,
+ val isAvailable: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
copy to packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
index efc7431..bfa7ef2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
@@ -14,12 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.impl.custom.di.bound
+package com.android.systemui.volume.panel.ui
-import javax.inject.Qualifier
+import com.android.systemui.volume.panel.ui.viewmodel.ComponentsLayoutManager
+import com.android.systemui.volume.panel.ui.viewmodel.DefaultComponentsLayoutManager
+import dagger.Binds
+import dagger.Module
-/** User associated with current custom tile binding. */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class CustomTileUser
+/** UI layer bindings module. */
+@Module
+interface UiModule {
+
+ @Binds fun bindSorter(impl: DefaultComponentsLayoutManager): ComponentsLayoutManager
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/ComponentState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/ComponentState.kt
new file mode 100644
index 0000000..0a226e2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/ComponentState.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.ui.model
+
+import com.android.systemui.volume.panel.VolumePanelComponentKey
+
+/**
+ * State of the [VolumePanelComponent].
+ *
+ * @property key uniquely identifies this component
+ * @property component is an inflated component obtained be the View Model
+ * @property isVisible determines component visibility in the UI
+ */
+data class ComponentState(
+ val key: VolumePanelComponentKey,
+ val isVisible: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/ComponentsLayout.kt
similarity index 70%
copy from packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
copy to packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/ComponentsLayout.kt
index efc7431..5690ac3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/ComponentsLayout.kt
@@ -14,12 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.impl.custom.di.bound
+package com.android.systemui.volume.panel.ui.model
-import javax.inject.Qualifier
-
-/** User associated with current custom tile binding. */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class CustomTileUser
+/** Represents components grouping into the layout. */
+data class ComponentsLayout(
+ val contentComponents: List<ComponentState>,
+ val bottomBarComponent: ComponentState,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/VolumePanelState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/VolumePanelState.kt
new file mode 100644
index 0000000..399342f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/VolumePanelState.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.ui.model
+
+import android.content.res.Configuration
+import android.content.res.Configuration.Orientation
+
+/**
+ * State of the Volume Panel itself.
+ *
+ * @property orientation is current Volume Panel orientation.
+ */
+data class VolumePanelState(
+ @Orientation val orientation: Int,
+ val isVisible: Boolean,
+) {
+ init {
+ require(
+ orientation == Configuration.ORIENTATION_PORTRAIT ||
+ orientation == Configuration.ORIENTATION_LANDSCAPE ||
+ orientation == Configuration.ORIENTATION_UNDEFINED ||
+ orientation == Configuration.ORIENTATION_SQUARE
+ ) {
+ "Unknown orientation: $orientation"
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentsLayoutManager.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentsLayoutManager.kt
new file mode 100644
index 0000000..f45401a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentsLayoutManager.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.ui.viewmodel
+
+import com.android.systemui.volume.panel.ui.model.ComponentState
+import com.android.systemui.volume.panel.ui.model.ComponentsLayout
+import com.android.systemui.volume.panel.ui.model.VolumePanelState
+
+/**
+ * Lays out components to [ComponentsLayout], that UI uses to render the Volume Panel.
+ *
+ * Vertical layout shows the list from top to bottom:
+ * ```
+ * -----
+ * | 1 |
+ * | 2 |
+ * | 3 |
+ * | 4 |
+ * -----
+ * ```
+ *
+ * Horizontal layout shows the list in a grid from, filling the columns first:
+ * ```
+ * ----------
+ * | 1 || 3 |
+ * | 2 || 4 |
+ * ----------
+ * ```
+ */
+interface ComponentsLayoutManager {
+
+ fun layout(
+ volumePanelState: VolumePanelState,
+ components: Collection<ComponentState>,
+ ): ComponentsLayout
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManager.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManager.kt
new file mode 100644
index 0000000..cedfaf3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManager.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.ui.viewmodel
+
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.ui.model.ComponentState
+import com.android.systemui.volume.panel.ui.model.ComponentsLayout
+import com.android.systemui.volume.panel.ui.model.VolumePanelState
+import javax.inject.Inject
+
+/**
+ * Default [ComponentsLayoutManager]. It places [VolumePanelComponents.BOTTOM_BAR] to
+ * [ComponentsLayout.bottomBarComponent] and everything else to
+ * [ComponentsLayout.contentComponents].
+ */
+@VolumePanelScope
+class DefaultComponentsLayoutManager @Inject constructor() : ComponentsLayoutManager {
+
+ override fun layout(
+ volumePanelState: VolumePanelState,
+ components: Collection<ComponentState>
+ ): ComponentsLayout = TODO("Unimplemented yet")
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
new file mode 100644
index 0000000..dda361a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.ui.viewmodel
+
+import android.content.Context
+import android.content.res.Resources
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.onConfigChanged
+import com.android.systemui.volume.panel.dagger.VolumePanelComponent
+import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory
+import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
+import com.android.systemui.volume.panel.ui.model.ComponentState
+import com.android.systemui.volume.panel.ui.model.ComponentsLayout
+import com.android.systemui.volume.panel.ui.model.VolumePanelState
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+class VolumePanelViewModel(
+ resources: Resources,
+ daggerComponentFactory: VolumePanelComponentFactory,
+ configurationController: ConfigurationController,
+) : ViewModel() {
+
+ private val volumePanelComponent: VolumePanelComponent = daggerComponentFactory.create(this)
+
+ private val scope: CoroutineScope
+ get() = volumePanelComponent.coroutineScope()
+
+ private val componentsInteractor: ComponentsInteractor
+ get() = volumePanelComponent.componentsInteractor()
+
+ private val componentsLayoutManager: ComponentsLayoutManager
+ get() = volumePanelComponent.componentsLayoutManager()
+
+ private val mutablePanelVisibility = MutableStateFlow(true)
+
+ val volumePanelState: StateFlow<VolumePanelState> =
+ combine(
+ configurationController.onConfigChanged.distinctUntilChanged(),
+ mutablePanelVisibility,
+ ) { configuration, isVisible ->
+ VolumePanelState(orientation = configuration.orientation, isVisible = isVisible)
+ }
+ .stateIn(
+ volumePanelComponent.coroutineScope(),
+ SharingStarted.Eagerly,
+ VolumePanelState(
+ orientation = resources.configuration.orientation,
+ isVisible = mutablePanelVisibility.value,
+ ),
+ )
+ val mComponentsLayout: Flow<ComponentsLayout> =
+ combine(
+ componentsInteractor.components,
+ volumePanelState,
+ ) { components, scope ->
+ val componentStates =
+ components.map { model ->
+ ComponentState(
+ model.key,
+ model.isAvailable,
+ )
+ }
+ componentsLayoutManager.layout(scope, componentStates)
+ }
+ .shareIn(
+ volumePanelComponent.coroutineScope(),
+ SharingStarted.Eagerly,
+ replay = 1,
+ )
+
+ fun dismissPanel() {
+ scope.launch { mutablePanelVisibility.emit(false) }
+ }
+
+ override fun onCleared() {
+ scope.cancel()
+ super.onCleared()
+ }
+
+ class Factory
+ @Inject
+ constructor(
+ @Application private val context: Context,
+ private val daggerComponentFactory: VolumePanelComponentFactory,
+ private val configurationController: ConfigurationController,
+ ) : ViewModelProvider.Factory {
+
+ @Suppress("UNCHECKED_CAST")
+ override fun <T : ViewModel> create(modelClass: Class<T>): T {
+ check(modelClass == VolumePanelViewModel::class.java)
+ return VolumePanelViewModel(
+ context.resources,
+ daggerComponentFactory,
+ configurationController,
+ )
+ as T
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
index 5558aa7..111492c 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
@@ -43,7 +43,7 @@
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.res.R;
@@ -69,7 +69,7 @@
private final Executor mExecutor;
private final Handler mHandler;
private final FalsingManager mFalsingManager;
- private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
+ private final DeviceEntryFaceAuthInteractor mDeviceEntryFaceAuthInteractor;
private FalsingCollector mFalsingCollector;
private final UserTracker mUserTracker;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -94,7 +94,7 @@
KeyguardUpdateMonitor keyguardUpdateMonitor,
StatusBarKeyguardViewManager keyguardViewManager,
UiEventLogger uiEventLogger,
- KeyguardFaceAuthInteractor keyguardFaceAuthInteractor) {
+ DeviceEntryFaceAuthInteractor deviceEntryFaceAuthInteractor) {
mKeyguardStateController = keyguardStateController;
mKeyguardDismissUtil = keyguardDismissUtil;
mActivityStarter = activityStarter;
@@ -106,7 +106,7 @@
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mKeyguardViewManager = keyguardViewManager;
mUiEventLogger = uiEventLogger;
- mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
+ mDeviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor;
}
@Override
@@ -213,7 +213,7 @@
true,
Utils.getColorAttrDefaultColor(
this, com.android.internal.R.attr.colorAccentPrimary));
- mKeyguardFaceAuthInteractor.onWalletLaunched();
+ mDeviceEntryFaceAuthInteractor.onWalletLaunched();
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index d03a898..d8eb05a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -30,6 +30,7 @@
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING_RESTARTING;
+import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_STOPPED;
import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT;
import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED;
@@ -109,6 +110,7 @@
import android.text.TextUtils;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.foldables.FoldGracePeriodProvider;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.InstanceId;
import com.android.internal.logging.UiEventLogger;
@@ -119,16 +121,19 @@
import com.android.keyguard.KeyguardUpdateMonitor.BiometricAuthenticated;
import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
import com.android.settingslib.fuelgauge.BatteryStatus;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfig;
+import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigImpl;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
+import com.android.systemui.deviceentry.domain.interactor.FaceAuthenticationListener;
+import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus;
+import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus;
+import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.domain.interactor.FaceAuthenticationListener;
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
-import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.FaceDetectionStatus;
-import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.UserTracker;
@@ -218,6 +223,8 @@
@Mock
private BroadcastDispatcher mBroadcastDispatcher;
@Mock
+ private FoldGracePeriodProvider mFoldGracePeriodProvider;
+ @Mock
private TelephonyManager mTelephonyManager;
@Mock
private SensorPrivacyManager mSensorPrivacyManager;
@@ -260,7 +267,7 @@
@Mock
private SelectedUserInteractor mSelectedUserInteractor;
@Mock
- private KeyguardFaceAuthInteractor mFaceAuthInteractor;
+ private DeviceEntryFaceAuthInteractor mFaceAuthInteractor;
@Captor
private ArgumentCaptor<FaceAuthenticationListener> mFaceAuthenticationListener;
@@ -314,7 +321,7 @@
mContext.getOrCreateTestableResources().addOverride(
com.android.systemui.res.R.integer.config_face_auth_supported_posture,
DEVICE_POSTURE_UNKNOWN);
- mFaceWakeUpTriggersConfig = new FaceWakeUpTriggersConfig(
+ mFaceWakeUpTriggersConfig = new FaceWakeUpTriggersConfigImpl(
mContext.getResources(),
mGlobalSettings,
mDumpManager
@@ -334,6 +341,7 @@
anyInt());
mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
+ mKeyguardUpdateMonitor.mFoldGracePeriodProvider = mFoldGracePeriodProvider;
setupBiometrics(mKeyguardUpdateMonitor);
mKeyguardUpdateMonitor.setFaceAuthInteractor(mFaceAuthInteractor);
verify(mFaceAuthInteractor).registerListener(mFaceAuthenticationListener.capture());
@@ -922,8 +930,7 @@
@Test
public void trustAgentHasTrust() {
// WHEN user has trust
- mKeyguardUpdateMonitor.onTrustChanged(true, true,
- mSelectedUserInteractor.getSelectedUserId(), 0, null);
+ givenSelectedUserCanSkipBouncerFromTrustedState();
// THEN user is considered as "having trust" and bouncer can be skipped
Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(
@@ -947,8 +954,7 @@
@Test
public void trustAgentHasTrust_fingerprintLockout() {
// GIVEN user has trust
- mKeyguardUpdateMonitor.onTrustChanged(true, true,
- mSelectedUserInteractor.getSelectedUserId(), 0, null);
+ givenSelectedUserCanSkipBouncerFromTrustedState();
Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(
mSelectedUserInteractor.getSelectedUserId()));
@@ -1973,6 +1979,61 @@
}
@Test
+ public void detectFingerprint_onSuccess_biometricStateStopped() {
+ // GIVEN FP detection is running
+ givenDetectFingerprintWithClearingFingerprintManagerInvocations();
+
+ // WHEN detection is successful
+ ArgumentCaptor<FingerprintManager.FingerprintDetectionCallback> fpDetectCallbackCaptor =
+ ArgumentCaptor.forClass(FingerprintManager.FingerprintDetectionCallback.class);
+ verify(mFingerprintManager).detectFingerprint(
+ any(), fpDetectCallbackCaptor.capture(), any());
+ fpDetectCallbackCaptor.getValue().onFingerprintDetected(0, 0, true);
+ mTestableLooper.processAllMessages();
+
+ // THEN fingerprint detect state should immediately update to STOPPED
+ assertThat(mKeyguardUpdateMonitor.mFingerprintRunningState)
+ .isEqualTo(BIOMETRIC_STATE_STOPPED);
+ }
+
+ @Test
+ public void runFpDetectFlagDisabled_sideFps_keyguardDismissible_fingerprintAuthenticateRuns() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_RUN_FINGERPRINT_DETECT_ON_DISMISSIBLE_KEYGUARD);
+
+ // Clear invocations, since previous setup (e.g. registering BiometricManager callbacks)
+ // will trigger updateBiometricListeningState();
+ clearInvocations(mFingerprintManager);
+ mKeyguardUpdateMonitor.resetBiometricListeningState();
+
+ // GIVEN the user can skip the bouncer
+ givenSelectedUserCanSkipBouncerFromTrustedState();
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+ mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
+ mTestableLooper.processAllMessages();
+
+ // WHEN verify authenticate runs
+ verifyFingerprintAuthenticateCall();
+ }
+
+ @Test
+ public void sideFps_keyguardDismissible_fingerprintDetectRuns() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RUN_FINGERPRINT_DETECT_ON_DISMISSIBLE_KEYGUARD);
+ // Clear invocations, since previous setup (e.g. registering BiometricManager callbacks)
+ // will trigger updateBiometricListeningState();
+ clearInvocations(mFingerprintManager);
+ mKeyguardUpdateMonitor.resetBiometricListeningState();
+
+ // GIVEN the user can skip the bouncer
+ givenSelectedUserCanSkipBouncerFromTrustedState();
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+ mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
+ mTestableLooper.processAllMessages();
+
+ // WHEN verify detect runs
+ verifyFingerprintDetectCall();
+ }
+
+ @Test
public void testFingerprintSensorProperties() throws RemoteException {
mFingerprintAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(
new ArrayList<>());
@@ -2077,6 +2138,35 @@
verify(callback).onBiometricEnrollmentStateChanged(BiometricSourceType.FACE);
}
+ private void givenSelectedUserCanSkipBouncerFromTrustedState() {
+ mKeyguardUpdateMonitor.onTrustChanged(true, true,
+ mSelectedUserInteractor.getSelectedUserId(), 0, null);
+ }
+
+ @Test
+ public void forceIsDismissibleKeyguard_foldingGracePeriodNotEnabled() {
+ when(mFoldGracePeriodProvider.isEnabled()).thenReturn(false);
+ primaryAuthNotRequiredByStrongAuthTracker();
+ mKeyguardUpdateMonitor.tryForceIsDismissibleKeyguard();
+ Assert.assertFalse(mKeyguardUpdateMonitor.forceIsDismissibleIsKeepingDeviceUnlocked());
+ }
+
+ @Test
+ public void forceIsDismissibleKeyguard() {
+ when(mFoldGracePeriodProvider.isEnabled()).thenReturn(true);
+ primaryAuthNotRequiredByStrongAuthTracker();
+ mKeyguardUpdateMonitor.tryForceIsDismissibleKeyguard();
+ Assert.assertTrue(mKeyguardUpdateMonitor.forceIsDismissibleIsKeepingDeviceUnlocked());
+ }
+
+ @Test
+ public void forceIsDismissibleKeyguard_respectsLockdown() {
+ when(mFoldGracePeriodProvider.isEnabled()).thenReturn(true);
+ userDeviceLockDown();
+ mKeyguardUpdateMonitor.tryForceIsDismissibleKeyguard();
+ Assert.assertFalse(mKeyguardUpdateMonitor.forceIsDismissibleIsKeepingDeviceUnlocked());
+ }
+
private void verifyFingerprintAuthenticateNeverCalled() {
verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), any());
verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
index 342494d..46936d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
@@ -25,6 +25,7 @@
import com.android.internal.R
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
import com.android.systemui.decor.FaceScanningProviderFactory
import com.android.systemui.log.ScreenDecorationsLogger
import com.android.systemui.log.logcatLogBuffer
@@ -53,6 +54,8 @@
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ private val facePropertyRepository = FakeFacePropertyRepository()
+
private val displayId = 2
@Before
@@ -86,9 +89,10 @@
keyguardUpdateMonitor,
mock(Executor::class.java),
ScreenDecorationsLogger(logcatLogBuffer("FaceScanningProviderFactoryTest")),
+ facePropertyRepository,
)
- whenever(authController.faceSensorLocation).thenReturn(Point(10, 10))
+ facePropertyRepository.setSensorLocation(Point(10, 10))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index c094df5..c07148b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -54,6 +54,7 @@
import android.content.res.TypedArray;
import android.graphics.Path;
import android.graphics.PixelFormat;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.hardware.display.DisplayManager;
@@ -80,6 +81,7 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository;
import com.android.systemui.decor.CornerDecorProvider;
import com.android.systemui.decor.CutoutDecorProviderFactory;
import com.android.systemui.decor.CutoutDecorProviderImpl;
@@ -101,6 +103,7 @@
import com.android.systemui.statusbar.events.PrivacyDotViewController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.concurrency.FakeThreadFactory;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.FakeSettings;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.time.FakeSystemClock;
@@ -108,8 +111,6 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
@@ -169,8 +170,11 @@
private PrivacyDotViewController.ShowingListener mPrivacyDotShowingListener;
@Mock
private CutoutDecorProviderFactory mCutoutFactory;
- @Captor
- private ArgumentCaptor<AuthController.Callback> mAuthControllerCallback;
+ @Mock
+ private JavaAdapter mJavaAdapter;
+
+ private FakeFacePropertyRepository mFakeFacePropertyRepository =
+ new FakeFacePropertyRepository();
private List<DecorProvider> mMockCutoutList;
@Before
@@ -227,20 +231,23 @@
doAnswer(it -> !(mMockCutoutList.isEmpty())).when(mCutoutFactory).getHasProviders();
doReturn(mMockCutoutList).when(mCutoutFactory).getProviders();
+ mFakeFacePropertyRepository.setSensorLocation(new Point(10, 10));
+
mFaceScanningDecorProvider = spy(new FaceScanningOverlayProviderImpl(
BOUNDS_POSITION_TOP,
mAuthController,
mStatusBarStateController,
mKeyguardUpdateMonitor,
mExecutor,
- new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer"))));
+ new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
+ mFakeFacePropertyRepository));
mScreenDecorations = spy(new ScreenDecorations(mContext, mSecureSettings,
mCommandRegistry, mUserTracker, mDisplayTracker, mDotViewController,
mThreadFactory,
mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory,
new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
- mAuthController) {
+ mFakeFacePropertyRepository, mJavaAdapter) {
@Override
public void start() {
super.start();
@@ -1235,9 +1242,9 @@
mSecureSettings, mCommandRegistry, mUserTracker, mDisplayTracker,
mDotViewController,
mThreadFactory, mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory,
- new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")), mAuthController);
+ new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
+ mFakeFacePropertyRepository, mJavaAdapter);
screenDecorations.start();
- verify(mAuthController).addCallback(mAuthControllerCallback.capture());
when(mContext.getDisplay()).thenReturn(mDisplay);
when(mDisplay.getDisplayInfo(any())).thenAnswer(new Answer<Boolean>() {
@Override
@@ -1252,9 +1259,9 @@
});
mExecutor.runAllReady();
clearInvocations(mFaceScanningDecorProvider);
-
- AuthController.Callback callback = mAuthControllerCallback.getValue();
- callback.onFaceSensorLocationChanged();
+ final Point location = new Point();
+ mFakeFacePropertyRepository.setSensorLocation(location);
+ screenDecorations.onFaceSensorLocationChanged(location);
mExecutor.runAllReady();
verify(mFaceScanningDecorProvider).onReloadResAndMeasure(any(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
index c525711..9b6c8cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
@@ -25,14 +25,12 @@
import android.widget.Button
import android.widget.SeekBar
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.model.SysUiState
+import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK
@@ -78,7 +76,6 @@
@Mock private lateinit var dialogManager: SystemUIDialogManager
@Mock private lateinit var dialogFactory: SystemUIDialog.Factory
@Mock private lateinit var userTracker: UserTracker
- private val featureFlags = FakeFeatureFlags()
@Mock private lateinit var sysuiState: SysUiState
@Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
@@ -88,7 +85,6 @@
testableLooper = TestableLooper.get(this)
val mainHandler = Handler(testableLooper.looper)
systemSettings = FakeSettings()
- featureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM, true)
// Guarantee that the systemSettings always starts with the default font scale.
systemSettings.putFloatForUser(Settings.System.FONT_SCALE, 1.0f, userTracker.userId)
secureSettings = FakeSettings()
@@ -96,29 +92,32 @@
backgroundDelayableExecutor = FakeExecutor(systemClock)
whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState)
- fontScalingDialogDelegate = spy(FontScalingDialogDelegate(
- mContext,
- dialogFactory,
- LayoutInflater.from(mContext),
- systemSettings,
- secureSettings,
- systemClock,
- userTracker,
- mainHandler,
- backgroundDelayableExecutor
- ))
+ fontScalingDialogDelegate =
+ spy(
+ FontScalingDialogDelegate(
+ mContext,
+ dialogFactory,
+ LayoutInflater.from(mContext),
+ systemSettings,
+ secureSettings,
+ systemClock,
+ userTracker,
+ mainHandler,
+ backgroundDelayableExecutor
+ )
+ )
- dialog = SystemUIDialog(
- mContext,
- 0,
- DEFAULT_DISMISS_ON_DEVICE_LOCK,
- featureFlags,
- dialogManager,
- sysuiState,
- fakeBroadcastDispatcher,
- dialogLaunchAnimator,
- fontScalingDialogDelegate
- )
+ dialog =
+ SystemUIDialog(
+ mContext,
+ 0,
+ DEFAULT_DISMISS_ON_DEVICE_LOCK,
+ dialogManager,
+ sysuiState,
+ fakeBroadcastDispatcher,
+ dialogLaunchAnimator,
+ fontScalingDialogDelegate
+ )
whenever(dialogFactory.create(any(), any())).thenReturn(dialog)
}
@@ -299,11 +298,7 @@
// Default seekbar progress for font size is 1, simulate dragging to 0 without
// releasing the finger
changeListener.onStartTrackingTouch(seekBar)
- changeListener.onProgressChanged(
- seekBar,
- /* progress= */ 0,
- /* fromUser= */ false
- )
+ changeListener.onProgressChanged(seekBar, /* progress= */ 0, /* fromUser= */ false)
backgroundDelayableExecutor.advanceClockToNext()
backgroundDelayableExecutor.runAllReady()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
index 8693d5c..7c626a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
@@ -16,6 +16,9 @@
package com.android.systemui.back.domain.interactor
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.view.ViewRootImpl
import android.window.BackEvent
import android.window.BackEvent.EDGE_LEFT
@@ -26,9 +29,8 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.statusbar.IStatusBarService
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
@@ -72,7 +74,6 @@
@OptIn(ExperimentalCoroutinesApi::class)
class BackActionInteractorTest : SysuiTestCase() {
private val testScope = TestScope()
- private val featureFlags = FakeFeatureFlags()
private val executor = FakeExecutor(FakeSystemClock())
@JvmField @Rule var mockitoRule = MockitoJUnit.rule()
@@ -107,17 +108,17 @@
statusBarKeyguardViewManager,
shadeController,
notificationShadeWindowController,
- windowRootViewVisibilityInteractor,
- featureFlags,
+ windowRootViewVisibilityInteractor
)
.apply { this.setup(qsController, shadeViewController) }
}
private val powerInteractor = PowerInteractorFactory.create().powerInteractor
+ @get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
@Before
fun setUp() {
- featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, false)
whenever(notificationShadeWindowController.windowRootView).thenReturn(windowRootView)
whenever(windowRootView.viewRootImpl).thenReturn(viewRootImpl)
whenever(viewRootImpl.onBackInvokedDispatcher).thenReturn(onBackInvokedDispatcher)
@@ -229,9 +230,9 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_SHADE)
fun animationFlagOff_onBackInvoked_keyguardNotified() {
backActionInteractor.start()
- featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, false)
windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
powerInteractor.setAwakeForTest()
val callback = getBackInvokedCallback()
@@ -243,8 +244,8 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_SHADE)
fun animationFlagOn_onBackInvoked_keyguardNotified() {
- featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true)
backActionInteractor.start()
windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
powerInteractor.setAwakeForTest()
@@ -257,8 +258,8 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_SHADE)
fun animationFlagOn_callbackIsAnimationCallback() {
- featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true)
backActionInteractor.start()
windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
powerInteractor.setAwakeForTest()
@@ -269,8 +270,8 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_SHADE)
fun onBackProgressed_shadeCannotBeCollapsed_shadeViewControllerNotNotified() {
- featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true)
backActionInteractor.start()
windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
powerInteractor.setAwakeForTest()
@@ -284,8 +285,8 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_SHADE)
fun onBackProgressed_shadeCanBeCollapsed_shadeViewControllerNotified() {
- featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true)
backActionInteractor.start()
windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
powerInteractor.setAwakeForTest()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
index 1f7dd6d..a47e288 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -29,6 +29,7 @@
import com.android.keyguard.logging.KeyguardLogger
import com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION
import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.keyguard.WakefulnessLifecycle
@@ -93,6 +94,7 @@
@Mock
private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal
+ private val facePropertyRepository = FakeFacePropertyRepository()
private val displayMetrics = DisplayMetrics()
@Captor
@@ -123,10 +125,10 @@
udfpsControllerProvider,
statusBarStateController,
displayMetrics,
- featureFlags,
KeyguardLogger(logcatLogBuffer(AuthRippleController.TAG)),
biometricUnlockController,
lightRevealScrim,
+ facePropertyRepository,
rippleView,
)
controller.init()
@@ -203,7 +205,7 @@
@Test
fun testNullFaceSensorLocationDoesNothing() {
- `when`(authController.faceSensorLocation).thenReturn(null)
+ facePropertyRepository.setSensorLocation(null)
controller.onViewAttached()
val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
@@ -271,7 +273,7 @@
fun testAnimatorRunWhenWakeAndUnlock_faceUdfpsFingerDown() {
mSetFlagsRule.disableFlags(FLAG_LIGHT_REVEAL_MIGRATION)
val faceLocation = Point(5, 5)
- `when`(authController.faceSensorLocation).thenReturn(faceLocation)
+ facePropertyRepository.setSensorLocation(faceLocation)
controller.onViewAttached()
`when`(keyguardStateController.isShowing).thenReturn(true)
`when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt
index 86b9b84..9b0e58d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt
@@ -22,7 +22,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.whenever
@@ -42,7 +42,7 @@
class FaceAuthAccessibilityDelegateTest : SysuiTestCase() {
@Mock private lateinit var hostView: View
- @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor
+ @Mock private lateinit var faceAuthInteractor: DeviceEntryFaceAuthInteractor
private lateinit var underTest: FaceAuthAccessibilityDelegate
@Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java
index 2aeba9a..3dcb3f8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java
@@ -20,6 +20,7 @@
import android.content.res.Resources;
import android.graphics.Rect;
+import android.hardware.fingerprint.FingerprintSensorProperties;
import android.view.Surface;
import androidx.test.filters.SmallTest;
@@ -63,28 +64,32 @@
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, 0/* touchY */,
- new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
)
).isEqualTo(mTouchHints[0]);
// touch at 90 degrees
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, -1/* touchY */,
- new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
)
).isEqualTo(mTouchHints[1]);
// touch at 180 degrees
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
-1 /* touchX */, 0/* touchY */,
- new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
)
).isEqualTo(mTouchHints[2]);
// touch at 270 degrees
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, 1/* touchY */,
- new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
)
).isEqualTo(mTouchHints[3]);
}
@@ -97,28 +102,32 @@
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, 0 /* touchY */,
- new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
)
).isEqualTo(mTouchHints[1]);
// touch at 90 degrees -> 180 degrees
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, -1 /* touchY */,
- new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
)
).isEqualTo(mTouchHints[2]);
// touch at 180 degrees -> 270 degrees
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
-1 /* touchX */, 0 /* touchY */,
- new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
)
).isEqualTo(mTouchHints[3]);
// touch at 270 degrees -> 0 degrees
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, 1/* touchY */,
- new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
)
).isEqualTo(mTouchHints[0]);
}
@@ -131,28 +140,32 @@
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, 0/* touchY */,
- new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
)
).isEqualTo(mTouchHints[3]);
// touch at 90 degrees -> 0 degrees
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, -1/* touchY */,
- new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
)
).isEqualTo(mTouchHints[0]);
// touch at 180 degrees -> 90 degrees
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
-1 /* touchX */, 0/* touchY */,
- new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
)
).isEqualTo(mTouchHints[1]);
// touch at 270 degrees -> 180 degrees
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, 1/* touchY */,
- new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
)
).isEqualTo(mTouchHints[2]);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
index 834179bf..a84778a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
@@ -19,6 +19,7 @@
import android.hardware.devicestate.DeviceStateManager
import android.hardware.display.DisplayManager
import android.os.Handler
+import android.util.Size
import android.view.Display
import android.view.DisplayInfo
import android.view.Surface
@@ -147,6 +148,40 @@
displayListenerCaptor.value.onDisplayChanged(Surface.ROTATION_180)
assertThat(currentRotation).isEqualTo(DisplayRotation.ROTATION_180)
}
+
+ @Test
+ fun updatesCurrentSize_whenDisplayStateChanges() =
+ testScope.runTest {
+ val currentSize by collectLastValue(underTest.currentDisplaySize)
+ runCurrent()
+
+ verify(displayManager)
+ .registerDisplayListener(
+ displayListenerCaptor.capture(),
+ same(handler),
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED)
+ )
+
+ whenever(display.getDisplayInfo(any())).then {
+ val info = it.getArgument<DisplayInfo>(0)
+ info.rotation = Surface.ROTATION_0
+ info.logicalWidth = 100
+ info.logicalHeight = 200
+ return@then true
+ }
+ displayListenerCaptor.value.onDisplayChanged(Surface.ROTATION_0)
+ assertThat(currentSize).isEqualTo(Size(100, 200))
+
+ whenever(display.getDisplayInfo(any())).then {
+ val info = it.getArgument<DisplayInfo>(0)
+ info.rotation = Surface.ROTATION_90
+ info.logicalWidth = 100
+ info.logicalHeight = 200
+ return@then true
+ }
+ displayListenerCaptor.value.onDisplayChanged(Surface.ROTATION_180)
+ assertThat(currentSize).isEqualTo(Size(200, 100))
+ }
}
private fun DeviceStateManager.captureCallback() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt
index c14ad6a..9f24d5d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt
@@ -17,10 +17,12 @@
package com.android.systemui.biometrics.data.repository
+import android.graphics.Point
import android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_NONE
import android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_PERMANENT
import android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_TIMED
import android.hardware.biometrics.SensorProperties
+import android.hardware.camera2.CameraManager
import android.hardware.face.FaceManager
import android.hardware.face.FaceSensorPropertiesInternal
import android.hardware.face.IFaceAuthenticatorsRegisteredCallback
@@ -28,9 +30,12 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.shared.model.LockoutMode
import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.res.R
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestDispatcher
@@ -45,6 +50,7 @@
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
+import org.mockito.Mockito.any
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
@@ -53,23 +59,56 @@
@SmallTest
@RunWith(JUnit4::class)
class FacePropertyRepositoryImplTest : SysuiTestCase() {
+ companion object {
+ private const val LOGICAL_CAMERA_ID_OUTER_FRONT = "0"
+ private const val LOGICAL_CAMERA_ID_INNER_FRONT = "1"
+ private const val PHYSICAL_CAMERA_ID_OUTER_FRONT = "5"
+ private const val PHYSICAL_CAMERA_ID_INNER_FRONT = "6"
+ private val OUTER_FRONT_SENSOR_LOCATION = intArrayOf(100, 10, 20)
+ private val INNER_FRONT_SENSOR_LOCATION = intArrayOf(200, 20, 30)
+ }
+
@JvmField @Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
private lateinit var underTest: FacePropertyRepository
private lateinit var dispatcher: TestDispatcher
private lateinit var testScope: TestScope
+ private val displayStateRepository = FakeDisplayStateRepository()
+ private val configurationRepository = FakeConfigurationRepository()
+
@Captor private lateinit var callback: ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback>
@Mock private lateinit var faceManager: FaceManager
+ @Captor private lateinit var cameraCallback: ArgumentCaptor<CameraManager.AvailabilityCallback>
+ @Mock private lateinit var cameraManager: CameraManager
@Before
fun setup() {
+ overrideResource(R.string.config_protectedCameraId, LOGICAL_CAMERA_ID_OUTER_FRONT)
+ overrideResource(R.string.config_protectedPhysicalCameraId, PHYSICAL_CAMERA_ID_OUTER_FRONT)
+ overrideResource(R.string.config_protectedInnerCameraId, LOGICAL_CAMERA_ID_INNER_FRONT)
+ overrideResource(
+ R.string.config_protectedInnerPhysicalCameraId,
+ PHYSICAL_CAMERA_ID_INNER_FRONT
+ )
+ overrideResource(R.array.config_face_auth_props, OUTER_FRONT_SENSOR_LOCATION)
+ overrideResource(R.array.config_inner_face_auth_props, INNER_FRONT_SENSOR_LOCATION)
+
dispatcher = StandardTestDispatcher()
testScope = TestScope(dispatcher)
underTest = createRepository(faceManager)
}
private fun createRepository(manager: FaceManager? = faceManager) =
- FacePropertyRepositoryImpl(testScope.backgroundScope, dispatcher, manager)
+ FacePropertyRepositoryImpl(
+ context,
+ context.mainExecutor,
+ testScope.backgroundScope,
+ dispatcher,
+ manager,
+ cameraManager,
+ displayStateRepository,
+ configurationRepository,
+ )
@Test
fun whenFaceManagerIsNotPresentIsNull() =
@@ -129,6 +168,75 @@
assertThat(underTest.getLockoutMode(userId)).isEqualTo(LockoutMode.NONE)
}
+ @Test
+ fun providesTheSensorLocationOfOuterCameraFromOnPhysicalCameraAvailable() {
+ testScope.runTest {
+ runCurrent()
+ collectLastValue(underTest.sensorLocation)
+
+ verify(faceManager).addAuthenticatorsRegisteredCallback(callback.capture())
+ callback.value.onAllAuthenticatorsRegistered(
+ listOf(createSensorProperties(1, SensorProperties.STRENGTH_STRONG))
+ )
+ runCurrent()
+ verify(cameraManager)
+ .registerAvailabilityCallback(any(Executor::class.java), cameraCallback.capture())
+
+ cameraCallback.value.onPhysicalCameraAvailable("1", PHYSICAL_CAMERA_ID_OUTER_FRONT)
+ runCurrent()
+
+ val sensorLocation by collectLastValue(underTest.sensorLocation)
+ assertThat(sensorLocation)
+ .isEqualTo(Point(OUTER_FRONT_SENSOR_LOCATION[0], OUTER_FRONT_SENSOR_LOCATION[1]))
+ }
+ }
+
+ @Test
+ fun providesTheSensorLocationOfInnerCameraFromOnPhysicalCameraAvailable() {
+ testScope.runTest {
+ runCurrent()
+ collectLastValue(underTest.sensorLocation)
+
+ verify(faceManager).addAuthenticatorsRegisteredCallback(callback.capture())
+ callback.value.onAllAuthenticatorsRegistered(
+ listOf(createSensorProperties(1, SensorProperties.STRENGTH_STRONG))
+ )
+ runCurrent()
+ verify(cameraManager)
+ .registerAvailabilityCallback(any(Executor::class.java), cameraCallback.capture())
+
+ cameraCallback.value.onPhysicalCameraAvailable("1", PHYSICAL_CAMERA_ID_INNER_FRONT)
+ runCurrent()
+
+ val sensorLocation by collectLastValue(underTest.sensorLocation)
+ assertThat(sensorLocation)
+ .isEqualTo(Point(INNER_FRONT_SENSOR_LOCATION[0], INNER_FRONT_SENSOR_LOCATION[1]))
+ }
+ }
+
+ @Test
+ fun providesTheSensorLocationOfCameraFromOnPhysicalCameraUnavailable() {
+ testScope.runTest {
+ runCurrent()
+ collectLastValue(underTest.sensorLocation)
+
+ verify(faceManager).addAuthenticatorsRegisteredCallback(callback.capture())
+ callback.value.onAllAuthenticatorsRegistered(
+ listOf(createSensorProperties(1, SensorProperties.STRENGTH_STRONG))
+ )
+ runCurrent()
+ verify(cameraManager)
+ .registerAvailabilityCallback(any(Executor::class.java), cameraCallback.capture())
+
+ cameraCallback.value.onPhysicalCameraUnavailable("1", PHYSICAL_CAMERA_ID_INNER_FRONT)
+ runCurrent()
+
+ val sensorLocation by collectLastValue(underTest.sensorLocation)
+ assertThat(sensorLocation)
+ .isEqualTo(Point(OUTER_FRONT_SENSOR_LOCATION[0], OUTER_FRONT_SENSOR_LOCATION[1]))
+ }
+ }
+
private fun createSensorProperties(id: Int, strength: Int) =
FaceSensorPropertiesInternal(id, strength, 0, emptyList(), 1, false, false, false)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
index 755fa02..54dbd04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
@@ -57,13 +57,13 @@
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.bouncer.ui.BouncerView
import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
import com.android.systemui.keyguard.domain.interactor.DeviceEntrySideFpsOverlayInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
import com.android.systemui.log.SideFpsLogger
import com.android.systemui.log.logcatLogBuffer
@@ -110,7 +110,7 @@
@JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
@Mock private lateinit var activityTaskManager: ActivityTaskManager
@Mock private lateinit var displayManager: DisplayManager
- @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor
+ @Mock private lateinit var faceAuthInteractor: DeviceEntryFaceAuthInteractor
@Mock
private lateinit var fingerprintInteractiveToAuthProvider: FingerprintInteractiveToAuthProvider
@Mock private lateinit var fpsUnlockTracker: FpsUnlockTracker
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
index bdca948..1fa60fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
@@ -55,13 +55,13 @@
import com.android.systemui.bouncer.ui.BouncerView
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
import com.android.systemui.keyguard.domain.interactor.DeviceEntrySideFpsOverlayInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
import com.android.systemui.log.SideFpsLogger
import com.android.systemui.log.logcatLogBuffer
@@ -101,7 +101,7 @@
@JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
@Mock private lateinit var activityTaskManager: ActivityTaskManager
- @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor
+ @Mock private lateinit var faceAuthInteractor: DeviceEntryFaceAuthInteractor
@Mock
private lateinit var fingerprintInteractiveToAuthProvider: FingerprintInteractiveToAuthProvider
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
index 3ff43c6..7d5aec6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
@@ -17,7 +17,9 @@
package com.android.systemui.bluetooth;
import static com.android.systemui.statusbar.phone.SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK;
+
import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.any;
@@ -40,8 +42,6 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastSender;
-import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.model.SysUiState;
import com.android.systemui.res.R;
@@ -72,7 +72,6 @@
LocalBluetoothLeBroadcast.class);
private final BroadcastSender mBroadcastSender = mock(BroadcastSender.class);
private BroadcastDialogDelegate mBroadcastDialogDelegate;
- private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
@Mock SystemUIDialog.Factory mSystemUIDialogFactory;
@Mock SystemUIDialogManager mDialogManager;
@Mock SysUiState mSysUiState;
@@ -91,7 +90,6 @@
when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager);
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(null);
- mFeatureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM, true);
when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
when(mSystemUIDialogFactory.create(any(), any())).thenReturn(mDialog);
@@ -110,7 +108,6 @@
mContext,
0,
DEFAULT_DISMISS_ON_DEVICE_LOCK,
- mFeatureFlags,
mDialogManager,
mSysUiState,
getFakeBroadcastDispatcher(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
index 45a426e..e796303 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
@@ -36,13 +36,13 @@
import com.android.systemui.bouncer.ui.BouncerView
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.flags.SystemPropertiesHelper
import com.android.systemui.keyguard.DismissCallbackRegistry
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.FakeTrustRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
import com.android.systemui.keyguard.shared.model.AuthenticationFlags
import com.android.systemui.res.R.string.kg_too_many_failed_attempts_countdown
import com.android.systemui.res.R.string.kg_trust_agent_disabled
@@ -122,7 +122,7 @@
fakeTrustRepository,
testScope.backgroundScope,
mSelectedUserInteractor,
- mock(KeyguardFaceAuthInteractor::class.java),
+ mock(DeviceEntryFaceAuthInteractor::class.java),
)
underTest =
BouncerMessageInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
index dacf23a..ee46f76 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -32,9 +32,9 @@
import com.android.systemui.bouncer.ui.BouncerView
import com.android.systemui.bouncer.ui.BouncerViewDelegate
import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
@@ -73,7 +73,7 @@
@Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
- @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor
+ @Mock private lateinit var faceAuthInteractor: DeviceEntryFaceAuthInteractor
private lateinit var mainHandler: FakeHandler
private lateinit var underTest: PrimaryBouncerInteractor
private lateinit var resources: TestableResources
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
index 8f65fc8..bcef67e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
@@ -24,19 +24,28 @@
import android.content.IntentFilter
import android.content.pm.ApplicationInfo
import android.content.pm.ServiceInfo
+import android.os.UserHandle
import android.os.UserManager
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.data.repository.fakePackageChangeRepository
+import com.android.systemui.common.data.repository.packageChangeRepository
+import com.android.systemui.common.data.shared.model.PackageChangeModel
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
import com.android.systemui.controls.panels.FakeSelectedComponentRepository
+import com.android.systemui.controls.panels.SelectedComponentRepository
import com.android.systemui.controls.ui.SelectedItem
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.settings.UserTracker
+import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
@@ -48,6 +57,9 @@
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.Optional
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -61,10 +73,13 @@
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidTestingRunner::class)
class ControlsStartableTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
@Mock private lateinit var controlsController: ControlsController
@Mock private lateinit var controlsListingController: ControlsListingController
@Mock private lateinit var userTracker: UserTracker
@@ -72,7 +87,7 @@
@Mock private lateinit var userManager: UserManager
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
- private val preferredPanelsRepository = FakeSelectedComponentRepository()
+ private lateinit var preferredPanelsRepository: FakeSelectedComponentRepository
private lateinit var fakeExecutor: FakeExecutor
@@ -81,8 +96,10 @@
MockitoAnnotations.initMocks(this)
whenever(authorizedPanelsRepository.getPreferredPackages()).thenReturn(setOf())
whenever(userManager.isUserUnlocked(anyInt())).thenReturn(true)
+ whenever(userTracker.userHandle).thenReturn(UserHandle.of(1))
fakeExecutor = FakeExecutor(FakeSystemClock())
+ preferredPanelsRepository = FakeSelectedComponentRepository()
}
@Test
@@ -306,6 +323,100 @@
verify(controlsController, never()).setPreferredSelection(any())
}
+ @Test
+ fun testSelectedComponentIsUninstalled() =
+ with(kosmos) {
+ testScope.runTest {
+ val selectedComponent =
+ SelectedComponentRepository.SelectedComponent(
+ "panel",
+ TEST_COMPONENT_PANEL,
+ isPanel = true
+ )
+ preferredPanelsRepository.setSelectedComponent(selectedComponent)
+ val activeUser = UserHandle.of(100)
+ whenever(userTracker.userHandle).thenReturn(activeUser)
+
+ createStartable(enabled = true).onBootCompleted()
+ fakeExecutor.runAllReady()
+ runCurrent()
+
+ assertThat(preferredPanelsRepository.getSelectedComponent())
+ .isEqualTo(selectedComponent)
+ fakePackageChangeRepository.notifyChange(
+ PackageChangeModel.Uninstalled(
+ packageName = TEST_PACKAGE_PANEL,
+ packageUid = UserHandle.getUid(100, 1)
+ )
+ )
+ runCurrent()
+
+ assertThat(preferredPanelsRepository.getSelectedComponent()).isNull()
+ }
+ }
+
+ @Test
+ fun testSelectedComponentIsChanged() =
+ with(kosmos) {
+ testScope.runTest {
+ val selectedComponent =
+ SelectedComponentRepository.SelectedComponent(
+ "panel",
+ TEST_COMPONENT_PANEL,
+ isPanel = true
+ )
+ preferredPanelsRepository.setSelectedComponent(selectedComponent)
+ val activeUser = UserHandle.of(100)
+ whenever(userTracker.userHandle).thenReturn(activeUser)
+
+ createStartable(enabled = true).onBootCompleted()
+ fakeExecutor.runAllReady()
+ runCurrent()
+
+ fakePackageChangeRepository.notifyChange(
+ PackageChangeModel.Changed(
+ packageName = TEST_PACKAGE_PANEL,
+ packageUid = UserHandle.getUid(100, 1)
+ )
+ )
+ runCurrent()
+
+ assertThat(preferredPanelsRepository.getSelectedComponent())
+ .isEqualTo(selectedComponent)
+ }
+ }
+
+ @Test
+ fun testOtherPackageIsUninstalled() =
+ with(kosmos) {
+ testScope.runTest {
+ val selectedComponent =
+ SelectedComponentRepository.SelectedComponent(
+ "panel",
+ TEST_COMPONENT_PANEL,
+ isPanel = true
+ )
+ preferredPanelsRepository.setSelectedComponent(selectedComponent)
+ val activeUser = UserHandle.of(100)
+ whenever(userTracker.userHandle).thenReturn(activeUser)
+
+ createStartable(enabled = true).onBootCompleted()
+ fakeExecutor.runAllReady()
+ runCurrent()
+
+ fakePackageChangeRepository.notifyChange(
+ PackageChangeModel.Uninstalled(
+ packageName = TEST_PACKAGE,
+ packageUid = UserHandle.getUid(100, 1)
+ )
+ )
+ runCurrent()
+
+ assertThat(preferredPanelsRepository.getSelectedComponent())
+ .isEqualTo(selectedComponent)
+ }
+ }
+
private fun setUpControlsListingControls(listings: List<ControlsServiceInfo>) {
doAnswer { doReturn(listings).`when`(controlsListingController).getCurrentServices() }
.`when`(controlsListingController)
@@ -326,11 +437,14 @@
}
}
return ControlsStartable(
+ kosmos.applicationCoroutineScope,
+ kosmos.testDispatcher,
fakeExecutor,
component,
userTracker,
authorizedPanelsRepository,
preferredPanelsRepository,
+ kosmos.packageChangeRepository,
userManager,
broadcastDispatcher,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
index 59bcf01..d5c3641 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
@@ -24,13 +24,13 @@
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.domain.interactor.deviceEntryHapticsInteractor
+import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
import com.android.systemui.keyevent.data.repository.fakeKeyEventRepository
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.data.repository.powerRepository
import com.android.systemui.power.shared.model.WakeSleepReason
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/FaceWakeUpTriggersConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfigTest.kt
similarity index 91%
rename from packages/SystemUI/tests/src/com/android/keyguard/FaceWakeUpTriggersConfigTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfigTest.kt
index 94c34a5..e9b4bbb 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/FaceWakeUpTriggersConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfigTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.keyguard
+package com.android.systemui.deviceentry.data.repository
import android.os.PowerManager
import android.testing.AndroidTestingRunner
@@ -71,6 +71,6 @@
wakeUpTriggers
)
- return FaceWakeUpTriggersConfig(mContext.getResources(), globalSettings, dumpManager)
+ return FaceWakeUpTriggersConfigImpl(mContext.resources, globalSettings, dumpManager)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
similarity index 93%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
index 769cf45..368d1d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
@@ -1,21 +1,20 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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
+ * 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
+ * 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.
- *
+ * 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.domain.interactor
+package com.android.systemui.deviceentry.domain.interactor
import android.app.trust.TrustManager
import android.content.pm.UserInfo
@@ -25,8 +24,6 @@
import android.os.PowerManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.keyguard.FaceAuthUiEvent
-import com.android.keyguard.FaceWakeUpTriggersConfig
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.SysuiTestCase
@@ -42,6 +39,9 @@
import com.android.systemui.bouncer.ui.BouncerView
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfig
+import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
+import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
@@ -49,7 +49,8 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -87,9 +88,9 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
-class KeyguardFaceAuthInteractorTest : SysuiTestCase() {
+class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() {
- private lateinit var underTest: SystemUIKeyguardFaceAuthInteractor
+ private lateinit var underTest: SystemUIDeviceEntryFaceAuthInteractor
private lateinit var testScope: TestScope
private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
@@ -133,7 +134,7 @@
fakeBiometricSettingsRepository = FakeBiometricSettingsRepository()
underTest =
- SystemUIKeyguardFaceAuthInteractor(
+ SystemUIDeviceEntryFaceAuthInteractor(
mContext,
testScope.backgroundScope,
dispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/FaceAuthReasonTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/shared/FaceAuthReasonTest.kt
similarity index 92%
rename from packages/SystemUI/tests/src/com/android/keyguard/FaceAuthReasonTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/deviceentry/shared/FaceAuthReasonTest.kt
index 68d0f41..ef89752 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/FaceAuthReasonTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/shared/FaceAuthReasonTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -13,7 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.keyguard
+
+package com.android.systemui.deviceentry.shared
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index b38c9ec..b57cf53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -72,6 +72,7 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.foldables.FoldGracePeriodProvider;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.InstanceId;
import com.android.internal.logging.UiEventLogger;
@@ -324,6 +325,54 @@
@Test
@TestableLooper.RunWithLooper(setAsMainLooper = true)
+ public void showKeyguardAfterKeyguardNotEnabled() {
+ // GIVEN feature is enabled
+ final FoldGracePeriodProvider mockedFoldGracePeriodProvider =
+ mock(FoldGracePeriodProvider.class);
+ mViewMediator.mFoldGracePeriodProvider = mockedFoldGracePeriodProvider;
+ when(mockedFoldGracePeriodProvider.isEnabled()).thenReturn(true);
+
+ // GIVEN keyguard is not enabled and isn't showing
+ mViewMediator.onSystemReady();
+ mViewMediator.setKeyguardEnabled(false);
+ TestableLooper.get(this).processAllMessages();
+ captureKeyguardUpdateMonitorCallback();
+ assertFalse(mViewMediator.isShowingAndNotOccluded());
+
+ // WHEN showKeyguard is requested
+ mViewMediator.showDismissibleKeyguard();
+
+ // THEN keyguard is shown
+ TestableLooper.get(this).processAllMessages();
+ assertTrue(mViewMediator.isShowingAndNotOccluded());
+ }
+
+ @Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
+ public void showKeyguardAfterKeyguardNotEnabled_featureNotEnabled() {
+ // GIVEN feature is NOT enabled
+ final FoldGracePeriodProvider mockedFoldGracePeriodProvider =
+ mock(FoldGracePeriodProvider.class);
+ mViewMediator.mFoldGracePeriodProvider = mockedFoldGracePeriodProvider;
+ when(mockedFoldGracePeriodProvider.isEnabled()).thenReturn(false);
+
+ // GIVEN keyguard is not enabled and isn't showing
+ mViewMediator.onSystemReady();
+ mViewMediator.setKeyguardEnabled(false);
+ TestableLooper.get(this).processAllMessages();
+ captureKeyguardUpdateMonitorCallback();
+ assertFalse(mViewMediator.isShowingAndNotOccluded());
+
+ // WHEN showKeyguard is requested
+ mViewMediator.showDismissibleKeyguard();
+
+ // THEN keyguard is still NOT shown
+ TestableLooper.get(this).processAllMessages();
+ assertFalse(mViewMediator.isShowingAndNotOccluded());
+ }
+
+ @Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
public void doNotHideKeyguard_whenLockdown_onKeyguardNotEnabledExternally() {
// GIVEN keyguard is enabled and lockdown occurred so the keyguard is showing
mViewMediator.onSystemReady();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
index 027dfa1..c4df27c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.bouncer.ui.BouncerView
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
@@ -62,7 +63,7 @@
class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() {
@JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
- @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor
+ @Mock private lateinit var faceAuthInteractor: DeviceEntryFaceAuthInteractor
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
index 1f245f1..7358b9d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
@@ -147,7 +147,7 @@
trustRepository,
testScope.backgroundScope,
mSelectedUserInteractor,
- keyguardFaceAuthInteractor = mock(),
+ deviceEntryFaceAuthInteractor = mock(),
),
AlternateBouncerInteractor(
statusBarStateController = mock(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
index 0a5b124..fb101dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
@@ -78,7 +78,7 @@
mMediaData = new MediaData(
USER_ID, true, APP, null, ARTIST, TITLE, null,
new ArrayList<>(), new ArrayList<>(), null, PACKAGE, null, null, null, true, null,
- MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L,
+ MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L, 0L,
InstanceId.fakeInstanceId(-1), -1, false, null);
mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME, null, false);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index 018fa9e..a92111e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -394,6 +394,7 @@
public void setTiles_differentTiles_extraTileRemoved() {
when(mQSHost.getTiles()).thenReturn(List.of(mQSTile, mOtherTile));
mController.setTiles();
+ assertEquals(2, mController.mRecords.size());
clearInvocations(mQSPanel);
@@ -402,6 +403,7 @@
verify(mQSPanel, times(1)).removeTile(any());
verify(mQSPanel, never()).addTile(any());
+ assertEquals(1, mController.mRecords.size());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
index 02d40da..ea2b22c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
@@ -115,7 +115,7 @@
.isEqualTo(
"test_spec:\n" +
" QSTileState(" +
- "icon=() -> com.android.systemui.common.shared.model.Icon, " +
+ "icon=() -> com.android.systemui.common.shared.model.Icon?, " +
"label=test_data, " +
"activationState=INACTIVE, " +
"secondaryLabel=null, " +
diff --git a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
index 35bf775..dc211303 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
@@ -83,7 +83,6 @@
public void setup() {
MockitoAnnotations.initMocks(this);
- mFeatureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM, true);
when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
when(mSystemUIDialogFactory.create()).thenReturn(mSystemUIDialog);
when(mSystemUIDialog.getContext()).thenReturn(mContext);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
index 7ce51ae..86ab01c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
@@ -105,7 +105,6 @@
spy(
SystemUIDialog.Factory(
context,
- flags,
systemUIDialogManager,
sysuiState,
broadcastDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index cb90cc5..0ba99f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -44,7 +44,6 @@
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
import com.android.systemui.mediaprojection.SessionCreationSource;
@@ -113,7 +112,6 @@
mDialogFactory = new TestSystemUIDialogFactory(
mContext,
- mFeatureFlags,
Dependency.get(SystemUIDialogManager.class),
Dependency.get(SysUiState.class),
Dependency.get(BroadcastDispatcher.class),
@@ -313,14 +311,12 @@
TestSystemUIDialogFactory(
Context context,
- FeatureFlags featureFlags,
SystemUIDialogManager systemUIDialogManager,
SysUiState sysUiState,
BroadcastDispatcher broadcastDispatcher,
DialogLaunchAnimator dialogLaunchAnimator) {
super(
context,
- featureFlags,
systemUIDialogManager,
sysUiState,
broadcastDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
index 8f696e7..2399536 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
@@ -74,7 +74,6 @@
val systemUIDialogFactory =
SystemUIDialog.Factory(
context,
- Dependency.get(FeatureFlags::class.java),
Dependency.get(SystemUIDialogManager::class.java),
Dependency.get(SysUiState::class.java),
Dependency.get(BroadcastDispatcher::class.java),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 2220756..3132767 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -86,6 +86,7 @@
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
import com.android.systemui.common.ui.view.LongPressHandlingView;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.dump.DumpManager;
@@ -98,7 +99,6 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
@@ -337,7 +337,7 @@
protected ArgumentCaptor<NotificationStackScrollLayout.OnEmptySpaceClickListener>
mEmptySpaceClickListenerCaptor;
@Mock protected ActivityStarter mActivityStarter;
- @Mock protected KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
+ @Mock protected DeviceEntryFaceAuthInteractor mDeviceEntryFaceAuthInteractor;
@Mock private JavaAdapter mJavaAdapter;
@Mock private CastController mCastController;
@Mock private SharedNotificationContainerInteractor mSharedNotificationContainerInteractor;
@@ -382,7 +382,6 @@
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mFeatureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, false);
mFeatureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false);
mFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false);
mFeatureFlags.set(Flags.QS_USER_DETAIL_SHORTCUT, false);
@@ -730,7 +729,7 @@
mActiveNotificationsInteractor,
mShadeAnimationInteractor,
mKeyguardViewConfigurator,
- mKeyguardFaceAuthInteractor,
+ mDeviceEntryFaceAuthInteractor,
new ResourcesSplitShadeStateController(),
mPowerInteractor,
mKeyguardClockPositionAlgorithm,
@@ -800,7 +799,7 @@
mInteractionJankMonitor,
mShadeLog,
mDumpManager,
- mKeyguardFaceAuthInteractor,
+ mDeviceEntryFaceAuthInteractor,
mShadeRepository,
mShadeInteractor,
mActiveNotificationsInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 3cbb9bb..2e8d46a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -1120,7 +1120,7 @@
mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
- verify(mKeyguardFaceAuthInteractor).onNotificationPanelClicked();
+ verify(mDeviceEntryFaceAuthInteractor).onNotificationPanelClicked();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index c21addc..ee7c6c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -62,7 +62,7 @@
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -257,7 +257,7 @@
FakeTrustRepository(),
testScope.backgroundScope,
mSelectedUserInteractor,
- mock(KeyguardFaceAuthInteractor::class.java)
+ mock(DeviceEntryFaceAuthInteractor::class.java)
),
facePropertyRepository = FakeFacePropertyRepository(),
deviceEntryFingerprintAuthRepository =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index eb5633b..727a6c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -42,6 +42,7 @@
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository;
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlagsClassic;
@@ -55,7 +56,6 @@
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.media.controls.pipeline.MediaDataManager;
@@ -378,7 +378,7 @@
mInteractionJankMonitor,
mShadeLogger,
mDumpManager,
- mock(KeyguardFaceAuthInteractor.class),
+ mock(DeviceEntryFaceAuthInteractor.class),
mShadeRepository,
mShadeInteractor,
mActiveNotificationsInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 0c6f456..757f16c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -598,6 +598,17 @@
@Test
@EnableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS)
+ public void testEarlyUserSwitch() {
+ mLockscreenUserManager =
+ new TestNotificationLockscreenUserManager(mContext);
+ mBackgroundExecutor.runAllReady();
+ mLockscreenUserManager.mUserChangedCallback.onUserChanging(
+ mCurrentUser.id, mContext);
+ // no crash!
+ }
+
+ @Test
+ @EnableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS)
public void testKeyguardManager_noPrivateNotifications() {
Mockito.clearInvocations(mDevicePolicyManager);
// User allows notifications
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index e339636..316f2b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -334,15 +334,8 @@
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
- // CentralSurfacesImpl's runtime flag check fails if the flag is absent.
- // This value is unused, because test manifest is opted in.
- mFeatureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI, false);
// Set default value to avoid IllegalStateException.
mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, false);
- // For the Shade to respond to Back gesture, we must enable the event routing
- mFeatureFlags.set(Flags.WM_SHADE_ALLOW_BACK_GESTURE, true);
- // For the Shade to animate during the Back gesture, we must enable the animation flag.
- mFeatureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true);
mSetFlagsRule.enableFlags(FLAG_LIGHT_REVEAL_MIGRATION);
// Turn AOD on and toggle feature flag for jank fixes
mFeatureFlags.set(Flags.ZJ_285570694_LOCKSCREEN_TRANSITION_FROM_AOD, true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt
index 7eba3b46..c44c178 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt
@@ -22,11 +22,15 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
import com.android.systemui.util.time.FakeSystemClock
import junit.framework.Assert
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
+import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@@ -72,6 +76,34 @@
Assert.assertEquals(true, controller.hasActiveProfile())
}
+ @Test
+ fun callbackRemovedWhileDispatching_doesntCrash() {
+ var remove = false
+ val callback =
+ object : ManagedProfileController.Callback {
+ override fun onManagedProfileChanged() {
+ if (remove) {
+ controller.removeCallback(this)
+ }
+ }
+
+ override fun onManagedProfileRemoved() {
+ if (remove) {
+ controller.removeCallback(this)
+ }
+ }
+ }
+ controller.addCallback(callback)
+ controller.addCallback(TestCallback)
+
+ remove = true
+ setupWorkingProfile(1)
+
+ val captor = argumentCaptor<UserTracker.Callback>()
+ verify(userTracker).addCallback(capture(captor), any())
+ captor.value.onProfilesChanged(userManager.getEnabledProfiles(1))
+ }
+
private fun setupWorkingProfile(userId: Int) {
`when`(userManager.getEnabledProfiles(userId))
.thenReturn(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 225ddb6..8dde935 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -37,6 +37,9 @@
import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.service.trust.TrustAgentService;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -98,6 +101,7 @@
import com.google.common.truth.Truth;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -165,6 +169,8 @@
@Captor
private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallback;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@Before
public void setUp() {
@@ -175,7 +181,6 @@
when(mBouncerView.getDelegate()).thenReturn(mBouncerViewDelegate);
when(mBouncerViewDelegate.getBackCallback()).thenReturn(mBouncerViewDelegateBackCallback);
mFeatureFlags = new FakeFeatureFlags();
- mFeatureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM, true);
mFeatureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false);
mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR);
@@ -584,6 +589,7 @@
}
@Test
+ @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_PREDICTIVE_BACK_ANIMATE_BOUNCER)
public void testPredictiveBackCallback_registration() {
/* verify that a predictive back callback is registered when the bouncer becomes visible */
mBouncerExpansionCallback.onVisibilityChanged(true);
@@ -598,6 +604,7 @@
}
@Test
+ @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_PREDICTIVE_BACK_ANIMATE_BOUNCER)
public void testPredictiveBackCallback_invocationHidesBouncer() {
mBouncerExpansionCallback.onVisibilityChanged(true);
/* capture the predictive back callback during registration */
@@ -615,6 +622,7 @@
}
@Test
+ @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_PREDICTIVE_BACK_ANIMATE_BOUNCER)
public void testPredictiveBackCallback_noBackAnimationForFullScreenBouncer() {
when(mKeyguardSecurityModel.getSecurityMode(anyInt()))
.thenReturn(KeyguardSecurityModel.SecurityMode.SimPin);
@@ -634,6 +642,7 @@
}
@Test
+ @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_PREDICTIVE_BACK_ANIMATE_BOUNCER)
public void testPredictiveBackCallback_forwardsBackDispatches() {
mBouncerExpansionCallback.onVisibilityChanged(true);
/* capture the predictive back callback during registration */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index 2f79955..a5c766d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -17,10 +17,12 @@
package com.android.systemui.statusbar.policy;
import static android.os.BatteryManager.EXTRA_PRESENT;
+
import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker;
import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS;
+
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -59,6 +61,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -286,6 +289,24 @@
Assert.assertFalse(mBatteryController.isIncompatibleCharging());
}
+ @Test
+ public void callbackRemovedWhileDispatching_doesntCrash() {
+ final AtomicBoolean remove = new AtomicBoolean(false);
+ BatteryStateChangeCallback callback = new BatteryStateChangeCallback() {
+ @Override
+ public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
+ if (remove.get()) {
+ mBatteryController.removeCallback(this);
+ }
+ }
+ };
+ mBatteryController.addCallback(callback);
+ // Add another callback so the iteration continues
+ mBatteryController.addCallback(new BatteryStateChangeCallback() {});
+ remove.set(true);
+ mBatteryController.fireBatteryLevelChanged();
+ }
+
private void setupIncompatibleCharging() {
final List<UsbPort> usbPorts = new ArrayList<>();
usbPorts.add(mUsbPort);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java
index b8e4306..68c1b8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java
@@ -127,6 +127,25 @@
}
}
+ /** Regression test for b/317700495 */
+ @Test
+ public void removeCallbackWhileIterating_doesntCrash() {
+ final AtomicBoolean remove = new AtomicBoolean(false);
+ Callback callback = new Callback() {
+ @Override
+ public void onCastDevicesChanged() {
+ if (remove.get()) {
+ mController.removeCallback(this);
+ }
+ }
+ };
+ mController.addCallback(callback);
+ // Add another callback so the iteration continues
+ mController.addCallback(() -> {});
+ remove.set(true);
+ mController.fireOnCastDevicesChanged();
+ }
+
@Test
public void hasConnectedCastDevice_connected() {
CastController.CastDevice castDevice = new CastController.CastDevice();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
index db0029a..777fa28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
@@ -26,6 +26,8 @@
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.dump.DumpManager
import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.time.FakeSystemClock
import java.util.concurrent.Executor
@@ -128,11 +130,42 @@
verify(cameraManager).setTorchMode(id, enable)
}
+ @Test
+ fun testCallbackRemovedWhileDispatching_doesntCrash() {
+ injectCamera()
+ var remove = false
+ val callback = object : FlashlightController.FlashlightListener {
+ override fun onFlashlightChanged(enabled: Boolean) {
+ if (remove) {
+ controller.removeCallback(this)
+ }
+ }
+
+ override fun onFlashlightError() {}
+
+ override fun onFlashlightAvailabilityChanged(available: Boolean) {}
+ }
+ controller.addCallback(callback)
+ controller.addCallback(object : FlashlightController.FlashlightListener {
+ override fun onFlashlightChanged(enabled: Boolean) {}
+
+ override fun onFlashlightError() {}
+
+ override fun onFlashlightAvailabilityChanged(available: Boolean) {}
+ })
+ backgroundExecutor.runAllReady()
+
+ val captor = argumentCaptor<CameraManager.TorchCallback>()
+ verify(cameraManager).registerTorchCallback(any(), capture(captor))
+ remove = true
+ captor.value.onTorchModeChanged(ID, true)
+ }
+
private fun injectCamera(
flash: Boolean = true,
facing: Int = CameraCharacteristics.LENS_FACING_BACK
): String {
- val cameraID = "ID"
+ val cameraID = ID
val camera = CameraCharacteristics(CameraMetadataNative().apply {
set(CameraCharacteristics.FLASH_INFO_AVAILABLE, flash)
set(CameraCharacteristics.LENS_FACING, facing)
@@ -141,4 +174,8 @@
`when`(cameraManager.getCameraCharacteristics(cameraID)).thenReturn(camera)
return cameraID
}
+
+ companion object {
+ private const val ID = "ID"
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt
index 89989ce..b03edaf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt
@@ -27,6 +27,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
+import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
@@ -130,4 +131,61 @@
controller.mPermControllerChangeReceiver.onReceive(context, testIntent)
verify(listener, never()).onSafetyCenterEnableChanged(true)
}
+
+ @Test
+ fun listenerRemovedWhileDispatching_doesNotCrash() {
+ var remove = false
+ val callback = object : SafetyController.Listener {
+ override fun onSafetyCenterEnableChanged(isSafetyCenterEnabled: Boolean) {
+ if (remove) {
+ controller.removeCallback(this)
+ }
+ }
+ }
+
+ controller.addCallback(callback)
+ controller.addCallback {}
+
+ remove = true
+
+ `when`(scm.isSafetyCenterEnabled).thenReturn(true)
+ val testIntent = Intent(Intent.ACTION_PACKAGE_CHANGED)
+ testIntent.data = Uri.parse("package:$TEST_PC_PKG")
+ controller.mPermControllerChangeReceiver.onReceive(context, testIntent)
+ }
+
+ @Test
+ fun listenerRemovedWhileDispatching_otherCallbacksCalled() {
+ var remove = false
+ var called = false
+
+ val callback1 = object : SafetyController.Listener {
+ override fun onSafetyCenterEnableChanged(isSafetyCenterEnabled: Boolean) {
+ if (remove) {
+ controller.removeCallback(this)
+ }
+ }
+ }
+
+ val callback2 = object : SafetyController.Listener {
+ override fun onSafetyCenterEnableChanged(isSafetyCenterEnabled: Boolean) {
+ // When the first callback is removed, we track if this is called
+ if (remove) {
+ called = true
+ }
+ }
+ }
+
+ controller.addCallback(callback1)
+ controller.addCallback(callback2)
+
+ remove = true
+
+ `when`(scm.isSafetyCenterEnabled).thenReturn(true)
+ val testIntent = Intent(Intent.ACTION_PACKAGE_CHANGED)
+ testIntent.data = Uri.parse("package:$TEST_PC_PKG")
+ controller.mPermControllerChangeReceiver.onReceive(context, testIntent)
+
+ assertThat(called).isTrue()
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
index c35bc69..1dab84e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
@@ -63,6 +63,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -217,6 +218,28 @@
), any(NetworkCallback.class));
}
+ @Test
+ public void testRemoveCallbackWhileDispatch_doesntCrash() {
+ final AtomicBoolean remove = new AtomicBoolean(false);
+ SecurityController.SecurityControllerCallback callback =
+ new SecurityController.SecurityControllerCallback() {
+ @Override
+ public void onStateChanged() {
+ if (remove.get()) {
+ mSecurityController.removeCallback(this);
+ }
+ }
+ };
+ mSecurityController.addCallback(callback);
+ // Add another callback so the iteration continues
+ mSecurityController.addCallback(() -> {});
+ mBgExecutor.runAllReady();
+ remove.set(true);
+
+ mSecurityController.onUserSwitched(10);
+ mBgExecutor.runAllReady();
+ }
+
/**
* refresh CA certs by sending a user unlocked broadcast for the desired user
*/
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
index 6825f65..f1a2c28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
@@ -47,6 +47,7 @@
import org.mockito.MockitoAnnotations;
import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
@SmallTest
@@ -174,4 +175,26 @@
}
}
+
+ @Test
+ public void testCallbackRemovedWhileDispatching_doesntCrash() {
+ final AtomicBoolean remove = new AtomicBoolean(false);
+ mGlobalSettings.putInt(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
+ TestableLooper.get(this).processAllMessages();
+ final ZenModeController.Callback callback = new ZenModeController.Callback() {
+ @Override
+ public void onZenChanged(int zen) {
+ if (remove.get()) {
+ mController.removeCallback(this);
+ }
+ }
+ };
+ mController.addCallback(callback);
+ mController.addCallback(new ZenModeController.Callback() {});
+
+ remove.set(true);
+
+ mGlobalSettings.putInt(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_NO_INTERRUPTIONS);
+ TestableLooper.get(this).processAllMessages();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
new file mode 100644
index 0000000..ee2e5ad
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.unfold
+
+import android.content.Context
+import android.content.res.Resources
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.DeviceStateRepository
+import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.ScreenPowerState
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessModel
+import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.shared.system.SysUiStatsLog
+import com.android.systemui.unfold.DisplaySwitchLatencyTracker.Companion.FOLDABLE_DEVICE_STATE_CLOSED
+import com.android.systemui.unfold.DisplaySwitchLatencyTracker.Companion.FOLDABLE_DEVICE_STATE_HALF_OPEN
+import com.android.systemui.unfold.DisplaySwitchLatencyTracker.DisplaySwitchLatencyEvent
+import com.android.systemui.unfold.data.repository.UnfoldTransitionRepositoryImpl
+import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractorImpl
+import com.android.systemui.util.animation.data.repository.AnimationStatusRepository
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class DisplaySwitchLatencyTrackerTest : SysuiTestCase() {
+ private lateinit var displaySwitchLatencyTracker: DisplaySwitchLatencyTracker
+ @Captor private lateinit var loggerArgumentCaptor: ArgumentCaptor<DisplaySwitchLatencyEvent>
+
+ private val mockContext = mock<Context>()
+ private val resources = mock<Resources>()
+ private val foldStateRepository = mock<DeviceStateRepository>()
+ private val powerInteractor = mock<PowerInteractor>()
+ private val animationStatusRepository = mock<AnimationStatusRepository>()
+ private val keyguardInteractor = mock<KeyguardInteractor>()
+ private val displaySwitchLatencyLogger = mock<DisplaySwitchLatencyLogger>()
+
+ private val nonEmptyClosedDeviceStatesArray: IntArray = IntArray(2) { 0 }
+ private val testDispatcher: TestDispatcher = StandardTestDispatcher()
+ private val testScope: TestScope = TestScope(testDispatcher)
+ private val isAsleep = MutableStateFlow(false)
+ private val isAodAvailable = MutableStateFlow(false)
+ private val deviceState = MutableStateFlow(DeviceState.UNFOLDED)
+ private val screenPowerState = MutableStateFlow(ScreenPowerState.SCREEN_ON)
+ private val areAnimationEnabled = MutableStateFlow(true)
+ private val lastWakefulnessEvent = MutableStateFlow(WakefulnessModel())
+ private val systemClock = FakeSystemClock()
+ private val unfoldTransitionProgressProvider = TestUnfoldTransitionProvider()
+ private val unfoldTransitionRepository =
+ UnfoldTransitionRepositoryImpl(Optional.of(unfoldTransitionProgressProvider))
+ private val unfoldTransitionInteractor =
+ UnfoldTransitionInteractorImpl(unfoldTransitionRepository)
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ whenever(mockContext.resources).thenReturn(resources)
+ whenever(resources.getIntArray(R.array.config_foldedDeviceStates))
+ .thenReturn(nonEmptyClosedDeviceStatesArray)
+ whenever(foldStateRepository.state).thenReturn(deviceState)
+ whenever(powerInteractor.isAsleep).thenReturn(isAsleep)
+ whenever(animationStatusRepository.areAnimationsEnabled()).thenReturn(areAnimationEnabled)
+ whenever(powerInteractor.screenPowerState).thenReturn(screenPowerState)
+ whenever(keyguardInteractor.isAodAvailable).thenReturn(isAodAvailable)
+ whenever(powerInteractor.detailedWakefulness).thenReturn(lastWakefulnessEvent)
+
+ displaySwitchLatencyTracker =
+ DisplaySwitchLatencyTracker(
+ mockContext,
+ foldStateRepository,
+ powerInteractor,
+ unfoldTransitionInteractor,
+ animationStatusRepository,
+ keyguardInteractor,
+ testDispatcher.asExecutor(),
+ testScope.backgroundScope,
+ displaySwitchLatencyLogger,
+ systemClock
+ )
+ }
+
+ @Test
+ fun unfold_logsLatencyTillTransitionStarted() {
+ testScope.runTest {
+ areAnimationEnabled.emit(true)
+
+ displaySwitchLatencyTracker.start()
+ deviceState.emit(DeviceState.FOLDED)
+ screenPowerState.emit(ScreenPowerState.SCREEN_OFF)
+ systemClock.advanceTime(50)
+ runCurrent()
+ deviceState.emit(DeviceState.HALF_FOLDED)
+ runCurrent()
+ systemClock.advanceTime(50)
+ screenPowerState.emit(ScreenPowerState.SCREEN_ON)
+ systemClock.advanceTime(200)
+ unfoldTransitionProgressProvider.onTransitionStarted()
+ runCurrent()
+ deviceState.emit(DeviceState.UNFOLDED)
+
+ verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor))
+ val loggedEvent = loggerArgumentCaptor.value
+ val expectedLoggedEvent =
+ DisplaySwitchLatencyEvent(
+ latencyMs = 250,
+ fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED,
+ toFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN
+ )
+ assertThat(loggedEvent).isEqualTo(expectedLoggedEvent)
+ }
+ }
+
+ @Test
+ fun unfold_progressUnavailable_logsLatencyTillScreenTurnedOn() {
+ testScope.runTest {
+ val unfoldTransitionInteractorWithEmptyProgressProvider =
+ UnfoldTransitionInteractorImpl(UnfoldTransitionRepositoryImpl(Optional.empty()))
+ displaySwitchLatencyTracker =
+ DisplaySwitchLatencyTracker(
+ mockContext,
+ foldStateRepository,
+ powerInteractor,
+ unfoldTransitionInteractorWithEmptyProgressProvider,
+ animationStatusRepository,
+ keyguardInteractor,
+ testDispatcher.asExecutor(),
+ testScope.backgroundScope,
+ displaySwitchLatencyLogger,
+ systemClock
+ )
+ areAnimationEnabled.emit(true)
+
+ displaySwitchLatencyTracker.start()
+ deviceState.emit(DeviceState.FOLDED)
+ screenPowerState.emit(ScreenPowerState.SCREEN_OFF)
+ systemClock.advanceTime(50)
+ runCurrent()
+ deviceState.emit(DeviceState.HALF_FOLDED)
+ systemClock.advanceTime(50)
+ runCurrent()
+ screenPowerState.emit(ScreenPowerState.SCREEN_ON)
+ systemClock.advanceTime(50)
+ runCurrent()
+ systemClock.advanceTime(200)
+ unfoldTransitionProgressProvider.onTransitionStarted()
+ runCurrent()
+ deviceState.emit(DeviceState.UNFOLDED)
+
+ verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor))
+ val loggedEvent = loggerArgumentCaptor.value
+ val expectedLoggedEvent =
+ DisplaySwitchLatencyEvent(
+ latencyMs = 50,
+ fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED,
+ toFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN
+ )
+ assertThat(loggedEvent).isEqualTo(expectedLoggedEvent)
+ }
+ }
+
+ @Test
+ fun unfold_animationDisabled_logsLatencyTillScreenTurnedOn() {
+ testScope.runTest {
+ areAnimationEnabled.emit(false)
+
+ displaySwitchLatencyTracker.start()
+ deviceState.emit(DeviceState.FOLDED)
+ screenPowerState.emit(ScreenPowerState.SCREEN_OFF)
+ systemClock.advanceTime(50)
+ runCurrent()
+ deviceState.emit(DeviceState.HALF_FOLDED)
+ systemClock.advanceTime(50)
+ runCurrent()
+ screenPowerState.emit(ScreenPowerState.SCREEN_ON)
+ systemClock.advanceTime(50)
+ runCurrent()
+ unfoldTransitionProgressProvider.onTransitionStarted()
+ systemClock.advanceTime(200)
+ runCurrent()
+ deviceState.emit(DeviceState.UNFOLDED)
+
+ verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor))
+ val loggedEvent = loggerArgumentCaptor.value
+ val expectedLoggedEvent =
+ DisplaySwitchLatencyEvent(
+ latencyMs = 50,
+ fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED,
+ toFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN
+ )
+ assertThat(loggedEvent).isEqualTo(expectedLoggedEvent)
+ }
+ }
+
+ @Test
+ fun foldWhileStayingAwake_logsLatency() {
+ testScope.runTest {
+ areAnimationEnabled.emit(true)
+ deviceState.emit(DeviceState.UNFOLDED)
+ screenPowerState.emit(ScreenPowerState.SCREEN_ON)
+
+ displaySwitchLatencyTracker.start()
+ deviceState.emit(DeviceState.HALF_FOLDED)
+ systemClock.advanceTime(50)
+ runCurrent()
+ deviceState.emit(DeviceState.FOLDED)
+ screenPowerState.emit(ScreenPowerState.SCREEN_OFF)
+ runCurrent()
+ systemClock.advanceTime(200)
+ screenPowerState.emit(ScreenPowerState.SCREEN_ON)
+ runCurrent()
+
+ verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor))
+ val loggedEvent = loggerArgumentCaptor.value
+ val expectedLoggedEvent =
+ DisplaySwitchLatencyEvent(
+ latencyMs = 200,
+ fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN,
+ toFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED
+ )
+ assertThat(loggedEvent).isEqualTo(expectedLoggedEvent)
+ }
+ }
+
+ @Test
+ fun foldToAod_capturesToStateAsAod() {
+ testScope.runTest {
+ areAnimationEnabled.emit(true)
+ deviceState.emit(DeviceState.UNFOLDED)
+ isAodAvailable.emit(true)
+
+ displaySwitchLatencyTracker.start()
+ deviceState.emit(DeviceState.HALF_FOLDED)
+ systemClock.advanceTime(50)
+ runCurrent()
+ deviceState.emit(DeviceState.FOLDED)
+ lastWakefulnessEvent.emit(
+ WakefulnessModel(
+ internalWakefulnessState = WakefulnessState.ASLEEP,
+ lastSleepReason = WakeSleepReason.FOLD
+ )
+ )
+ screenPowerState.emit(ScreenPowerState.SCREEN_OFF)
+ runCurrent()
+ systemClock.advanceTime(200)
+ screenPowerState.emit(ScreenPowerState.SCREEN_ON)
+ runCurrent()
+
+ verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor))
+ val loggedEvent = loggerArgumentCaptor.value
+ val expectedLoggedEvent =
+ DisplaySwitchLatencyEvent(
+ latencyMs = 200,
+ fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN,
+ toFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED,
+ toState = SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__AOD
+ )
+ assertThat(loggedEvent).isEqualTo(expectedLoggedEvent)
+ }
+ }
+
+ @Test
+ fun fold_notAFoldable_shouldNotLogLatency() {
+ testScope.runTest {
+ areAnimationEnabled.emit(true)
+ deviceState.emit(DeviceState.UNFOLDED)
+ whenever(resources.getIntArray(R.array.config_foldedDeviceStates))
+ .thenReturn(IntArray(0))
+
+ displaySwitchLatencyTracker.start()
+ deviceState.emit(DeviceState.HALF_FOLDED)
+ systemClock.advanceTime(50)
+ runCurrent()
+ deviceState.emit(DeviceState.FOLDED)
+ screenPowerState.emit(ScreenPowerState.SCREEN_OFF)
+ runCurrent()
+ systemClock.advanceTime(200)
+ screenPowerState.emit(ScreenPowerState.SCREEN_ON)
+ runCurrent()
+
+ verify(displaySwitchLatencyLogger, never()).log(any())
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/FoldStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateRepositoryTest.kt
similarity index 82%
rename from packages/SystemUI/tests/src/com/android/systemui/unfold/updates/FoldStateRepositoryTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateRepositoryTest.kt
index 0651323..ab779a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/FoldStateRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateRepositoryTest.kt
@@ -13,13 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.unfold.updates
+package com.android.systemui.unfold
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.unfold.updates.FoldStateRepository.FoldUpdate
+import com.android.systemui.unfold.data.repository.FoldStateRepository.FoldUpdate
+import com.android.systemui.unfold.data.repository.FoldStateRepositoryImpl
+import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED
+import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
+import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN
+import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING
+import com.android.systemui.unfold.updates.FOLD_UPDATE_START_OPENING
+import com.android.systemui.unfold.updates.FoldStateProvider
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt
index 4e61b89..2bc05fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.unfold.progress
+import android.os.Handler
+import android.os.HandlerThread
import android.os.Looper
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
@@ -24,6 +26,7 @@
import com.android.systemui.unfold.TestUnfoldTransitionProvider
import com.android.systemui.utils.os.FakeHandler
import kotlin.test.Test
+import kotlinx.coroutines.test.runTest
import org.junit.runner.RunWith
@RunWith(AndroidTestingRunner::class)
@@ -93,4 +96,13 @@
listener.assertNotStarted()
}
+
+ @Test
+ fun addCallback_fromBackgroundThread_succeeds() = runTest {
+ val bgHandler = Handler(HandlerThread("TestBgThread").apply { start() }.looper)
+ bgHandler.runWithScissors({ progressProvider.addCallback(listener) }, 5000L)
+
+ wrappedProgressProvider.onTransitionStarted()
+ listener.assertStarted()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt b/packages/SystemUI/tests/utils/src/com/android/keyguard/TrustManagerKosmos.kt
similarity index 64%
copy from packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
copy to packages/SystemUI/tests/utils/src/com/android/keyguard/TrustManagerKosmos.kt
index efc7431..187935b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/keyguard/TrustManagerKosmos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -14,12 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.impl.custom.di.bound
+package com.android.keyguard
-import javax.inject.Qualifier
+import android.app.trust.TrustManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
-/** User associated with current custom tile binding. */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class CustomTileUser
+val Kosmos.trustManager by Kosmos.Fixture { mock<TrustManager>() }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryKosmos.kt
similarity index 64%
copy from packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryKosmos.kt
index efc7431..6ef7419 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryKosmos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -14,12 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.impl.custom.di.bound
+package com.android.systemui.biometrics.data.repository
-import javax.inject.Qualifier
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
-/** User associated with current custom tile binding. */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class CustomTileUser
+val Kosmos.facePropertyRepository by Fixture { FakeFacePropertyRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt
index 3fdeb30..3b5ff38 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.biometrics.data.repository
+import android.util.Size
import com.android.systemui.biometrics.shared.model.DisplayRotation
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -29,6 +30,9 @@
private val _currentRotation = MutableStateFlow<DisplayRotation>(DisplayRotation.ROTATION_0)
override val currentRotation: StateFlow<DisplayRotation> = _currentRotation.asStateFlow()
+ private val _currentDisplaySize = MutableStateFlow<Size>(Size(0, 0))
+ override val currentDisplaySize: StateFlow<Size> = _currentDisplaySize.asStateFlow()
+
override val isReverseDefaultRotation = false
fun setIsInRearDisplayMode(isInRearDisplayMode: Boolean) {
@@ -38,4 +42,8 @@
fun setCurrentRotation(currentRotation: DisplayRotation) {
_currentRotation.value = currentRotation
}
+
+ fun setCurrentDisplaySize(size: Size) {
+ _currentDisplaySize.value = size
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
index 51ce9f0..77f501f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.biometrics.data.repository
+import android.graphics.Point
import com.android.systemui.biometrics.shared.model.LockoutMode
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -28,6 +29,10 @@
private val lockoutModesForUser = mutableMapOf<Int, LockoutMode>()
+ private val faceSensorLocation = MutableStateFlow<Point?>(null)
+ override val sensorLocation: StateFlow<Point?>
+ get() = faceSensorLocation
+
fun setLockoutMode(userId: Int, mode: LockoutMode) {
lockoutModesForUser[userId] = mode
}
@@ -38,4 +43,8 @@
fun setSensorInfo(value: FaceSensorInfo?) {
faceSensorInfo.value = value
}
+
+ fun setSensorLocation(value: Point?) {
+ faceSensorLocation.value = value
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakePackageChangeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakePackageChangeRepository.kt
new file mode 100644
index 0000000..60f0448
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakePackageChangeRepository.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.common.data.repository
+
+import android.os.UserHandle
+import com.android.systemui.common.data.shared.model.PackageChangeModel
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.filter
+
+class FakePackageChangeRepository : PackageChangeRepository {
+
+ private var _packageChanged = MutableSharedFlow<PackageChangeModel>()
+
+ override fun packageChanged(user: UserHandle) =
+ _packageChanged.filter {
+ user == UserHandle.ALL || user == UserHandle.getUserHandleForUid(it.packageUid)
+ }
+
+ suspend fun notifyChange(model: PackageChangeModel) {
+ _packageChanged.emit(model)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/PackageChangeRepositoryKosmos.kt
similarity index 66%
copy from packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/PackageChangeRepositoryKosmos.kt
index efc7431..adc05e0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/PackageChangeRepositoryKosmos.kt
@@ -14,12 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.impl.custom.di.bound
+package com.android.systemui.common.data.repository
-import javax.inject.Qualifier
+import com.android.systemui.kosmos.Kosmos
-/** User associated with current custom tile binding. */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class CustomTileUser
+var Kosmos.packageChangeRepository: PackageChangeRepository by
+ Kosmos.Fixture { fakePackageChangeRepository }
+val Kosmos.fakePackageChangeRepository by Kosmos.Fixture { FakePackageChangeRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
index 3ab1b6c..3ea3ccf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
@@ -16,8 +16,24 @@
package com.android.systemui.communal.data.repository
+import com.android.systemui.communal.data.model.CommunalMediaModel
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-class FakeCommunalMediaRepository(
- override val mediaPlaying: MutableStateFlow<Boolean> = MutableStateFlow(false)
-) : CommunalMediaRepository
+class FakeCommunalMediaRepository : CommunalMediaRepository {
+ private val _mediaModel = MutableStateFlow(CommunalMediaModel.INACTIVE)
+
+ override val mediaModel: Flow<CommunalMediaModel> = _mediaModel
+
+ fun mediaActive(timestamp: Long = 0L) {
+ _mediaModel.value =
+ CommunalMediaModel(
+ hasAnyMediaOrRecommendation = true,
+ createdTimestampMillis = timestamp,
+ )
+ }
+
+ fun mediaInactive() {
+ _mediaModel.value = CommunalMediaModel.INACTIVE
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
index c85c27e..e82cae4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
@@ -9,6 +9,7 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
@@ -52,4 +53,12 @@
fun setIsCommunalHubShowing(isCommunalHubShowing: Boolean) {
_isCommunalHubShowing.value = isCommunalHubShowing
}
+
+ private val _isCtaTileInViewModeVisible: MutableStateFlow<Boolean> = MutableStateFlow(true)
+ override val isCtaTileInViewModeVisible: Flow<Boolean> =
+ _isCtaTileInViewModeVisible.asStateFlow()
+
+ override fun setCtaTileInViewModeVisibility(isVisible: Boolean) {
+ _isCtaTileInViewModeVisible.value = isVisible
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfigKosmos.kt
similarity index 64%
copy from packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfigKosmos.kt
index efc7431..21cff0d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfigKosmos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -14,12 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.impl.custom.di.bound
+package com.android.systemui.deviceentry.data.repository
-import javax.inject.Qualifier
+import com.android.systemui.kosmos.Kosmos
-/** User associated with current custom tile binding. */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class CustomTileUser
+var Kosmos.faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig by
+ Kosmos.Fixture { FakeFaceWakeUpTriggersConfig() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeFaceWakeUpTriggersConfig.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeFaceWakeUpTriggersConfig.kt
new file mode 100644
index 0000000..af617b7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeFaceWakeUpTriggersConfig.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.deviceentry.data.repository
+
+import com.android.systemui.power.shared.model.WakeSleepReason
+
+class FakeFaceWakeUpTriggersConfig : FaceWakeUpTriggersConfig {
+ private val triggerFaceAuthOnWakeUpFrom: MutableSet<Int> = mutableSetOf()
+ private val wakeSleepReasonsToTriggerFaceAuth: MutableSet<WakeSleepReason> = mutableSetOf()
+ override fun shouldTriggerFaceAuthOnWakeUpFrom(pmWakeReason: Int): Boolean {
+ return triggerFaceAuthOnWakeUpFrom.contains(pmWakeReason)
+ }
+
+ override fun shouldTriggerFaceAuthOnWakeUpFrom(wakeReason: WakeSleepReason): Boolean {
+ return wakeSleepReasonsToTriggerFaceAuth.contains(wakeReason)
+ }
+
+ fun setTriggerFaceAuthOnWakeUpFrom(pmWakeReasons: Set<Int>) {
+ triggerFaceAuthOnWakeUpFrom.clear()
+ triggerFaceAuthOnWakeUpFrom.addAll(pmWakeReasons)
+
+ wakeSleepReasonsToTriggerFaceAuth.clear()
+ wakeSleepReasonsToTriggerFaceAuth.addAll(
+ pmWakeReasons.map { WakeSleepReason.fromPowerManagerWakeReason(it) }
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
index d2dff78..0b1fb40 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
@@ -18,13 +18,45 @@
package com.android.systemui.deviceentry.domain.interactor
+import android.content.applicationContext
+import com.android.keyguard.keyguardUpdateMonitor
+import com.android.keyguard.trustManager
+import com.android.systemui.biometrics.data.repository.facePropertyRepository
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
+import com.android.systemui.deviceentry.data.repository.faceWakeUpTriggersConfig
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.data.repository.deviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.log.FaceAuthenticationLogger
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.user.data.repository.userRepository
+import com.android.systemui.util.mockito.mock
import kotlinx.coroutines.ExperimentalCoroutinesApi
+val Kosmos.faceAuthLogger by Kosmos.Fixture { mock<FaceAuthenticationLogger>() }
val Kosmos.deviceEntryFaceAuthInteractor by
Kosmos.Fixture {
- DeviceEntryFaceAuthInteractor(
+ SystemUIDeviceEntryFaceAuthInteractor(
+ context = applicationContext,
+ applicationScope = applicationCoroutineScope,
+ mainDispatcher = testDispatcher,
repository = deviceEntryFaceAuthRepository,
+ primaryBouncerInteractor = { primaryBouncerInteractor },
+ alternateBouncerInteractor = alternateBouncerInteractor,
+ keyguardTransitionInteractor = keyguardTransitionInteractor,
+ faceAuthenticationLogger = faceAuthLogger,
+ keyguardUpdateMonitor = keyguardUpdateMonitor,
+ deviceEntryFingerprintAuthRepository = deviceEntryFingerprintAuthRepository,
+ userRepository = userRepository,
+ facePropertyRepository = facePropertyRepository,
+ faceWakeUpTriggersConfig = faceWakeUpTriggersConfig,
+ powerInteractor = powerInteractor,
+ biometricSettingsRepository = biometricSettingsRepository,
+ trustManager = trustManager,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryKosmos.kt
index 3d72967..599b5142 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.data.repository
+import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
import com.android.systemui.kosmos.Kosmos
var Kosmos.deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository by
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
index a1b6587..e96aeada 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
@@ -16,10 +16,11 @@
package com.android.systemui.keyguard.data.repository
-import com.android.keyguard.FaceAuthUiEvent
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
+import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
+import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus
import dagger.Binds
import dagger.Module
import javax.inject.Inject
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 4200f05..975db3b 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
@@ -80,7 +80,7 @@
override val lastDozeTapToWakePosition = _lastDozeTapToWakePosition.asStateFlow()
private val _isAodAvailable = MutableStateFlow(false)
- override val isAodAvailable: Flow<Boolean> = _isAodAvailable
+ override val isAodAvailable: StateFlow<Boolean> = _isAodAvailable
private val _isDreaming = MutableStateFlow(false)
override val isDreaming: Flow<Boolean> = _isDreaming
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
index 3cabf0c..5f5d428 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
@@ -27,6 +27,7 @@
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.bouncer.ui.BouncerView
import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -73,7 +74,7 @@
trustRepository,
testScope.backgroundScope,
mock(SelectedUserInteractor::class.java),
- mock(KeyguardFaceAuthInteractor::class.java),
+ mock(DeviceEntryFaceAuthInteractor::class.java),
)
val alternateBouncerInteractor =
AlternateBouncerInteractor(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
index d705248..14f28fe 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
@@ -33,6 +33,7 @@
val Kosmos.customTileRepository: FakeCustomTileRepository by
Kosmos.Fixture {
FakeCustomTileRepository(
+ tileSpec,
customTileStatePersister,
packageManagerAdapterFacade,
testScope.testScheduler,
@@ -46,4 +47,4 @@
Kosmos.Fixture { FakeCustomTilePackageUpdatesRepository() }
val Kosmos.packageManagerAdapterFacade: FakePackageManagerAdapterFacade by
- Kosmos.Fixture { FakePackageManagerAdapterFacade(tileSpec) }
+ Kosmos.Fixture { FakePackageManagerAdapterFacade(tileSpec.componentName) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt
index ba803d8..c110da0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt
@@ -19,11 +19,13 @@
import android.os.UserHandle
import android.service.quicksettings.Tile
import com.android.systemui.qs.external.FakeCustomTileStatePersister
+import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.flow.Flow
class FakeCustomTileRepository(
+ tileSpec: TileSpec.CustomTileSpec,
customTileStatePersister: FakeCustomTileStatePersister,
private val packageManagerAdapterFacade: FakePackageManagerAdapterFacade,
testBackgroundContext: CoroutineContext,
@@ -31,12 +33,16 @@
private val realDelegate: CustomTileRepository =
CustomTileRepositoryImpl(
- packageManagerAdapterFacade.tileSpec,
+ tileSpec,
customTileStatePersister,
packageManagerAdapterFacade.packageManagerAdapter,
testBackgroundContext,
)
+ init {
+ require(tileSpec.componentName == packageManagerAdapterFacade.componentName)
+ }
+
override suspend fun restoreForTheUserIfNeeded(user: UserHandle, isPersistable: Boolean) =
realDelegate.restoreForTheUserIfNeeded(user, isPersistable)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt
index c9a7655..634d121 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt
@@ -16,17 +16,27 @@
package com.android.systemui.qs.tiles.impl.custom.data.repository
+import android.content.ComponentName
import android.content.pm.ServiceInfo
import android.os.Bundle
import com.android.systemui.qs.external.PackageManagerAdapter
-import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+/**
+ * Facade for [PackageManagerAdapter] to provide a fake-like behaviour. You can create this class
+ * and then get [packageManagerAdapter] to use in your test code.
+ *
+ * This allows to mock [PackageManagerAdapter] to provide a custom behaviour for
+ * [CustomTileRepository.isTileActive], [CustomTileRepository.isTileToggleable],
+ * [com.android.systemui.qs.external.TileServiceManager.isToggleableTile] or
+ * [com.android.systemui.qs.external.TileServiceManager.isActiveTile] when the real objects are
+ * used.
+ */
class FakePackageManagerAdapterFacade(
- val tileSpec: TileSpec.CustomTileSpec,
+ val componentName: ComponentName,
val packageManagerAdapter: PackageManagerAdapter = mock {},
) {
@@ -34,22 +44,21 @@
private var isActive: Boolean = false
init {
- whenever(packageManagerAdapter.getServiceInfo(eq(tileSpec.componentName), any()))
- .thenAnswer {
- ServiceInfo().apply {
- metaData =
- Bundle().apply {
- putBoolean(
- android.service.quicksettings.TileService.META_DATA_TOGGLEABLE_TILE,
- isToggleable
- )
- putBoolean(
- android.service.quicksettings.TileService.META_DATA_ACTIVE_TILE,
- isActive
- )
- }
- }
+ whenever(packageManagerAdapter.getServiceInfo(eq(componentName), any())).thenAnswer {
+ ServiceInfo().apply {
+ metaData =
+ Bundle().apply {
+ putBoolean(
+ android.service.quicksettings.TileService.META_DATA_TOGGLEABLE_TILE,
+ isToggleable
+ )
+ putBoolean(
+ android.service.quicksettings.TileService.META_DATA_ACTIVE_TILE,
+ isActive
+ )
+ }
}
+ }
}
fun setIsActive(isActive: Boolean) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index 9f71161..09ab655 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -52,20 +52,20 @@
import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
+import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.doze.DozeLogger
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.kosmos.Kosmos
@@ -266,14 +266,14 @@
fun bouncerInteractor(
authenticationInteractor: AuthenticationInteractor,
- keyguardFaceAuthInteractor: KeyguardFaceAuthInteractor = mock(),
+ deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor = mock(),
): BouncerInteractor {
return BouncerInteractor(
applicationScope = applicationScope(),
applicationContext = context,
repository = bouncerRepository,
authenticationInteractor = authenticationInteractor,
- keyguardFaceAuthInteractor = keyguardFaceAuthInteractor,
+ deviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor,
falsingInteractor = falsingInteractor(),
powerInteractor = powerInteractor(),
simBouncerInteractor = simBouncerInteractor,
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
index f7fb014..1b7e71a 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
@@ -27,8 +27,6 @@
import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder
import com.android.systemui.unfold.updates.DeviceFoldStateProvider
import com.android.systemui.unfold.updates.FoldStateProvider
-import com.android.systemui.unfold.updates.FoldStateRepository
-import com.android.systemui.unfold.updates.FoldStateRepositoryImpl
import com.android.systemui.unfold.updates.RotationChangeProvider
import com.android.systemui.unfold.updates.hinge.EmptyHingeAngleProvider
import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
@@ -68,10 +66,6 @@
fun unfoldKeyguardVisibilityManager(
impl: UnfoldKeyguardVisibilityManagerImpl
): UnfoldKeyguardVisibilityManager = impl
-
- @Provides
- @Singleton
- fun foldStateRepository(impl: FoldStateRepositoryImpl): FoldStateRepository = impl
}
@Module
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProvider.kt
index 9bdf3d5..fdce147 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProvider.kt
@@ -24,12 +24,16 @@
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import java.util.Collections.synchronizedMap
/**
* [UnfoldTransitionProgressProvider] that forwards all progress to the main thread handler.
*
* This is needed when progress are calculated in the background, but some listeners need the
* callbacks in the main thread.
+ *
+ * Note that this class assumes that the root provider has thread safe callback registration, as
+ * they might be called from any thread.
*/
class MainThreadUnfoldTransitionProgressProvider
@AssistedInject
@@ -38,27 +42,20 @@
@Assisted private val rootProvider: UnfoldTransitionProgressProvider
) : UnfoldTransitionProgressProvider {
- private val listenerMap = mutableMapOf<TransitionProgressListener, TransitionProgressListener>()
+ private val listenerMap: MutableMap<TransitionProgressListener, TransitionProgressListener> =
+ synchronizedMap(mutableMapOf())
override fun addCallback(listener: TransitionProgressListener) {
- assertMainThread()
val proxy = TransitionProgressListerProxy(listener)
rootProvider.addCallback(proxy)
listenerMap[listener] = proxy
}
override fun removeCallback(listener: TransitionProgressListener) {
- assertMainThread()
val proxy = listenerMap.remove(listener) ?: return
rootProvider.removeCallback(proxy)
}
- private fun assertMainThread() {
- check(mainHandler.looper.isCurrentThread) {
- "Should be called from the main thread, but this is ${Thread.currentThread()}"
- }
- }
-
override fun destroy() {
rootProvider.destroy()
}
diff --git a/packages/overlays/NoCutoutOverlay/res/values/config.xml b/packages/overlays/NoCutoutOverlay/res/values/config.xml
index ed0340b..b44a153a 100644
--- a/packages/overlays/NoCutoutOverlay/res/values/config.xml
+++ b/packages/overlays/NoCutoutOverlay/res/values/config.xml
@@ -20,10 +20,17 @@
black in software (to avoid aliasing or emulate a cutout that is not physically existent).
-->
<bool name="config_fillMainBuiltInDisplayCutout">false</bool>
+ <!-- Whether the display cutout region of the secondary built-in display should be forced to
+ black in software (to avoid aliasing or emulate a cutout that is not physically existent).
+ -->
+ <bool name="config_fillSecondaryBuiltInDisplayCutout">false</bool>
<!-- If true, and there is a cutout on the main built in display, the cutout will be masked
by shrinking the display such that it does not overlap the cutout area. -->
<bool name="config_maskMainBuiltInDisplayCutout">true</bool>
+ <!-- If true, and there is a cutout on the secondary built in display, the cutout will be masked
+ by shrinking the display such that it does not overlap the cutout area. -->
+ <bool name="config_maskSecondaryBuiltInDisplayCutout">true</bool>
<!-- Height of the status bar -->
<dimen name="status_bar_height_portrait">28dp</dimen>
diff --git a/proto/src/criticalevents/critical_event_log.proto b/proto/src/criticalevents/critical_event_log.proto
index 9cda267..cffcd09 100644
--- a/proto/src/criticalevents/critical_event_log.proto
+++ b/proto/src/criticalevents/critical_event_log.proto
@@ -60,8 +60,11 @@
JavaCrash java_crash = 5;
NativeCrash native_crash = 6;
SystemServerStarted system_server_started = 7;
+ InstallPackages install_packages = 8;
}
+ message InstallPackages {}
+
message SystemServerStarted {}
message Watchdog {
diff --git a/services/Android.bp b/services/Android.bp
index 5cb8ec6..0b484f4 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -148,6 +148,9 @@
java_library {
name: "Slogf",
srcs: ["core/java/com/android/server/utils/Slogf.java"],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// merge all required services into one jar
@@ -220,6 +223,9 @@
required: [
"libukey2_jni_shared",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
// Uncomment to enable output of certain warnings (deprecated, unchecked)
//javacflags: ["-Xlint"],
diff --git a/services/accessibility/Android.bp b/services/accessibility/Android.bp
index e2488a5..a354671 100644
--- a/services/accessibility/Android.bp
+++ b/services/accessibility/Android.bp
@@ -21,6 +21,7 @@
],
lint: {
error_checks: ["MissingPermissionAnnotation"],
+ baseline_filename: "lint-baseline.xml",
},
srcs: [
":services.accessibility-sources",
@@ -49,6 +50,9 @@
libs: [
"androidx.annotation_annotation",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
aconfig_declarations {
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index 6b0fdb5..cf414d1 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -240,10 +240,20 @@
mEventInternal = Optional.of(new PresentationStatsEventInternal());
}
+ /**
+ * Set request_id
+ */
public void maybeSetRequestId(int requestId) {
mEventInternal.ifPresent(event -> event.mRequestId = requestId);
}
+ /**
+ * Set is_credential_request
+ */
+ public void maybeSetIsCredentialRequest(boolean isCredentialRequest) {
+ mEventInternal.ifPresent(event -> event.mIsCredentialRequest = isCredentialRequest);
+ }
+
public void maybeSetNoPresentationEventReason(@NotShownReason int reason) {
mEventInternal.ifPresent(event -> {
if (event.mCountShown == 0) {
@@ -567,7 +577,8 @@
+ " mSelectedDatasetPickedReason=" + event.mSelectedDatasetPickedReason
+ " mDetectionPreference=" + event.mDetectionPreference
+ " mFieldClassificationRequestId=" + event.mFieldClassificationRequestId
- + " mAppPackageUid=" + mCallingAppUid);
+ + " mAppPackageUid=" + mCallingAppUid
+ + " mIsCredentialRequest=" + event.mIsCredentialRequest);
}
// TODO(b/234185326): Distinguish empty responses from other no presentation reasons.
@@ -606,7 +617,8 @@
event.mSelectedDatasetPickedReason,
event.mDetectionPreference,
event.mFieldClassificationRequestId,
- mCallingAppUid);
+ mCallingAppUid,
+ event.mIsCredentialRequest);
mEventInternal = Optional.empty();
}
@@ -640,6 +652,7 @@
@DatasetPickedReason int mSelectedDatasetPickedReason = PICK_REASON_UNKNOWN;
@DetectionPreference int mDetectionPreference = DETECTION_PREFER_UNKNOWN;
int mFieldClassificationRequestId = -1;
+ boolean mIsCredentialRequest = false;
PresentationStatsEventInternal() {}
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index a49f9db..007be05 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1290,7 +1290,9 @@
Slog.v(TAG, "Requesting structure for request #" + ordinal + " ,requestId=" + requestId
+ ", flags=" + flags);
}
+ boolean isCredmanRequested = (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0;
mPresentationStatsEventLogger.maybeSetRequestId(requestId);
+ mPresentationStatsEventLogger.maybeSetIsCredentialRequest(isCredmanRequested);
mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId(
mFieldClassificationIdSnapshot);
mFillRequestEventLogger.maybeSetRequestId(requestId);
@@ -4360,8 +4362,10 @@
}
if (viewState.getResponse() != null) {
+ boolean isCredmanRequested = (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0;
FillResponse response = viewState.getResponse();
mPresentationStatsEventLogger.maybeSetRequestId(response.getRequestId());
+ mPresentationStatsEventLogger.maybeSetIsCredentialRequest(isCredmanRequested);
mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId(
mFieldClassificationIdSnapshot);
mPresentationStatsEventLogger.maybeSetAvailableCount(
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
index a6ed846..4b3772a 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
@@ -284,7 +284,7 @@
final AssociationInfo association = new AssociationInfo(id, userId, packageName,
/* tag */ null, macAddress, displayName, deviceProfile, associatedDevice,
selfManaged, /* notifyOnDeviceNearby */ false, /* revoked */ false,
- timestamp, Long.MAX_VALUE, /* systemDataSyncFlags */ 0);
+ /* pending */ false, timestamp, Long.MAX_VALUE, /* systemDataSyncFlags */ 0);
// Add role holder for association (if specified) and add new association to store.
maybeGrantRoleAndStoreAssociation(association, callback, resultReceiver);
diff --git a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
new file mode 100644
index 0000000..e4cc1f8
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
@@ -0,0 +1,277 @@
+/*
+ * 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.server.companion;
+
+import static android.os.UserHandle.getCallingUserId;
+
+import static com.android.server.companion.CompanionDeviceManagerService.PerUserAssociationSet;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.UserIdInt;
+import android.companion.AssociationInfo;
+import android.companion.Flags;
+import android.companion.datatransfer.SystemDataTransferRequest;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManagerInternal;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.CollectionUtils;
+import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
+
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Predicate;
+
+@SuppressLint("LongLogTag")
+class BackupRestoreProcessor {
+ static final String TAG = "CDM_BackupRestoreProcessor";
+ private static final int BACKUP_AND_RESTORE_VERSION = 0;
+
+ @NonNull
+ private final CompanionDeviceManagerService mService;
+ @NonNull
+ private final PackageManagerInternal mPackageManager;
+ @NonNull
+ private final AssociationStoreImpl mAssociationStore;
+ @NonNull
+ private final PersistentDataStore mPersistentStore;
+ @NonNull
+ private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
+ @NonNull
+ private final AssociationRequestsProcessor mAssociationRequestsProcessor;
+
+ /**
+ * A structure that consists of a set of restored associations that are pending corresponding
+ * companion app to be installed.
+ */
+ @GuardedBy("mAssociationsPendingAppInstall")
+ private final PerUserAssociationSet mAssociationsPendingAppInstall =
+ new PerUserAssociationSet();
+
+ BackupRestoreProcessor(@NonNull CompanionDeviceManagerService service,
+ @NonNull AssociationStoreImpl associationStore,
+ @NonNull PersistentDataStore persistentStore,
+ @NonNull SystemDataTransferRequestStore systemDataTransferRequestStore,
+ @NonNull AssociationRequestsProcessor associationRequestsProcessor) {
+ mService = service;
+ mPackageManager = service.mPackageManagerInternal;
+ mAssociationStore = associationStore;
+ mPersistentStore = persistentStore;
+ mSystemDataTransferRequestStore = systemDataTransferRequestStore;
+ mAssociationRequestsProcessor = associationRequestsProcessor;
+ }
+
+ /**
+ * Generate CDM state payload to be backed up.
+ * Backup payload is formatted as following:
+ * | (4) payload version | (4) AssociationInfo length | AssociationInfo XML
+ * | (4) SystemDataTransferRequest length | SystemDataTransferRequest XML (without userId)|
+ */
+ byte[] getBackupPayload(int userId) {
+ // Persist state first to generate an up-to-date XML file
+ mService.persistStateForUser(userId);
+ byte[] associationsPayload = mPersistentStore.getBackupPayload(userId);
+ int associationsPayloadLength = associationsPayload.length;
+
+ // System data transfer requests are persisted up-to-date already
+ byte[] requestsPayload = mSystemDataTransferRequestStore.getBackupPayload(userId);
+ int requestsPayloadLength = requestsPayload.length;
+
+ int payloadSize = /* 3 integers */ 12
+ + associationsPayloadLength
+ + requestsPayloadLength;
+
+ return ByteBuffer.allocate(payloadSize)
+ .putInt(BACKUP_AND_RESTORE_VERSION)
+ .putInt(associationsPayloadLength)
+ .put(associationsPayload)
+ .putInt(requestsPayloadLength)
+ .put(requestsPayload)
+ .array();
+ }
+
+ /**
+ * Create new associations and system data transfer request consents using backed up payload.
+ */
+ void applyRestoredPayload(byte[] payload, int userId) {
+ ByteBuffer buffer = ByteBuffer.wrap(payload);
+
+ // Make sure that payload version matches current version to ensure proper deserialization
+ int version = buffer.getInt();
+ if (version != BACKUP_AND_RESTORE_VERSION) {
+ Slog.e(TAG, "Unsupported backup payload version");
+ return;
+ }
+
+ // Read the bytes containing backed-up associations
+ byte[] associationsPayload = new byte[buffer.getInt()];
+ buffer.get(associationsPayload);
+ final Set<AssociationInfo> restoredAssociations = new HashSet<>();
+ mPersistentStore.readStateFromPayload(associationsPayload, userId,
+ restoredAssociations, new HashMap<>());
+
+ // Read the bytes containing backed-up system data transfer requests user consent
+ byte[] requestsPayload = new byte[buffer.getInt()];
+ buffer.get(requestsPayload);
+ List<SystemDataTransferRequest> restoredRequestsForUser =
+ mSystemDataTransferRequestStore.readRequestsFromPayload(requestsPayload, userId);
+
+ // Get a list of installed packages ahead of time.
+ List<ApplicationInfo> installedApps = mPackageManager.getInstalledApplications(
+ 0, userId, getCallingUserId());
+
+ // Restored device may have a different user ID than the backed-up user's user-ID. Since
+ // association ID is dependent on the user ID, restored associations must account for
+ // this potential difference on their association IDs.
+ for (AssociationInfo restored : restoredAssociations) {
+ // Don't restore a revoked association. Since they weren't added to the device being
+ // restored in the first place, there is no need to worry about revoking a role that
+ // was never granted either.
+ if (restored.isRevoked()) {
+ continue;
+ }
+
+ // Filter restored requests for those that belong to the restored association.
+ List<SystemDataTransferRequest> restoredRequests = CollectionUtils.filter(
+ restoredRequestsForUser, it -> it.getAssociationId() == restored.getId());
+
+ // Handle collision: If a local association belonging to the same package already exists
+ // and their tags match, then keep the local one in favor of creating a new association.
+ if (handleCollision(userId, restored, restoredRequests)) {
+ continue;
+ }
+
+ // Create a new association reassigned to this user and a valid association ID
+ final String packageName = restored.getPackageName();
+ final int newId = mService.getNewAssociationIdForPackage(userId, packageName);
+ AssociationInfo newAssociation =
+ new AssociationInfo.Builder(newId, userId, packageName, restored)
+ .build();
+
+ // Check if the companion app for this association is already installed, then do one
+ // of the following:
+ // (1) If the app is already installed, then go ahead and add this association and grant
+ // the role attached to this association to the app.
+ // (2) If the app isn't yet installed, then add this association to the list of pending
+ // associations to be added when the package is installed in the future.
+ boolean isPackageInstalled = installedApps.stream()
+ .anyMatch(app -> packageName.equals(app.packageName));
+ if (isPackageInstalled) {
+ mAssociationRequestsProcessor.maybeGrantRoleAndStoreAssociation(newAssociation,
+ null, null);
+ } else {
+ addToPendingAppInstall(newAssociation);
+ }
+
+ // Re-map restored system data transfer requests to newly created associations
+ for (SystemDataTransferRequest restoredRequest : restoredRequests) {
+ SystemDataTransferRequest newRequest = restoredRequest.copyWithNewId(newId);
+ newRequest.setUserId(userId);
+ mSystemDataTransferRequestStore.writeRequest(userId, newRequest);
+ }
+ }
+
+ // Persist restored state.
+ mService.persistStateForUser(userId);
+ }
+
+ void addToPendingAppInstall(@NonNull AssociationInfo association) {
+ association = (new AssociationInfo.Builder(association))
+ .setPending(true)
+ .build();
+
+ synchronized (mAssociationsPendingAppInstall) {
+ mAssociationsPendingAppInstall.forUser(association.getUserId()).add(association);
+ }
+ }
+
+ void removeFromPendingAppInstall(@NonNull AssociationInfo association) {
+ synchronized (mAssociationsPendingAppInstall) {
+ mAssociationsPendingAppInstall.forUser(association.getUserId()).remove(association);
+ }
+ }
+
+ @NonNull
+ Set<AssociationInfo> getAssociationsPendingAppInstallForUser(@UserIdInt int userId) {
+ synchronized (mAssociationsPendingAppInstall) {
+ // Return a copy.
+ return new ArraySet<>(mAssociationsPendingAppInstall.forUser(userId));
+ }
+ }
+
+ /**
+ * Detects and handles collision between restored association and local association. Returns
+ * true if there has been a collision and false otherwise.
+ */
+ private boolean handleCollision(@UserIdInt int userId,
+ AssociationInfo restored,
+ List<SystemDataTransferRequest> restoredRequests) {
+ List<AssociationInfo> localAssociations = mAssociationStore.getAssociationsForPackage(
+ restored.getUserId(), restored.getPackageName());
+ Predicate<AssociationInfo> isSameDevice = associationInfo -> {
+ boolean matchesMacAddress = Objects.equals(
+ associationInfo.getDeviceMacAddress(),
+ restored.getDeviceMacAddress());
+ boolean matchesTag = !Flags.associationTag()
+ || Objects.equals(associationInfo.getTag(), restored.getTag());
+ return matchesMacAddress && matchesTag;
+ };
+ AssociationInfo local = CollectionUtils.find(localAssociations, isSameDevice);
+
+ // No collision detected
+ if (local == null) {
+ return false;
+ }
+
+ Log.d(TAG, "Conflict detected with association id=" + local.getId()
+ + " while restoring CDM backup. Keeping local association.");
+
+ List<SystemDataTransferRequest> localRequests = mSystemDataTransferRequestStore
+ .readRequestsByAssociationId(userId, local.getId());
+
+ // If local association doesn't have any existing system data transfer request of same type
+ // attached, then restore corresponding request onto the local association. Otherwise, keep
+ // the locally stored request.
+ for (SystemDataTransferRequest restoredRequest : restoredRequests) {
+ boolean requestTypeExists = CollectionUtils.any(localRequests, request ->
+ request.getDataType() == restoredRequest.getDataType());
+
+ // This type of request consent already exists for the association.
+ if (requestTypeExists) {
+ continue;
+ }
+
+ Log.d(TAG, "Restoring " + restoredRequest.getClass().getSimpleName()
+ + " to an existing association id=" + local.getId() + ".");
+
+ SystemDataTransferRequest newRequest =
+ restoredRequest.copyWithNewId(local.getId());
+ newRequest.setUserId(userId);
+ mSystemDataTransferRequestStore.writeRequest(userId, newRequest);
+ }
+
+ return true;
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 487b66c..056ec89 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -164,6 +164,7 @@
private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
private AssociationRequestsProcessor mAssociationRequestsProcessor;
private SystemDataTransferProcessor mSystemDataTransferProcessor;
+ private BackupRestoreProcessor mBackupRestoreProcessor;
private CompanionDevicePresenceMonitor mDevicePresenceMonitor;
private CompanionApplicationController mCompanionAppController;
private CompanionTransportManager mTransportManager;
@@ -256,6 +257,9 @@
mSystemDataTransferProcessor = new SystemDataTransferProcessor(this,
mPackageManagerInternal, mAssociationStore,
mSystemDataTransferRequestStore, mTransportManager);
+ mBackupRestoreProcessor = new BackupRestoreProcessor(
+ /* cdmService */ this, mAssociationStore, mPersistentStore,
+ mSystemDataTransferRequestStore, mAssociationRequestsProcessor);
// TODO(b/279663946): move context sync to a dedicated system service
mCrossDeviceSyncController = new CrossDeviceSyncController(getContext(), mTransportManager);
@@ -283,7 +287,9 @@
final Set<Integer> usersToPersistStateFor = new ArraySet<>();
for (AssociationInfo association : allAssociations) {
- if (!association.isRevoked()) {
+ if (association.isPending()) {
+ mBackupRestoreProcessor.addToPendingAppInstall(association);
+ } else if (!association.isRevoked()) {
activeAssociations.add(association);
} else if (maybeRemoveRoleHolderForAssociation(association)) {
// Nothing more to do here, but we'll need to persist all the associations to the
@@ -501,7 +507,7 @@
updateAtm(userId, updatedAssociations);
}
- private void persistStateForUser(@UserIdInt int userId) {
+ void persistStateForUser(@UserIdInt int userId) {
// We want to store both active associations and the revoked (removed) association that we
// are keeping around for the final clean-up (delayed role holder removal).
final List<AssociationInfo> allAssociations;
@@ -510,6 +516,9 @@
mAssociationStore.getAssociationsForUser(userId));
// ... and add the revoked (removed) association, that are yet to be permanently removed.
allAssociations.addAll(getPendingRoleHolderRemovalAssociationsForUser(userId));
+ // ... and add the restored associations that are pending missing package installation.
+ allAssociations.addAll(mBackupRestoreProcessor
+ .getAssociationsPendingAppInstallForUser(userId));
final Map<String, Set<Integer>> usedIdsForUser = getPreviouslyUsedIdsForUser(userId);
@@ -577,6 +586,23 @@
mCompanionAppController.onPackagesChanged(userId);
}
+ private void onPackageAddedInternal(@UserIdInt int userId, @NonNull String packageName) {
+ if (DEBUG) Log.i(TAG, "onPackageAddedInternal() u" + userId + "/" + packageName);
+
+ Set<AssociationInfo> associationsPendingAppInstall = mBackupRestoreProcessor
+ .getAssociationsPendingAppInstallForUser(userId);
+ for (AssociationInfo association : associationsPendingAppInstall) {
+ if (!packageName.equals(association.getPackageName())) continue;
+
+ AssociationInfo newAssociation = new AssociationInfo.Builder(association)
+ .setPending(false)
+ .build();
+ mAssociationRequestsProcessor.maybeGrantRoleAndStoreAssociation(newAssociation,
+ null, null);
+ mBackupRestoreProcessor.removeFromPendingAppInstall(association);
+ }
+ }
+
// Revoke associations if the selfManaged companion device does not connect for 3 months.
void removeInactiveSelfManagedAssociations() {
final long currentTime = System.currentTimeMillis();
@@ -1052,13 +1078,14 @@
@Override
public byte[] getBackupPayload(int userId) {
- // TODO(b/286124853): back up CDM data
- return new byte[0];
+ Log.i(TAG, "getBackupPayload() userId=" + userId);
+ return mBackupRestoreProcessor.getBackupPayload(userId);
}
@Override
public void applyRestoredPayload(byte[] payload, int userId) {
- // TODO(b/286124853): restore CDM data
+ Log.i(TAG, "applyRestoredPayload() userId=" + userId);
+ mBackupRestoreProcessor.applyRestoredPayload(payload, userId);
}
@Override
@@ -1067,7 +1094,8 @@
@NonNull String[] args) {
return new CompanionDeviceShellCommand(CompanionDeviceManagerService.this,
mAssociationStore, mDevicePresenceMonitor, mTransportManager,
- mSystemDataTransferProcessor, mAssociationRequestsProcessor)
+ mSystemDataTransferProcessor, mAssociationRequestsProcessor,
+ mBackupRestoreProcessor)
.exec(this, in.getFileDescriptor(), out.getFileDescriptor(),
err.getFileDescriptor(), args);
}
@@ -1141,6 +1169,15 @@
usedIds.put(it.getId(), true);
}
+ // Some IDs may be reserved by associations that aren't stored yet due to missing
+ // package after a backup restoration. We don't want the ID to have been taken by
+ // another association by the time when it is activated from the package installation.
+ final Set<AssociationInfo> pendingAssociations = mBackupRestoreProcessor
+ .getAssociationsPendingAppInstallForUser(userId);
+ for (AssociationInfo it: pendingAssociations) {
+ usedIds.put(it.getId(), true);
+ }
+
// Second: collect all IDs that have been previously used for this package (and user).
final Set<Integer> previouslyUsedIds =
getPreviouslyUsedIdsForPackageLocked(userId, packageName);
@@ -1499,6 +1536,11 @@
public void onPackageModified(String packageName) {
onPackageModifiedInternal(getChangingUserId(), packageName);
}
+
+ @Override
+ public void onPackageAdded(String packageName, int uid) {
+ onPackageAddedInternal(getChangingUserId(), packageName);
+ }
};
static int getFirstAssociationIdForUser(@UserIdInt int userId) {
@@ -1702,7 +1744,7 @@
}
}
- private static class PerUserAssociationSet extends PerUser<Set<AssociationInfo>> {
+ static class PerUserAssociationSet extends PerUser<Set<AssociationInfo>> {
@Override
protected @NonNull Set<AssociationInfo> create(int userId) {
return new ArraySet<>();
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 9fdf5c2..53c0184c 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -18,6 +18,8 @@
import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_CONTEXT_SYNC;
+import static com.android.server.companion.PermissionsUtils.sanitizeWithCallerChecks;
+
import android.companion.AssociationInfo;
import android.companion.ContextSyncMessage;
import android.companion.Flags;
@@ -26,6 +28,7 @@
import android.net.MacAddress;
import android.os.Binder;
import android.os.ShellCommand;
+import android.util.Base64;
import android.util.proto.ProtoOutputStream;
import com.android.server.companion.datatransfer.SystemDataTransferProcessor;
@@ -47,19 +50,22 @@
private final SystemDataTransferProcessor mSystemDataTransferProcessor;
private final AssociationRequestsProcessor mAssociationRequestsProcessor;
+ private final BackupRestoreProcessor mBackupRestoreProcessor;
CompanionDeviceShellCommand(CompanionDeviceManagerService service,
AssociationStoreImpl associationStore,
CompanionDevicePresenceMonitor devicePresenceMonitor,
CompanionTransportManager transportManager,
SystemDataTransferProcessor systemDataTransferProcessor,
- AssociationRequestsProcessor associationRequestsProcessor) {
+ AssociationRequestsProcessor associationRequestsProcessor,
+ BackupRestoreProcessor backupRestoreProcessor) {
mService = service;
mAssociationStore = associationStore;
mDevicePresenceMonitor = devicePresenceMonitor;
mTransportManager = transportManager;
mSystemDataTransferProcessor = systemDataTransferProcessor;
mAssociationRequestsProcessor = associationRequestsProcessor;
+ mBackupRestoreProcessor = backupRestoreProcessor;
}
@Override
@@ -111,6 +117,19 @@
}
break;
+ case "disassociate-all": {
+ final int userId = getNextIntArgRequired();
+ final String packageName = getNextArgRequired();
+ final List<AssociationInfo> userAssociations =
+ mAssociationStore.getAssociationsForPackage(userId, packageName);
+ for (AssociationInfo association : userAssociations) {
+ if (sanitizeWithCallerChecks(mService.getContext(), association) != null) {
+ mService.disassociateInternal(association.getId());
+ }
+ }
+ }
+ break;
+
case "clear-association-memory-cache":
mService.persistState();
mService.loadAssociationsFromDisk();
@@ -126,6 +145,20 @@
mDevicePresenceMonitor.simulateDeviceEvent(associationId, /* event */ 1);
break;
+ case "get-backup-payload": {
+ final int userId = getNextIntArgRequired();
+ byte[] payload = mBackupRestoreProcessor.getBackupPayload(userId);
+ out.println(Base64.encodeToString(payload, Base64.NO_WRAP));
+ }
+ break;
+
+ case "apply-restored-payload": {
+ final int userId = getNextIntArgRequired();
+ byte[] payload = Base64.decode(getNextArgRequired(), Base64.NO_WRAP);
+ mBackupRestoreProcessor.applyRestoredPayload(payload, userId);
+ }
+ break;
+
case "remove-inactive-associations": {
// This command should trigger the same "clean-up" job as performed by the
// InactiveAssociationsRemovalService JobService. However, since the
@@ -355,6 +388,8 @@
pw.println(" Create a new Association.");
pw.println(" disassociate USER_ID PACKAGE MAC_ADDRESS");
pw.println(" Remove an existing Association.");
+ pw.println(" disassociate-all USER_ID");
+ pw.println(" Remove all Associations for a user.");
pw.println(" clear-association-memory-cache");
pw.println(" Clear the in-memory association cache and reload all association ");
pw.println(" information from persistent storage. USE FOR DEBUGGING PURPOSES ONLY.");
@@ -378,6 +413,14 @@
pw.println(" invoked for the same device (same ASSOCIATION_ID) no longer than");
pw.println(" 60 seconds ago.");
+ pw.println(" get-backup-payload USER_ID");
+ pw.println(" Generate backup payload for the given user and print its content");
+ pw.println(" encoded to a Base64 string.");
+ pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
+ pw.println(" apply-restored-payload USER_ID PAYLOAD");
+ pw.println(" Apply restored backup payload for the given user.");
+ pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
+
if (Flags.devicePresence()) {
pw.println(" simulate-device-event ASSOCIATION_ID EVENT");
pw.println(" Simulate the companion device event changes:");
diff --git a/services/companion/java/com/android/server/companion/DataStoreUtils.java b/services/companion/java/com/android/server/companion/DataStoreUtils.java
index c182529..04ce1f6 100644
--- a/services/companion/java/com/android/server/companion/DataStoreUtils.java
+++ b/services/companion/java/com/android/server/companion/DataStoreUtils.java
@@ -30,8 +30,11 @@
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileOutputStream;
+import java.io.IOException;
/**
* Util class for CDM data stores
@@ -88,6 +91,29 @@
}
}
+ /**
+ * Read a file and return the byte array containing the bytes of the file.
+ */
+ @NonNull
+ public static byte[] fileToByteArray(@NonNull AtomicFile file) {
+ if (!file.getBaseFile().exists()) {
+ Slog.d(TAG, "File does not exist");
+ return new byte[0];
+ }
+ try (FileInputStream in = file.openRead()) {
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ byte[] buffer = new byte[1024];
+ int read;
+ while ((read = in.read(buffer)) != -1) {
+ bytes.write(buffer, 0, read);
+ }
+ return bytes.toByteArray();
+ } catch (IOException e) {
+ Slog.e(TAG, "Error while reading requests file", e);
+ }
+ return new byte[0];
+ }
+
private DataStoreUtils() {
}
}
diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java
index b4b9379..1ebe65c 100644
--- a/services/companion/java/com/android/server/companion/PersistentDataStore.java
+++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java
@@ -28,6 +28,7 @@
import static com.android.server.companion.CompanionDeviceManagerService.getFirstAssociationIdForUser;
import static com.android.server.companion.CompanionDeviceManagerService.getLastAssociationIdForUser;
import static com.android.server.companion.DataStoreUtils.createStorageFileForUser;
+import static com.android.server.companion.DataStoreUtils.fileToByteArray;
import static com.android.server.companion.DataStoreUtils.isEndOfTag;
import static com.android.server.companion.DataStoreUtils.isStartOfTag;
import static com.android.server.companion.DataStoreUtils.writeToFileSafely;
@@ -55,9 +56,11 @@
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
+import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
@@ -186,6 +189,7 @@
private static final String XML_ATTR_SELF_MANAGED = "self_managed";
private static final String XML_ATTR_NOTIFY_DEVICE_NEARBY = "notify_device_nearby";
private static final String XML_ATTR_REVOKED = "revoked";
+ private static final String XML_ATTR_PENDING = "pending";
private static final String XML_ATTR_TIME_APPROVED = "time_approved";
private static final String XML_ATTR_LAST_TIME_CONNECTED = "last_time_connected";
private static final String XML_ATTR_SYSTEM_DATA_SYNC_FLAGS = "system_data_sync_flags";
@@ -328,34 +332,42 @@
@NonNull String rootTag, @Nullable Collection<AssociationInfo> associationsOut,
@NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) {
try (FileInputStream in = file.openRead()) {
- final TypedXmlPullParser parser = Xml.resolvePullParser(in);
-
- XmlUtils.beginDocument(parser, rootTag);
- final int version = readIntAttribute(parser, XML_ATTR_PERSISTENCE_VERSION, 0);
- switch (version) {
- case 0:
- readAssociationsV0(parser, userId, associationsOut);
- break;
- case 1:
- while (true) {
- parser.nextTag();
- if (isStartOfTag(parser, XML_TAG_ASSOCIATIONS)) {
- readAssociationsV1(parser, userId, associationsOut);
- } else if (isStartOfTag(parser, XML_TAG_PREVIOUSLY_USED_IDS)) {
- readPreviouslyUsedIdsV1(parser, previouslyUsedIdsPerPackageOut);
- } else if (isEndOfTag(parser, rootTag)) {
- break;
- }
- }
- break;
- }
- return version;
+ return readStateFromInputStream(userId, in, rootTag, associationsOut,
+ previouslyUsedIdsPerPackageOut);
} catch (XmlPullParserException | IOException e) {
Slog.e(TAG, "Error while reading associations file", e);
return -1;
}
}
+ private int readStateFromInputStream(@UserIdInt int userId, @NonNull InputStream in,
+ @NonNull String rootTag, @Nullable Collection<AssociationInfo> associationsOut,
+ @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut)
+ throws XmlPullParserException, IOException {
+ final TypedXmlPullParser parser = Xml.resolvePullParser(in);
+
+ XmlUtils.beginDocument(parser, rootTag);
+ final int version = readIntAttribute(parser, XML_ATTR_PERSISTENCE_VERSION, 0);
+ switch (version) {
+ case 0:
+ readAssociationsV0(parser, userId, associationsOut);
+ break;
+ case 1:
+ while (true) {
+ parser.nextTag();
+ if (isStartOfTag(parser, XML_TAG_ASSOCIATIONS)) {
+ readAssociationsV1(parser, userId, associationsOut);
+ } else if (isStartOfTag(parser, XML_TAG_PREVIOUSLY_USED_IDS)) {
+ readPreviouslyUsedIdsV1(parser, previouslyUsedIdsPerPackageOut);
+ } else if (isEndOfTag(parser, rootTag)) {
+ break;
+ }
+ }
+ break;
+ }
+ return version;
+ }
+
private void persistStateToFileLocked(@NonNull AtomicFile file,
@Nullable Collection<AssociationInfo> associations,
@NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackage) {
@@ -391,6 +403,26 @@
u -> createStorageFileForUser(userId, FILE_NAME));
}
+ byte[] getBackupPayload(@UserIdInt int userId) {
+ Slog.i(TAG, "Fetching stored state data for user " + userId + " from disk");
+ final AtomicFile file = getStorageFileForUser(userId);
+
+ synchronized (file) {
+ return fileToByteArray(file);
+ }
+ }
+
+ void readStateFromPayload(byte[] payload, @UserIdInt int userId,
+ @NonNull Set<AssociationInfo> associationsOut,
+ @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) {
+ try (ByteArrayInputStream in = new ByteArrayInputStream(payload)) {
+ readStateFromInputStream(userId, in, XML_TAG_STATE, associationsOut,
+ previouslyUsedIdsPerPackageOut);
+ } catch (XmlPullParserException | IOException e) {
+ Slog.e(TAG, "Error while reading associations file", e);
+ }
+ }
+
private static @NonNull File getBaseLegacyStorageFileForUser(@UserIdInt int userId) {
return new File(Environment.getUserSystemDirectory(userId), FILE_NAME_LEGACY);
}
@@ -433,8 +465,8 @@
out.add(new AssociationInfo(associationId, userId, appPackage, tag,
MacAddress.fromString(deviceAddress), null, profile, null,
- /* managedByCompanionApp */ false, notify, /* revoked */ false, timeApproved,
- Long.MAX_VALUE, /* systemDataSyncFlags */ 0));
+ /* managedByCompanionApp */ false, notify, /* revoked */ false, /* pending */ false,
+ timeApproved, Long.MAX_VALUE, /* systemDataSyncFlags */ 0));
}
private static void readAssociationsV1(@NonNull TypedXmlPullParser parser,
@@ -465,6 +497,7 @@
final boolean selfManaged = readBooleanAttribute(parser, XML_ATTR_SELF_MANAGED);
final boolean notify = readBooleanAttribute(parser, XML_ATTR_NOTIFY_DEVICE_NEARBY);
final boolean revoked = readBooleanAttribute(parser, XML_ATTR_REVOKED, false);
+ final boolean pending = readBooleanAttribute(parser, XML_ATTR_PENDING, false);
final long timeApproved = readLongAttribute(parser, XML_ATTR_TIME_APPROVED, 0L);
final long lastTimeConnected = readLongAttribute(
parser, XML_ATTR_LAST_TIME_CONNECTED, Long.MAX_VALUE);
@@ -473,7 +506,7 @@
final AssociationInfo associationInfo = createAssociationInfoNoThrow(associationId, userId,
appPackage, tag, macAddress, displayName, profile, selfManaged, notify, revoked,
- timeApproved, lastTimeConnected, systemDataSyncFlags);
+ pending, timeApproved, lastTimeConnected, systemDataSyncFlags);
if (associationInfo != null) {
out.add(associationInfo);
}
@@ -527,8 +560,8 @@
writeBooleanAttribute(serializer, XML_ATTR_SELF_MANAGED, a.isSelfManaged());
writeBooleanAttribute(
serializer, XML_ATTR_NOTIFY_DEVICE_NEARBY, a.isNotifyOnDeviceNearby());
- writeBooleanAttribute(
- serializer, XML_ATTR_REVOKED, a.isRevoked());
+ writeBooleanAttribute(serializer, XML_ATTR_REVOKED, a.isRevoked());
+ writeBooleanAttribute(serializer, XML_ATTR_PENDING, a.isPending());
writeLongAttribute(serializer, XML_ATTR_TIME_APPROVED, a.getTimeApprovedMs());
writeLongAttribute(
serializer, XML_ATTR_LAST_TIME_CONNECTED, a.getLastTimeConnectedMs());
@@ -572,14 +605,14 @@
@UserIdInt int userId, @NonNull String appPackage, @Nullable String tag,
@Nullable MacAddress macAddress, @Nullable CharSequence displayName,
@Nullable String profile, boolean selfManaged, boolean notify, boolean revoked,
- long timeApproved, long lastTimeConnected, int systemDataSyncFlags) {
+ boolean pending, long timeApproved, long lastTimeConnected, int systemDataSyncFlags) {
AssociationInfo associationInfo = null;
try {
// We do not persist AssociatedDevice, which means that AssociationInfo retrieved from
// datastore is not guaranteed to be identical to the one from initial association.
associationInfo = new AssociationInfo(associationId, userId, appPackage, tag,
macAddress, displayName, profile, null, selfManaged, notify,
- revoked, timeApproved, lastTimeConnected, systemDataSyncFlags);
+ revoked, pending, timeApproved, lastTimeConnected, systemDataSyncFlags);
} catch (Exception e) {
if (DEBUG) Log.w(TAG, "Could not create AssociationInfo", e);
}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
index 9f489e8..51c5fd6 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
@@ -23,6 +23,7 @@
import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
import static com.android.internal.util.XmlUtils.writeIntAttribute;
import static com.android.server.companion.DataStoreUtils.createStorageFileForUser;
+import static com.android.server.companion.DataStoreUtils.fileToByteArray;
import static com.android.server.companion.DataStoreUtils.isEndOfTag;
import static com.android.server.companion.DataStoreUtils.isStartOfTag;
import static com.android.server.companion.DataStoreUtils.writeToFileSafely;
@@ -44,6 +45,7 @@
import org.xmlpull.v1.XmlPullParserException;
+import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
@@ -67,7 +69,6 @@
* <request
* association_id="1"
* data_type="1"
- * user_id="12"
* is_user_consented="true"
* </request>
* </requests>
@@ -84,7 +85,6 @@
private static final String XML_ATTR_ASSOCIATION_ID = "association_id";
private static final String XML_ATTR_DATA_TYPE = "data_type";
- private static final String XML_ATTR_USER_ID = "user_id";
private static final String XML_ATTR_IS_USER_CONSENTED = "is_user_consented";
private static final int READ_FROM_DISK_TIMEOUT = 5; // in seconds
@@ -152,6 +152,33 @@
mExecutor.execute(() -> writeRequestsToStore(userId, cachedRequests));
}
+ /**
+ * Return the byte contents of the XML file storing current system data transfer requests.
+ */
+ public byte[] getBackupPayload(@UserIdInt int userId) {
+ final AtomicFile file = getStorageFileForUser(userId);
+
+ synchronized (file) {
+ return fileToByteArray(file);
+ }
+ }
+
+ /**
+ * Parse the byte array containing XML information of system data transfer requests into
+ * an array list of requests.
+ */
+ public List<SystemDataTransferRequest> readRequestsFromPayload(byte[] payload, int userId) {
+ try (ByteArrayInputStream in = new ByteArrayInputStream(payload)) {
+ final TypedXmlPullParser parser = Xml.resolvePullParser(in);
+ XmlUtils.beginDocument(parser, XML_TAG_REQUESTS);
+
+ return readRequestsFromXml(parser, userId);
+ } catch (XmlPullParserException | IOException e) {
+ Slog.e(LOG_TAG, "Error while reading requests file", e);
+ return new ArrayList<>();
+ }
+ }
+
@GuardedBy("mLock")
private ArrayList<SystemDataTransferRequest> readRequestsFromCache(@UserIdInt int userId) {
ArrayList<SystemDataTransferRequest> cachedRequests = mCachedPerUser.get(userId);
@@ -197,7 +224,7 @@
final TypedXmlPullParser parser = Xml.resolvePullParser(in);
XmlUtils.beginDocument(parser, XML_TAG_REQUESTS);
- return readRequestsFromXml(parser);
+ return readRequestsFromXml(parser, userId);
} catch (XmlPullParserException | IOException e) {
Slog.e(LOG_TAG, "Error while reading requests file", e);
return new ArrayList<>();
@@ -207,7 +234,8 @@
@NonNull
private ArrayList<SystemDataTransferRequest> readRequestsFromXml(
- @NonNull TypedXmlPullParser parser) throws XmlPullParserException, IOException {
+ @NonNull TypedXmlPullParser parser, int userId)
+ throws XmlPullParserException, IOException {
if (!isStartOfTag(parser, XML_TAG_REQUESTS)) {
throw new XmlPullParserException("The XML doesn't have start tag: " + XML_TAG_REQUESTS);
}
@@ -220,14 +248,15 @@
break;
}
if (isStartOfTag(parser, XML_TAG_REQUEST)) {
- requests.add(readRequestFromXml(parser));
+ requests.add(readRequestFromXml(parser, userId));
}
}
return requests;
}
- private SystemDataTransferRequest readRequestFromXml(@NonNull TypedXmlPullParser parser)
+ private SystemDataTransferRequest readRequestFromXml(@NonNull TypedXmlPullParser parser,
+ int userId)
throws XmlPullParserException, IOException {
if (!isStartOfTag(parser, XML_TAG_REQUEST)) {
throw new XmlPullParserException("XML doesn't have start tag: " + XML_TAG_REQUEST);
@@ -235,7 +264,6 @@
final int associationId = readIntAttribute(parser, XML_ATTR_ASSOCIATION_ID);
final int dataType = readIntAttribute(parser, XML_ATTR_DATA_TYPE);
- final int userId = readIntAttribute(parser, XML_ATTR_USER_ID);
final boolean isUserConsented = readBooleanAttribute(parser, XML_ATTR_IS_USER_CONSENTED);
switch (dataType) {
@@ -292,7 +320,6 @@
writeIntAttribute(serializer, XML_ATTR_ASSOCIATION_ID, request.getAssociationId());
writeIntAttribute(serializer, XML_ATTR_DATA_TYPE, request.getDataType());
- writeIntAttribute(serializer, XML_ATTR_USER_ID, request.getUserId());
writeBooleanAttribute(serializer, XML_ATTR_IS_USER_CONSENTED, request.isUserConsented());
serializer.endTag(null, XML_TAG_REQUEST);
diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
index d089b05..2f9b6a5 100644
--- a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
+++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
@@ -55,9 +55,7 @@
@GuardedBy("mCameras")
private final Map<IBinder, CameraDescriptor> mCameras = new ArrayMap<>();
- public VirtualCameraController() {
- connectVirtualCameraService();
- }
+ public VirtualCameraController() {}
@VisibleForTesting
VirtualCameraController(IVirtualCameraService virtualCameraService) {
diff --git a/services/core/Android.bp b/services/core/Android.bp
index dd001ec..a3fc3bf 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -203,6 +203,7 @@
"com_android_wm_shell_flags_lib",
"com.android.server.utils_aconfig-java",
"service-jobscheduler-deviceidle.flags-aconfig-java",
+ "policy_flags_lib",
],
javac_shard_size: 50,
javacflags: [
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
index 9f279b1..329aac6 100644
--- a/services/core/java/com/android/server/BootReceiver.java
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -48,6 +48,8 @@
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.am.DropboxRateLimiter;
+import com.android.server.os.TombstoneProtos;
+import com.android.server.os.TombstoneProtos.Tombstone;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -60,11 +62,14 @@
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.attribute.PosixFilePermissions;
+import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
/**
* Performs a number of miscellaneous, non-system-critical actions
@@ -332,12 +337,12 @@
*
* @param ctx Context
* @param tombstone path to the tombstone
- * @param proto whether the tombstone is stored as proto
+ * @param tombstoneProto the parsed proto tombstone
* @param processName the name of the process corresponding to the tombstone
* @param tmpFileLock the lock for reading/writing tmp files
*/
public static void addTombstoneToDropBox(
- Context ctx, File tombstone, boolean proto, String processName,
+ Context ctx, File tombstone, Tombstone tombstoneProto, String processName,
ReentrantLock tmpFileLock) {
final DropBoxManager db = ctx.getSystemService(DropBoxManager.class);
if (db == null) {
@@ -347,31 +352,33 @@
// Check if we should rate limit and abort early if needed.
DropboxRateLimiter.RateLimitResult rateLimitResult =
- sDropboxRateLimiter.shouldRateLimit(
- proto ? TAG_TOMBSTONE_PROTO_WITH_HEADERS : TAG_TOMBSTONE, processName);
+ sDropboxRateLimiter.shouldRateLimit(TAG_TOMBSTONE_PROTO_WITH_HEADERS, processName);
if (rateLimitResult.shouldRateLimit()) return;
HashMap<String, Long> timestamps = readTimestamps();
try {
- if (proto) {
- if (recordFileTimestamp(tombstone, timestamps)) {
- // We need to attach the count indicating the number of dropped dropbox entries
- // due to rate limiting. Do this by enclosing the proto tombsstone in a
- // container proto that has the dropped entry count and the proto tombstone as
- // bytes (to avoid the complexity of reading and writing nested protos).
- tmpFileLock.lock();
- try {
- addAugmentedProtoToDropbox(tombstone, db, rateLimitResult);
- } finally {
- tmpFileLock.unlock();
- }
+ // Remove the memory data from the proto.
+ Tombstone tombstoneProtoWithoutMemory = removeMemoryFromTombstone(tombstoneProto);
+
+ final byte[] tombstoneBytes = tombstoneProtoWithoutMemory.toByteArray();
+
+ // Use JNI to call the c++ proto to text converter and add the headers to the tombstone.
+ String tombstoneWithoutMemory = new StringBuilder(getBootHeadersToLogAndUpdate())
+ .append(rateLimitResult.createHeader())
+ .append(getTombstoneText(tombstoneBytes))
+ .toString();
+
+ // Add the tombstone without memory data to dropbox.
+ db.addText(TAG_TOMBSTONE, tombstoneWithoutMemory);
+
+ // Add the tombstone proto to dropbox.
+ if (recordFileTimestamp(tombstone, timestamps)) {
+ tmpFileLock.lock();
+ try {
+ addAugmentedProtoToDropbox(tombstone, tombstoneBytes, db, rateLimitResult);
+ } finally {
+ tmpFileLock.unlock();
}
- } else {
- // Add the header indicating how many events have been dropped due to rate limiting.
- final String headers = getBootHeadersToLogAndUpdate()
- + rateLimitResult.createHeader();
- addFileToDropBox(db, timestamps, headers, tombstone.getPath(), LOG_SIZE,
- TAG_TOMBSTONE);
}
} catch (IOException e) {
Slog.e(TAG, "Can't log tombstone", e);
@@ -380,11 +387,8 @@
}
private static void addAugmentedProtoToDropbox(
- File tombstone, DropBoxManager db,
+ File tombstone, byte[] tombstoneBytes, DropBoxManager db,
DropboxRateLimiter.RateLimitResult rateLimitResult) throws IOException {
- // Read the proto tombstone file as bytes.
- final byte[] tombstoneBytes = Files.readAllBytes(tombstone.toPath());
-
final File tombstoneProtoWithHeaders = File.createTempFile(
tombstone.getName(), ".tmp", TOMBSTONE_TMP_DIR);
Files.setPosixFilePermissions(
@@ -417,6 +421,8 @@
}
}
+ private static native String getTombstoneText(byte[] tombstoneBytes);
+
private static void addLastkToDropBox(
DropBoxManager db, HashMap<String, Long> timestamps,
String headers, String footers, String filename, int maxSize,
@@ -434,6 +440,31 @@
addFileWithFootersToDropBox(db, timestamps, headers, footers, filename, maxSize, tag);
}
+ /** Removes memory information from the Tombstone proto. */
+ @VisibleForTesting
+ public static Tombstone removeMemoryFromTombstone(Tombstone tombstoneProto) {
+ Tombstone.Builder tombstoneBuilder = tombstoneProto.toBuilder()
+ .clearMemoryMappings()
+ .clearThreads()
+ .putAllThreads(tombstoneProto.getThreadsMap().entrySet()
+ .stream()
+ .map(BootReceiver::clearMemoryDump)
+ .collect(Collectors.toMap(e->e.getKey(), e->e.getValue())));
+
+ if (tombstoneProto.hasSignalInfo()) {
+ tombstoneBuilder.setSignalInfo(
+ tombstoneProto.getSignalInfo().toBuilder().clearFaultAdjacentMetadata());
+ }
+
+ return tombstoneBuilder.build();
+ }
+
+ private static AbstractMap.SimpleEntry<Integer, TombstoneProtos.Thread> clearMemoryDump(
+ Map.Entry<Integer, TombstoneProtos.Thread> e) {
+ return new AbstractMap.SimpleEntry<Integer, TombstoneProtos.Thread>(
+ e.getKey(), e.getValue().toBuilder().clearMemoryDump().build());
+ }
+
private static void addFileToDropBox(
DropBoxManager db, HashMap<String, Long> timestamps,
String headers, String filename, int maxSize, String tag) throws IOException {
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index e289a56..e923e30a 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -43,3 +43,6 @@
per-file TelephonyRegistry.java = file:/telephony/OWNERS
per-file UiModeManagerService.java = file:/packages/SystemUI/OWNERS
per-file VcnManagementService.java = file:/services/core/java/com/android/server/vcn/OWNERS
+
+# SystemConfig
+per-file SystemConfig.java = file:/PACKAGE_MANAGER_OWNERS
diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
new file mode 100644
index 0000000..70bd4b3
--- /dev/null
+++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
@@ -0,0 +1,188 @@
+/*
+ * 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;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.media.projection.MediaProjectionInfo;
+import android.media.projection.MediaProjectionManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationListenerService.RankingMap;
+import android.service.notification.StatusBarNotification;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wm.SensitiveContentPackages.PackageInfo;
+import com.android.server.wm.WindowManagerInternal;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Service that monitors for notifications with sensitive content and protects content from screen
+ * sharing
+ */
+public final class SensitiveContentProtectionManagerService extends SystemService {
+ private static final String TAG = "SensitiveContentProtect";
+ private static final boolean DEBUG = false;
+
+ @VisibleForTesting
+ NotificationListener mNotificationListener;
+ private @Nullable MediaProjectionManager mProjectionManager;
+ private @Nullable WindowManagerInternal mWindowManager;
+
+ private final MediaProjectionManager.Callback mProjectionCallback =
+ new MediaProjectionManager.Callback() {
+ @Override
+ public void onStart(MediaProjectionInfo info) {
+ if (DEBUG) Log.d(TAG, "onStart projection: " + info);
+ onProjectionStart();
+ }
+
+ @Override
+ public void onStop(MediaProjectionInfo info) {
+ if (DEBUG) Log.d(TAG, "onStop projection: " + info);
+ onProjectionEnd();
+ }
+ };
+
+ public SensitiveContentProtectionManagerService(@NonNull Context context) {
+ super(context);
+ mNotificationListener = new NotificationListener();
+ }
+
+ @Override
+ public void onStart() {}
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase != SystemService.PHASE_BOOT_COMPLETED) {
+ return;
+ }
+
+ if (DEBUG) Log.d(TAG, "onBootPhase - PHASE_BOOT_COMPLETED");
+
+ init(getContext().getSystemService(MediaProjectionManager.class),
+ LocalServices.getService(WindowManagerInternal.class));
+ }
+
+ @VisibleForTesting
+ void init(MediaProjectionManager projectionManager,
+ WindowManagerInternal windowManager) {
+ if (DEBUG) Log.d(TAG, "init");
+
+ checkNotNull(projectionManager, "Failed to get valid MediaProjectionManager");
+ checkNotNull(windowManager, "Failed to get valid WindowManagerInternal");
+
+ mProjectionManager = projectionManager;
+ mWindowManager = windowManager;
+
+ // TODO(b/317250444): use MediaProjectionManagerService directly, reduces unnecessary
+ // handler, delegate, and binder death recipient
+ mProjectionManager.addCallback(mProjectionCallback, new Handler(Looper.getMainLooper()));
+
+ try {
+ mNotificationListener.registerAsSystemService(getContext(),
+ new ComponentName(getContext(), NotificationListener.class),
+ UserHandle.USER_ALL);
+ } catch (RemoteException e) {
+ // Intra-process call, should never happen.
+ }
+ }
+
+ /** Cleanup any callbacks and listeners */
+ @VisibleForTesting
+ void onDestroy() {
+ if (mProjectionManager != null) {
+ mProjectionManager.removeCallback(mProjectionCallback);
+ }
+
+ try {
+ mNotificationListener.unregisterAsSystemService();
+ } catch (RemoteException e) {
+ // Intra-process call, should never happen.
+ }
+
+ if (mWindowManager != null) {
+ onProjectionEnd();
+ }
+ }
+
+ private void onProjectionStart() {
+ StatusBarNotification[] notifications;
+ try {
+ notifications = mNotificationListener.getActiveNotifications();
+ } catch (SecurityException e) {
+ Log.e(TAG, "SensitiveContentProtectionManagerService doesn't have access.", e);
+ notifications = new StatusBarNotification[0];
+ }
+
+ RankingMap rankingMap;
+ try {
+ rankingMap = mNotificationListener.getCurrentRanking();
+ } catch (SecurityException e) {
+ Log.e(TAG, "SensitiveContentProtectionManagerService doesn't have access.", e);
+ rankingMap = null;
+ }
+
+ // notify windowmanager of any currently posted sensitive content notifications
+ Set<PackageInfo> packageInfos = getSensitivePackagesFromNotifications(
+ notifications,
+ rankingMap);
+
+ mWindowManager.setShouldBlockScreenCaptureForApp(packageInfos);
+ }
+
+ private void onProjectionEnd() {
+ // notify windowmanager to clear any sensitive notifications observed during projection
+ // session
+ mWindowManager.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ }
+
+ private Set<PackageInfo> getSensitivePackagesFromNotifications(
+ StatusBarNotification[] notifications, RankingMap rankingMap) {
+ if (rankingMap == null) {
+ Log.w(TAG, "Ranking map not initialized.");
+ return Collections.emptySet();
+ }
+
+ Set<PackageInfo> sensitivePackages = new ArraySet<>();
+ for (StatusBarNotification sbn : notifications) {
+ NotificationListenerService.Ranking ranking =
+ rankingMap.getRawRankingObject(sbn.getKey());
+ if (ranking != null && ranking.hasSensitiveContent()) {
+ PackageInfo info = new PackageInfo(sbn.getPackageName(), sbn.getUid());
+ sensitivePackages.add(info);
+ }
+ }
+ return sensitivePackages;
+ }
+
+ // TODO(b/317251408): add trigger that updates on onNotificationPosted,
+ // onNotificationRankingUpdate and onListenerConnected
+ @VisibleForTesting
+ static class NotificationListener extends NotificationListenerService {}
+}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 39b8643..19a9239 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -3236,7 +3236,7 @@
super.createUserStorageKeys_enforcePermission();
try {
- mVold.createUserStorageKeys(userId, serialNumber, ephemeral);
+ mVold.createUserStorageKeys(userId, ephemeral);
// Since the user's CE key was just created, the user's CE storage is now unlocked.
synchronized (mLock) {
mCeUnlockedUsers.append(userId);
@@ -3281,7 +3281,7 @@
super.unlockCeStorage_enforcePermission();
if (StorageManager.isFileEncrypted()) {
- mVold.unlockCeStorage(userId, serialNumber, HexDump.toHexString(secret));
+ mVold.unlockCeStorage(userId, HexDump.toHexString(secret));
}
synchronized (mLock) {
mCeUnlockedUsers.append(userId);
@@ -3368,7 +3368,7 @@
private void prepareUserStorageInternal(String volumeUuid, int userId, int serialNumber,
int flags) throws Exception {
try {
- mVold.prepareUserStorage(volumeUuid, userId, serialNumber, flags);
+ mVold.prepareUserStorage(volumeUuid, userId, flags);
// After preparing user storage, we should check if we should mount data mirror again,
// and we do it for user 0 only as we only need to do once for all users.
if (volumeUuid != null) {
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index 40b29d7..3483c1a 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -315,6 +315,11 @@
private final ArraySet<String> mBugreportWhitelistedPackages = new ArraySet<>();
private final ArraySet<String> mAppDataIsolationWhitelistedApps = new ArraySet<>();
+ // These packages will be set as 'prevent disable', where they are no longer possible
+ // for the end user to disable via settings. This flag should only be used for packages
+ // which meet the 'force or keep enabled apps' policy.
+ private final ArrayList<String> mPreventUserDisablePackages = new ArrayList<>();
+
// Map of packagesNames to userTypes. Stored temporarily until cleared by UserManagerService().
private ArrayMap<String, Set<String>> mPackageToUserTypeWhitelist = new ArrayMap<>();
private ArrayMap<String, Set<String>> mPackageToUserTypeBlacklist = new ArrayMap<>();
@@ -504,6 +509,10 @@
return mAppDataIsolationWhitelistedApps;
}
+ public @NonNull ArrayList<String> getPreventUserDisablePackages() {
+ return mPreventUserDisablePackages;
+ }
+
/**
* Gets map of packagesNames to userTypes, dictating on which user types each package should be
* initially installed, and then removes this map from SystemConfig.
@@ -1309,6 +1318,16 @@
}
XmlUtils.skipCurrentTag(parser);
} break;
+ case "prevent-disable": {
+ String pkgname = parser.getAttributeValue(null, "package");
+ if (pkgname == null) {
+ Slog.w(TAG, "<" + name + "> without package in " + permFile
+ + " at " + parser.getPositionDescription());
+ } else {
+ mPreventUserDisablePackages.add(pkgname);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ } break;
case "install-in-user-type": {
// NB: We allow any directory permission to declare install-in-user-type.
readInstallInUserType(parser,
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index b20ed7c..9db5d0a 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -162,7 +162,7 @@
"pdf_viewer",
"pixel_audio_android",
"pixel_bluetooth",
- "pixel_system_sw_touch",
+ "pixel_system_sw_video",
"pixel_watch",
"platform_security",
"power",
@@ -174,7 +174,9 @@
"safety_center",
"sensors",
"system_performance",
+ "system_sw_touch",
"system_sw_usb",
+ "statsd",
"test_suites",
"text",
"threadnetwork",
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 203ac2c..df8d9e1 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -2906,7 +2906,13 @@
// TODO(b/302609140): Remove extra logging after this issue is diagnosed.
if (code == OP_BLUETOOTH_CONNECT) {
Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
- + " #getOpsLocked returned null");
+ + " #getOpsLocked returned null for"
+ + " uid: " + uid
+ + " packageName: " + packageName
+ + " attributionTag: " + attributionTag
+ + " pvr.isAttributionTagValid: " + pvr.isAttributionTagValid
+ + " pvr.bypass: " + pvr.bypass);
+ Slog.e(TAG, "mUidStates.get(" + uid + "): " + mUidStates.get(uid));
}
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 7ac4dd3..a1b6f29 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -7879,7 +7879,6 @@
DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.addAll(AudioSystem.DEVICE_OUT_ALL_A2DP_SET);
DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.addAll(AudioSystem.DEVICE_OUT_ALL_BLE_SET);
DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.addAll(AudioSystem.DEVICE_OUT_ALL_USB_SET);
- DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_HDMI);
}
/** only public for mocking/spying, do not call outside of AudioService */
diff --git a/services/core/java/com/android/server/audio/FadeOutManager.java b/services/core/java/com/android/server/audio/FadeOutManager.java
index cbcd8f5..4d5bce5 100644
--- a/services/core/java/com/android/server/audio/FadeOutManager.java
+++ b/services/core/java/com/android/server/audio/FadeOutManager.java
@@ -29,6 +29,7 @@
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.server.utils.EventLogger;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -345,7 +346,8 @@
}
if (apc.getPlayerProxy() != null) {
applyVolumeShaperInternal(apc, piid, volShaper,
- skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
+ skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED, skipRamp,
+ PlaybackActivityMonitor.EVENT_TYPE_FADE_OUT);
mFadedPlayers.put(piid, volShaper);
} else {
if (DEBUG) {
@@ -361,7 +363,8 @@
final AudioPlaybackConfiguration apc = players.get(piid);
if ((apc != null) && (apc.getPlayerProxy() != null)) {
applyVolumeShaperInternal(apc, piid, /* volShaperConfig= */ null,
- VolumeShaper.Operation.REVERSE);
+ VolumeShaper.Operation.REVERSE, /* skipRamp= */ false,
+ PlaybackActivityMonitor.EVENT_TYPE_FADE_IN);
} else {
// this piid was in the list of faded players, but wasn't found
if (DEBUG) {
@@ -373,6 +376,7 @@
mFadedPlayers.clear();
}
+ @GuardedBy("mLock")
void fadeInPlayer(@NonNull AudioPlaybackConfiguration apc,
@Nullable VolumeShaper.Configuration config) {
int piid = Integer.valueOf(apc.getPlayerInterfaceId());
@@ -385,10 +389,17 @@
return;
}
+ VolumeShaper.Operation operation = VolumeShaper.Operation.REVERSE;
+ if (config != null) {
+ // replace and join the volumeshapers with (possibly) in progress fade out operation
+ // for a smoother fade in
+ operation = new VolumeShaper.Operation.Builder()
+ .replace(mFadedPlayers.get(piid).getId(), /* join= */ true).build();
+ }
mFadedPlayers.remove(piid);
if (apc.getPlayerProxy() != null) {
- applyVolumeShaperInternal(apc, piid, config,
- config != null ? PLAY_CREATE_IF_NEEDED : VolumeShaper.Operation.REVERSE);
+ applyVolumeShaperInternal(apc, piid, config, operation, /* skipRamp= */ false,
+ PlaybackActivityMonitor.EVENT_TYPE_FADE_IN);
} else {
if (DEBUG) {
Slog.v(TAG, "Error fading in player piid:" + piid
@@ -397,6 +408,7 @@
}
}
+ @GuardedBy("mLock")
void clear() {
if (mFadedPlayers.size() > 0) {
if (DEBUG) {
@@ -413,21 +425,40 @@
}
private void applyVolumeShaperInternal(AudioPlaybackConfiguration apc, int piid,
- VolumeShaper.Configuration volShaperConfig, VolumeShaper.Operation operation) {
+ VolumeShaper.Configuration volShaperConfig, VolumeShaper.Operation operation,
+ boolean skipRamp, String eventType) {
VolumeShaper.Configuration config = volShaperConfig;
// when operation is reverse, use the fade out volume shaper config instead
if (operation.equals(VolumeShaper.Operation.REVERSE)) {
config = mFadedPlayers.get(piid);
}
try {
- PlaybackActivityMonitor.sEventLogger.enqueue(
- (new PlaybackActivityMonitor.FadeEvent(apc, config, operation))
- .printLog(TAG));
+ logFadeEvent(apc, piid, volShaperConfig, operation, skipRamp, eventType);
apc.getPlayerProxy().applyVolumeShaper(config, operation);
} catch (Exception e) {
- Slog.e(TAG, "Error fading player piid:" + piid + " uid:" + mUid
- + " operation:" + operation, e);
+ Slog.e(TAG, "Error " + eventType + " piid:" + piid + " uid:" + mUid, e);
}
}
+
+ private void logFadeEvent(AudioPlaybackConfiguration apc, int piid,
+ VolumeShaper.Configuration config, VolumeShaper.Operation operation,
+ boolean skipRamp, String eventType) {
+ if (eventType.equals(PlaybackActivityMonitor.EVENT_TYPE_FADE_OUT)) {
+ PlaybackActivityMonitor.sEventLogger.enqueue(
+ (new PlaybackActivityMonitor.FadeOutEvent(apc, skipRamp, config, operation))
+ .printLog(TAG));
+ return;
+ }
+
+ if (eventType.equals(PlaybackActivityMonitor.EVENT_TYPE_FADE_IN)) {
+ PlaybackActivityMonitor.sEventLogger.enqueue(
+ (new PlaybackActivityMonitor.FadeInEvent(apc, skipRamp, config, operation))
+ .printLog(TAG));
+ return;
+ }
+
+ PlaybackActivityMonitor.sEventLogger.enqueue(
+ (new EventLogger.StringEvent(eventType + " piid:" + piid)).printLog(TAG));
+ }
}
}
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index e69fbbd..08da32e 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -84,6 +84,8 @@
/*package*/ static final int VOLUME_SHAPER_SYSTEM_FADEOUT_ID = 2;
/*package*/ static final int VOLUME_SHAPER_SYSTEM_MUTE_AWAIT_CONNECTION_ID = 3;
/*package*/ static final int VOLUME_SHAPER_SYSTEM_STRONG_DUCK_ID = 4;
+ /*package*/ static final String EVENT_TYPE_FADE_OUT = "fading out";
+ /*package*/ static final String EVENT_TYPE_FADE_IN = "fading in";
// ducking settings for a "normal duck" at -14dB
private static final VolumeShaper.Configuration DUCK_VSHAPE =
@@ -1204,11 +1206,13 @@
return;
}
try {
- sEventLogger.enqueue((new DuckEvent(apc, skipRamp, mUseStrongDuck))
- .printLog(TAG));
- apc.getPlayerProxy().applyVolumeShaper(
- mUseStrongDuck ? STRONG_DUCK_VSHAPE : DUCK_VSHAPE,
- skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
+ VolumeShaper.Configuration config =
+ mUseStrongDuck ? STRONG_DUCK_VSHAPE : DUCK_VSHAPE;
+ VolumeShaper.Operation operation =
+ skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED;
+ sEventLogger.enqueue((new DuckEvent(apc, skipRamp, mUseStrongDuck, config,
+ operation)).printLog(TAG));
+ apc.getPlayerProxy().applyVolumeShaper(config, operation);
mDuckedPlayers.add(piid);
} catch (Exception e) {
Log.e(TAG, "Error ducking player piid:" + piid + " uid:" + mUid, e);
@@ -1363,58 +1367,41 @@
}
}
- static final class FadeEvent extends EventLogger.Event {
- private final int mPlayerIId;
- private final int mPlayerType;
- private final int mClientUid;
- private final int mClientPid;
- private final AudioAttributes mPlayerAttr;
- private final VolumeShaper.Configuration mVShaper;
- private final VolumeShaper.Operation mVOperation;
-
- FadeEvent(AudioPlaybackConfiguration apc, VolumeShaper.Configuration vShaper,
- VolumeShaper.Operation vOperation) {
- mPlayerIId = apc.getPlayerInterfaceId();
- mClientUid = apc.getClientUid();
- mClientPid = apc.getClientPid();
- mPlayerAttr = apc.getAudioAttributes();
- mPlayerType = apc.getPlayerType();
- mVShaper = vShaper;
- mVOperation = vOperation;
- }
-
- @Override
- public String eventToString() {
- return "Fade Event:" + " player piid:" + mPlayerIId
- + " uid/pid:" + mClientUid + "/" + mClientPid
- + " player type:"
- + AudioPlaybackConfiguration.toLogFriendlyPlayerType(mPlayerType)
- + " attr:" + mPlayerAttr
- + " volume shaper:" + mVShaper
- + " volume operation:" + mVOperation;
- }
- }
-
private abstract static class VolumeShaperEvent extends EventLogger.Event {
private final int mPlayerIId;
private final boolean mSkipRamp;
private final int mClientUid;
private final int mClientPid;
+ private final int mPlayerType;
+ private final AudioAttributes mPlayerAttr;
+ private final VolumeShaper.Configuration mConfig;
+ private final VolumeShaper.Operation mOperation;
abstract String getVSAction();
- VolumeShaperEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) {
+ VolumeShaperEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp,
+ VolumeShaper.Configuration config, VolumeShaper.Operation operation) {
mPlayerIId = apc.getPlayerInterfaceId();
mSkipRamp = skipRamp;
mClientUid = apc.getClientUid();
mClientPid = apc.getClientPid();
+ mPlayerAttr = apc.getAudioAttributes();
+ mPlayerType = apc.getPlayerType();
+ mConfig = config;
+ mOperation = operation;
}
@Override
public String eventToString() {
- return new StringBuilder(getVSAction()).append(" player piid:").append(mPlayerIId)
- .append(" uid/pid:").append(mClientUid).append("/").append(mClientPid)
- .append(" skip ramp:").append(mSkipRamp).toString();
+ return getVSAction()
+ + " player piid:" + mPlayerIId
+ + " uid/pid:" + mClientUid + "/" + mClientPid
+ + " skip ramp:" + mSkipRamp
+ + " player type:"
+ + AudioPlaybackConfiguration.toLogFriendlyPlayerType(mPlayerType)
+ + " attr:" + mPlayerAttr
+ + " config:" + mConfig
+ + " operation:" + mOperation;
}
}
@@ -1426,9 +1413,10 @@
return mUseStrongDuck ? "ducking (strong)" : "ducking";
}
- DuckEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp, boolean useStrongDuck)
+ DuckEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp, boolean useStrongDuck,
+ VolumeShaper.Configuration config, VolumeShaper.Operation operation)
{
- super(apc, skipRamp);
+ super(apc, skipRamp, config, operation);
mUseStrongDuck = useStrongDuck;
}
}
@@ -1436,11 +1424,24 @@
static final class FadeOutEvent extends VolumeShaperEvent {
@Override
String getVSAction() {
- return "fading out";
+ return EVENT_TYPE_FADE_OUT;
}
- FadeOutEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) {
- super(apc, skipRamp);
+ FadeOutEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp,
+ VolumeShaper.Configuration config, VolumeShaper.Operation operation) {
+ super(apc, skipRamp, config, operation);
+ }
+ }
+
+ static final class FadeInEvent extends VolumeShaperEvent {
+ @Override
+ String getVSAction() {
+ return EVENT_TYPE_FADE_IN;
+ }
+
+ FadeInEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp,
+ VolumeShaper.Configuration config, VolumeShaper.Operation operation) {
+ super(apc, skipRamp, config, operation);
}
}
diff --git a/services/core/java/com/android/server/criticalevents/CriticalEventLog.java b/services/core/java/com/android/server/criticalevents/CriticalEventLog.java
index 0814375..816c349 100644
--- a/services/core/java/com/android/server/criticalevents/CriticalEventLog.java
+++ b/services/core/java/com/android/server/criticalevents/CriticalEventLog.java
@@ -29,6 +29,7 @@
import com.android.server.criticalevents.nano.CriticalEventProto;
import com.android.server.criticalevents.nano.CriticalEventProto.AppNotResponding;
import com.android.server.criticalevents.nano.CriticalEventProto.HalfWatchdog;
+import com.android.server.criticalevents.nano.CriticalEventProto.InstallPackages;
import com.android.server.criticalevents.nano.CriticalEventProto.JavaCrash;
import com.android.server.criticalevents.nano.CriticalEventProto.NativeCrash;
import com.android.server.criticalevents.nano.CriticalEventProto.SystemServerStarted;
@@ -142,6 +143,13 @@
return System.currentTimeMillis();
}
+ /** Logs when one or more packages are installed. */
+ public void logInstallPackagesStarted() {
+ CriticalEventProto event = new CriticalEventProto();
+ event.setInstallPackages(new InstallPackages());
+ log(event);
+ }
+
/** Logs when system server started. */
public void logSystemServerStarted() {
CriticalEventProto event = new CriticalEventProto();
diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
index 1ac3a12..e54f30f 100644
--- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -27,6 +27,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.config.layout.Layouts;
import com.android.server.display.config.layout.XmlParser;
+import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.layout.DisplayIdProducer;
import com.android.server.display.layout.Layout;
@@ -63,24 +64,40 @@
private static final String CONFIG_FILE_PATH =
"etc/displayconfig/display_layout_configuration.xml";
+ private static final String DATA_CONFIG_FILE_PATH =
+ "system/displayconfig/display_layout_configuration.xml";
+
private final SparseArray<Layout> mLayoutMap = new SparseArray<>();
private final DisplayIdProducer mIdProducer;
+ private final boolean mIsPortInDisplayLayoutEnabled;
- DeviceStateToLayoutMap(DisplayIdProducer idProducer) {
- this(idProducer, Environment.buildPath(
- Environment.getVendorDirectory(), CONFIG_FILE_PATH));
+ DeviceStateToLayoutMap(DisplayIdProducer idProducer, DisplayManagerFlags flags) {
+ this(idProducer, flags, getConfigFile());
}
- DeviceStateToLayoutMap(DisplayIdProducer idProducer, File configFile) {
+ DeviceStateToLayoutMap(DisplayIdProducer idProducer, DisplayManagerFlags flags,
+ File configFile) {
+ mIsPortInDisplayLayoutEnabled = flags.isPortInDisplayLayoutEnabled();
mIdProducer = idProducer;
loadLayoutsFromConfig(configFile);
createLayout(STATE_DEFAULT);
}
+ static private File getConfigFile() {
+ final File configFileFromDataDir = Environment.buildPath(Environment.getDataDirectory(),
+ DATA_CONFIG_FILE_PATH);
+ if (configFileFromDataDir.exists()) {
+ return configFileFromDataDir;
+ } else {
+ return Environment.buildPath(Environment.getVendorDirectory(), CONFIG_FILE_PATH);
+ }
+ }
+
public void dumpLocked(IndentingPrintWriter ipw) {
ipw.println("DeviceStateToLayoutMap:");
ipw.increaseIndent();
+ ipw.println("mIsPortInDisplayLayoutEnabled=" + mIsPortInDisplayLayoutEnabled);
ipw.println("Registered Layouts:");
for (int i = 0; i < mLayoutMap.size(); i++) {
ipw.println("state(" + mLayoutMap.keyAt(i) + "): " + mLayoutMap.valueAt(i));
@@ -120,13 +137,15 @@
final Layout layout = createLayout(state);
for (com.android.server.display.config.layout.Display d: l.getDisplay()) {
assert layout != null;
+ final DisplayAddress address = getDisplayAddressForLayoutDisplay(d);
+
int position = getPosition(d.getPosition());
BigInteger leadDisplayPhysicalId = d.getLeadDisplayAddress();
DisplayAddress leadDisplayAddress = leadDisplayPhysicalId == null ? null
: DisplayAddress.fromPhysicalDisplayId(
leadDisplayPhysicalId.longValue());
layout.createDisplayLocked(
- DisplayAddress.fromPhysicalDisplayId(d.getAddress().longValue()),
+ address,
d.isDefaultDisplay(),
d.isEnabled(),
d.getDisplayGroup(),
@@ -146,6 +165,20 @@
}
}
+ private DisplayAddress getDisplayAddressForLayoutDisplay(
+ @NonNull com.android.server.display.config.layout.Display display) {
+ BigInteger xmlAddress = display.getAddress_optional();
+ if (xmlAddress != null) {
+ return DisplayAddress.fromPhysicalDisplayId(xmlAddress.longValue());
+ }
+ if (!mIsPortInDisplayLayoutEnabled || display.getPort_optional() == null) {
+ throw new IllegalArgumentException(
+ "Must specify a display identifier in display layout configuration: " + display);
+ }
+ return DisplayAddress.fromPortAndModel((int) display.getPort_optional().longValue(),
+ /* model= */ null);
+ }
+
private int getPosition(@NonNull String position) {
int positionInt = POSITION_UNKNOWN;
if (FRONT_STRING.equals(position)) {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceRepository.java b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
index 67e612d..6164154 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceRepository.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
@@ -129,7 +129,9 @@
public DisplayDevice getByAddressLocked(@NonNull DisplayAddress address) {
for (int i = mDisplayDevices.size() - 1; i >= 0; i--) {
final DisplayDevice device = mDisplayDevices.get(i);
- if (address.equals(device.getDisplayDeviceInfoLocked().address)) {
+ final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+ if (address.equals(info.address)
+ || DisplayAddress.Physical.isPortMatch(address, info.address)) {
return device;
}
}
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 115111a..2e8de31 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -205,7 +205,7 @@
@NonNull Handler handler, DisplayManagerFlags flags) {
this(context, foldSettingProvider, repo, listener, syncRoot, handler,
new DeviceStateToLayoutMap((isDefault) -> isDefault ? DEFAULT_DISPLAY
- : sNextNonDefaultDisplayId++), flags);
+ : sNextNonDefaultDisplayId++, flags), flags);
}
LogicalDisplayMapper(@NonNull Context context, FoldSettingProvider foldSettingProvider,
@@ -1094,8 +1094,8 @@
final DisplayAddress address = displayLayout.getAddress();
final DisplayDevice device = mDisplayDeviceRepo.getByAddressLocked(address);
if (device == null) {
- Slog.w(TAG, "The display device (" + address + "), is not available"
- + " for the display state " + mDeviceState);
+ Slog.w(TAG, "applyLayoutLocked: The display device (" + address + "), is not "
+ + "available for the display state " + mDeviceState);
continue;
}
diff --git a/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java b/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java
index 6978686..544f490 100644
--- a/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java
+++ b/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java
@@ -118,8 +118,13 @@
"The first lux value in the display brightness mapping must be 0");
}
- String key = (mapping.getMode() == null ? "default" : mapping.getMode()) + "_"
- + (mapping.getSetting() == null ? "normal" : mapping.getSetting());
+ String key = (mapping.getMode() == null
+ ? AutoBrightnessModeName._default.getRawName()
+ : mapping.getMode().getRawName())
+ + "_"
+ + (mapping.getSetting() == null
+ ? AutoBrightnessSettingName.normal.getRawName()
+ : mapping.getSetting().getRawName());
if (mBrightnessLevelsMap.containsKey(key)
|| mBrightnessLevelsLuxMap.containsKey(key)) {
throw new IllegalArgumentException(
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index aa7f07d..be48eb4 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -37,6 +37,9 @@
// 'adb shell setprop persist.log.tag.DisplayManagerFlags DEBUG && adb reboot'
private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
+ private final FlagState mPortInDisplayLayoutFlagState = new FlagState(
+ Flags.FLAG_ENABLE_PORT_IN_DISPLAY_LAYOUT,
+ Flags::enablePortInDisplayLayout);
private final FlagState mConnectedDisplayManagementFlagState = new FlagState(
Flags.FLAG_ENABLE_CONNECTED_DISPLAY_MANAGEMENT,
@@ -109,6 +112,18 @@
Flags.FLAG_FAST_HDR_TRANSITIONS,
Flags::fastHdrTransitions);
+ private final FlagState mRefreshRateVotingTelemetry = new FlagState(
+ Flags.FLAG_REFRESH_RATE_VOTING_TELEMETRY,
+ Flags::refreshRateVotingTelemetry
+ );
+
+ /**
+ * @return {@code true} if 'port' is allowed in display layout configuration file.
+ */
+ public boolean isPortInDisplayLayoutEnabled() {
+ return mPortInDisplayLayoutFlagState.isEnabled();
+ }
+
/** Returns whether connected display management is enabled or not. */
public boolean isConnectedDisplayManagementEnabled() {
return mConnectedDisplayManagementFlagState.isEnabled();
@@ -220,6 +235,10 @@
return mFastHdrTransitions.isEnabled();
}
+ public boolean isRefreshRateVotingTelemetryEnabled() {
+ return mRefreshRateVotingTelemetry.isEnabled();
+ }
+
/**
* dumps all flagstates
* @param pw printWriter
@@ -242,6 +261,7 @@
pw.println(" " + mBrightnessWearBedtimeModeClamperFlagState);
pw.println(" " + mAutoBrightnessModesFlagState);
pw.println(" " + mFastHdrTransitions);
+ pw.println(" " + mRefreshRateVotingTelemetry);
}
private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 04ecbb9..c9569cb 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -3,6 +3,14 @@
# Important: Flags must be accessed through DisplayManagerFlags.
flag {
+ name: "enable_port_in_display_layout"
+ namespace: "display_manager"
+ description: "Allows refering to displays by port in display layout"
+ bug: "303058435"
+ is_fixed_read_only: true
+}
+
+flag {
name: "enable_connected_display_management"
namespace: "display_manager"
description: "Feature flag for Connected Display management"
@@ -161,3 +169,10 @@
is_fixed_read_only: true
}
+flag {
+ name: "refresh_rate_voting_telemetry"
+ namespace: "display_manager"
+ description: "Feature flag for enabling telemetry for refresh rate voting in DisplayManager"
+ bug: "310029108"
+ is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java
index 40cb3303..8a362f7 100644
--- a/services/core/java/com/android/server/display/layout/Layout.java
+++ b/services/core/java/com/android/server/display/layout/Layout.java
@@ -200,13 +200,7 @@
* @return True if the specified address is used in this layout.
*/
public boolean contains(@NonNull DisplayAddress address) {
- final int size = mDisplays.size();
- for (int i = 0; i < size; i++) {
- if (address.equals(mDisplays.get(i).getAddress())) {
- return true;
- }
- }
- return false;
+ return getByAddress(address) != null;
}
/**
@@ -237,6 +231,9 @@
if (address.equals(display.getAddress())) {
return display;
}
+ if (DisplayAddress.Physical.isPortMatch(address, display.getAddress())) {
+ return display;
+ }
}
return null;
}
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 6e503cb..50e9533 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -154,6 +154,9 @@
private final VotesStorage mVotesStorage;
+ @Nullable
+ private final VotesStatsReporter mVotesStatsReporter;
+
/**
* The allowed refresh rate switching type. This is used by SurfaceFlinger.
*/
@@ -204,6 +207,8 @@
mContext = context;
mHandler = new DisplayModeDirectorHandler(handler.getLooper());
mInjector = injector;
+ mVotesStatsReporter = injector.getVotesStatsReporter(
+ displayManagerFlags.isRefreshRateVotingTelemetryEnabled());
mSupportedModesByDisplay = new SparseArray<>();
mDefaultModeByDisplay = new SparseArray<>();
mAppRequestObserver = new AppRequestObserver();
@@ -214,7 +219,8 @@
mBrightnessObserver = new BrightnessObserver(context, handler, injector);
mDefaultDisplayDeviceConfig = null;
mUdfpsObserver = new UdfpsObserver();
- mVotesStorage = new VotesStorage(this::notifyDesiredDisplayModeSpecsChangedLocked);
+ mVotesStorage = new VotesStorage(this::notifyDesiredDisplayModeSpecsChangedLocked,
+ mVotesStatsReporter);
mDisplayObserver = new DisplayObserver(context, handler, mVotesStorage);
mSensorObserver = new SensorObserver(context, mVotesStorage, injector);
mSkinThermalStatusObserver = new SkinThermalStatusObserver(injector, mVotesStorage);
@@ -341,6 +347,11 @@
appRequestSummary.limitRefreshRanges(primarySummary);
Display.Mode baseMode = primarySummary.selectBaseMode(availableModes, defaultMode);
+ if (mVotesStatsReporter != null) {
+ mVotesStatsReporter.reportVotesActivated(displayId, lowestConsideredPriority,
+ baseMode, votes);
+ }
+
if (baseMode == null) {
Slog.w(TAG, "Can't find a set of allowed modes which satisfies the votes. Falling"
+ " back to the default mode. Display = " + displayId + ", votes = " + votes
@@ -970,9 +981,14 @@
if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled) {
// The flag had been turned off, we need to restore the original value
- Settings.System.putFloatForUser(cr,
- Settings.System.MIN_REFRESH_RATE, minRefreshRate, cr.getUserId());
+ Settings.System.putFloatForUser(cr, Settings.System.MIN_REFRESH_RATE,
+ highestRefreshRate, cr.getUserId());
}
+ } else if (mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled
+ && Math.round(minRefreshRate) == Math.round(highestRefreshRate)) {
+ // The flag has been turned on, we need to upgrade the setting
+ Settings.System.putFloatForUser(cr, Settings.System.MIN_REFRESH_RATE,
+ Float.POSITIVE_INFINITY, cr.getUserId());
}
float peakRefreshRate = Settings.System.getFloatForUser(cr,
@@ -983,9 +999,14 @@
if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled) {
// The flag had been turned off, we need to restore the original value
- Settings.System.putFloatForUser(cr,
- Settings.System.PEAK_REFRESH_RATE, peakRefreshRate, cr.getUserId());
+ Settings.System.putFloatForUser(cr, Settings.System.PEAK_REFRESH_RATE,
+ highestRefreshRate, cr.getUserId());
}
+ } else if (mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled
+ && Math.round(peakRefreshRate) == Math.round(highestRefreshRate)) {
+ // The flag has been turned on, we need to upgrade the setting
+ Settings.System.putFloatForUser(cr, Settings.System.PEAK_REFRESH_RATE,
+ Float.POSITIVE_INFINITY, cr.getUserId());
}
updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate, mDefaultRefreshRate);
@@ -2811,6 +2832,9 @@
StatusBarManagerInternal getStatusBarManagerInternal();
SensorManagerInternal getSensorManagerInternal();
+
+ @Nullable
+ VotesStatsReporter getVotesStatsReporter(boolean refreshRateVotingTelemetryEnabled);
}
@VisibleForTesting
@@ -2943,6 +2967,13 @@
return LocalServices.getService(SensorManagerInternal.class);
}
+ @Override
+ public VotesStatsReporter getVotesStatsReporter(boolean refreshRateVotingTelemetryEnabled) {
+ // if frame rate override supported, renderRates will be ignored in mode selection
+ return new VotesStatsReporter(supportsFrameRateOverride(),
+ refreshRateVotingTelemetryEnabled);
+ }
+
private DisplayManager getDisplayManager() {
if (mDisplayManager == null) {
mDisplayManager = mContext.getSystemService(DisplayManager.class);
diff --git a/services/core/java/com/android/server/display/mode/VotesStatsReporter.java b/services/core/java/com/android/server/display/mode/VotesStatsReporter.java
new file mode 100644
index 0000000..a30c4d2
--- /dev/null
+++ b/services/core/java/com/android/server/display/mode/VotesStatsReporter.java
@@ -0,0 +1,93 @@
+/*
+ * 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.server.display.mode;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Trace;
+import android.util.SparseArray;
+import android.view.Display;
+
+/**
+ * The VotesStatsReporter is responsible for collecting and sending Vote related statistics
+ */
+class VotesStatsReporter {
+ private static final String TAG = "VotesStatsReporter";
+ private static final int REFRESH_RATE_NOT_LIMITED = 1000;
+ private final boolean mIgnoredRenderRate;
+ private final boolean mFrameworkStatsLogReportingEnabled;
+
+ public VotesStatsReporter(boolean ignoreRenderRate, boolean refreshRateVotingTelemetryEnabled) {
+ mIgnoredRenderRate = ignoreRenderRate;
+ mFrameworkStatsLogReportingEnabled = refreshRateVotingTelemetryEnabled;
+ }
+
+ void reportVoteAdded(int displayId, int priority, @NonNull Vote vote) {
+ int maxRefreshRate = getMaxRefreshRate(vote, mIgnoredRenderRate);
+ Trace.traceCounter(Trace.TRACE_TAG_POWER,
+ TAG + "." + displayId + ":" + Vote.priorityToString(priority), maxRefreshRate);
+ // if ( mFrameworkStatsLogReportingEnabled) {
+ // FrameworkStatsLog.write(VOTE_CHANGED, displayID, priority, ADDED, maxRefreshRate, -1);
+ // }
+ }
+
+ void reportVoteRemoved(int displayId, int priority) {
+ Trace.traceCounter(Trace.TRACE_TAG_POWER,
+ TAG + "." + displayId + ":" + Vote.priorityToString(priority), -1);
+ // if ( mFrameworkStatsLogReportingEnabled) {
+ // FrameworkStatsLog.write(VOTE_CHANGED, displayID, priority, REMOVED, -1, -1);
+ // }
+ }
+
+ void reportVotesActivated(int displayId, int minPriority, @Nullable Display.Mode baseMode,
+ SparseArray<Vote> votes) {
+// if (!mFrameworkStatsLogReportingEnabled) {
+// return;
+// }
+// int selectedRefreshRate = baseMode != null ? (int) baseMode.getRefreshRate() : -1;
+// for (int priority = minPriority; priority <= Vote.MAX_PRIORITY; priority ++) {
+// Vote vote = votes.get(priority);
+// if (vote != null) {
+// int maxRefreshRate = getMaxRefreshRate(vote, mIgnoredRenderRate);
+// FrameworkStatsLog.write(VOTE_CHANGED, displayId, priority,
+// ACTIVE, maxRefreshRate, selectedRefreshRate);
+// }
+// }
+ }
+
+ private static int getMaxRefreshRate(@NonNull Vote vote, boolean ignoreRenderRate) {
+ int maxRefreshRate = REFRESH_RATE_NOT_LIMITED;
+ if (vote instanceof RefreshRateVote.PhysicalVote physicalVote) {
+ maxRefreshRate = (int) physicalVote.mMaxRefreshRate;
+ } else if (!ignoreRenderRate && (vote instanceof RefreshRateVote.RenderVote renderVote)) {
+ maxRefreshRate = (int) renderVote.mMaxRefreshRate;
+ } else if (vote instanceof SupportedModesVote supportedModesVote) {
+ // SupportedModesVote limits mode by specific refreshRates, so highest rr is allowed
+ maxRefreshRate = 0;
+ for (SupportedModesVote.SupportedMode mode : supportedModesVote.mSupportedModes) {
+ maxRefreshRate = Math.max(maxRefreshRate, (int) mode.mPeakRefreshRate);
+ }
+ } else if (vote instanceof CombinedVote combinedVote) {
+ for (Vote subVote: combinedVote.mVotes) {
+ // CombinedVote should not have CombinedVote in mVotes, so recursion depth will be 1
+ maxRefreshRate = Math.min(maxRefreshRate,
+ getMaxRefreshRate(subVote, ignoreRenderRate));
+ }
+ }
+ return maxRefreshRate;
+ }
+}
diff --git a/services/core/java/com/android/server/display/mode/VotesStorage.java b/services/core/java/com/android/server/display/mode/VotesStorage.java
index 95fb8fc..7a1f7e9 100644
--- a/services/core/java/com/android/server/display/mode/VotesStorage.java
+++ b/services/core/java/com/android/server/display/mode/VotesStorage.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.os.Trace;
import android.util.Slog;
import android.util.SparseArray;
@@ -38,6 +37,9 @@
private final Listener mListener;
+ @Nullable
+ private final VotesStatsReporter mVotesStatsReporter;
+
private final Object mStorageLock = new Object();
// A map from the display ID to the collection of votes and their priority. The latter takes
// the form of another map from the priority to the vote itself so that each priority is
@@ -45,8 +47,9 @@
@GuardedBy("mStorageLock")
private final SparseArray<SparseArray<Vote>> mVotesByDisplay = new SparseArray<>();
- VotesStorage(@NonNull Listener listener) {
+ VotesStorage(@NonNull Listener listener, @Nullable VotesStatsReporter votesStatsReporter) {
mListener = listener;
+ mVotesStatsReporter = votesStatsReporter;
}
/** sets logging enabled/disabled for this class */
void setLoggingEnabled(boolean loggingEnabled) {
@@ -110,17 +113,26 @@
changed = true;
}
}
- Trace.traceCounter(Trace.TRACE_TAG_POWER,
- TAG + "." + displayId + ":" + Vote.priorityToString(priority),
- getMaxPhysicalRefreshRate(vote));
if (mLoggingEnabled) {
Slog.i(TAG, "Updated votes for display=" + displayId + " votes=" + votes);
}
if (changed) {
+ reportVoteStats(displayId, priority, vote);
mListener.onChanged();
}
}
+ private void reportVoteStats(int displayId, int priority, @Nullable Vote vote) {
+ if (mVotesStatsReporter == null) {
+ return;
+ }
+ if (vote == null) {
+ mVotesStatsReporter.reportVoteRemoved(displayId, priority);
+ } else {
+ mVotesStatsReporter.reportVoteAdded(displayId, priority, vote);
+ }
+ }
+
/** dump class values, for debugging */
void dump(@NonNull PrintWriter pw) {
SparseArray<SparseArray<Vote>> votesByDisplayLocal = new SparseArray<>();
@@ -157,21 +169,6 @@
}
}
- private static int getMaxPhysicalRefreshRate(@Nullable Vote vote) {
- if (vote == null) {
- return -1;
- } else if (vote instanceof RefreshRateVote.PhysicalVote physicalVote) {
- return (int) physicalVote.mMaxRefreshRate;
- } else if (vote instanceof CombinedVote combinedVote) {
- return combinedVote.mVotes.stream()
- .filter(v -> v instanceof RefreshRateVote.PhysicalVote)
- .map(pv -> (int) (((RefreshRateVote.PhysicalVote) pv).mMaxRefreshRate))
- .min(Integer::compare)
- .orElse(1000); // for visualisation
- }
- return 1000; // for visualisation, otherwise e.g. -1 -> 60 will be unnoticeable
- }
-
interface Listener {
void onChanged();
}
diff --git a/services/core/java/com/android/server/inputmethod/ClientController.java b/services/core/java/com/android/server/inputmethod/ClientController.java
new file mode 100644
index 0000000..2934640
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/ClientController.java
@@ -0,0 +1,162 @@
+/*
+ * 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.inputmethod;
+
+import android.annotation.NonNull;
+import android.content.pm.PackageManagerInternal;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.SparseArray;
+import android.view.inputmethod.InputBinding;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.IInputMethodClient;
+import com.android.internal.inputmethod.IRemoteInputConnection;
+
+/**
+ * Store and manage {@link InputMethodManagerService} clients. This class was designed to be a
+ * singleton in {@link InputMethodManagerService} since it stores information about all clients,
+ * still the current client will be defined per display.
+ *
+ * <p>
+ * As part of the re-architecture plan (described in go/imms-rearchitecture-plan), the following
+ * fields and methods will be moved out from IMMS and placed here:
+ * <ul>
+ * <li>mCurClient (ClientState)</li>
+ * <li>mClients (ArrayMap of ClientState indexed by IBinder)</li>
+ * <li>mLastSwitchUserId</li>
+ * </ul>
+ * <p>
+ * Nested Classes (to move from IMMS):
+ * <ul>
+ * <li>ClientDeathRecipient</li>
+ * <li>ClientState<</li>
+ * </ul>
+ * <p>
+ * Methods to rewrite and/or extract from IMMS and move here:
+ * <ul>
+ * <li>addClient</li>
+ * <li>removeClient</li>
+ * <li>verifyClientAndPackageMatch</li>
+ * <li>setImeTraceEnabledForAllClients (make it reactive)</li>
+ * <li>unbindCurrentClient</li>
+ * </ul>
+ */
+// TODO(b/314150112): Update the Javadoc above, by removing the re-architecture steps, once this
+// class is finalized
+final class ClientController {
+
+ // TODO(b/314150112): Make this field private when breaking the cycle with IMMS.
+ @GuardedBy("ImfLock.class")
+ final ArrayMap<IBinder, ClientState> mClients = new ArrayMap<>();
+
+ private final PackageManagerInternal mPackageManagerInternal;
+
+ ClientController(PackageManagerInternal packageManagerInternal) {
+ mPackageManagerInternal = packageManagerInternal;
+ }
+
+ @GuardedBy("ImfLock.class")
+ void addClient(IInputMethodClientInvoker clientInvoker,
+ IRemoteInputConnection inputConnection,
+ int selfReportedDisplayId, IBinder.DeathRecipient deathRecipient, int callerUid,
+ int callerPid) {
+ // TODO: Optimize this linear search.
+ final int numClients = mClients.size();
+ for (int i = 0; i < numClients; ++i) {
+ final ClientState state = mClients.valueAt(i);
+ if (state.mUid == callerUid && state.mPid == callerPid
+ && state.mSelfReportedDisplayId == selfReportedDisplayId) {
+ throw new SecurityException("uid=" + callerUid + "/pid=" + callerPid
+ + "/displayId=" + selfReportedDisplayId + " is already registered");
+ }
+ }
+ try {
+ clientInvoker.asBinder().linkToDeath(deathRecipient, 0 /* flags */);
+ } catch (RemoteException e) {
+ throw new IllegalStateException(e);
+ }
+ // We cannot fully avoid race conditions where the client UID already lost the access to
+ // the given self-reported display ID, even if the client is not maliciously reporting
+ // a fake display ID. Unconditionally returning SecurityException just because the
+ // client doesn't pass display ID verification can cause many test failures hence not an
+ // option right now. At the same time
+ // context.getSystemService(InputMethodManager.class)
+ // is expected to return a valid non-null instance at any time if we do not choose to
+ // have the client crash. Thus we do not verify the display ID at all here. Instead we
+ // later check the display ID every time the client needs to interact with the specified
+ // display.
+ mClients.put(clientInvoker.asBinder(), new ClientState(clientInvoker, inputConnection,
+ callerUid, callerPid, selfReportedDisplayId, deathRecipient));
+ }
+
+ @GuardedBy("ImfLock.class")
+ boolean verifyClientAndPackageMatch(
+ @NonNull IInputMethodClient client, @NonNull String packageName) {
+ ClientState cs = mClients.get(client.asBinder());
+ if (cs == null) {
+ throw new IllegalArgumentException("unknown client " + client.asBinder());
+ }
+ return InputMethodUtils.checkIfPackageBelongsToUid(
+ mPackageManagerInternal, cs.mUid, packageName);
+ }
+
+ static final class ClientState {
+ final IInputMethodClientInvoker mClient;
+ final IRemoteInputConnection mFallbackInputConnection;
+ final int mUid;
+ final int mPid;
+ final int mSelfReportedDisplayId;
+ final InputBinding mBinding;
+ final IBinder.DeathRecipient mClientDeathRecipient;
+
+ @GuardedBy("ImfLock.class")
+ boolean mSessionRequested;
+
+ @GuardedBy("ImfLock.class")
+ boolean mSessionRequestedForAccessibility;
+
+ @GuardedBy("ImfLock.class")
+ InputMethodManagerService.SessionState mCurSession;
+
+ @GuardedBy("ImfLock.class")
+ SparseArray<InputMethodManagerService.AccessibilitySessionState> mAccessibilitySessions =
+ new SparseArray<>();
+
+ @Override
+ public String toString() {
+ return "ClientState{" + Integer.toHexString(
+ System.identityHashCode(this)) + " mUid=" + mUid
+ + " mPid=" + mPid + " mSelfReportedDisplayId=" + mSelfReportedDisplayId + "}";
+ }
+
+ ClientState(IInputMethodClientInvoker client,
+ IRemoteInputConnection fallbackInputConnection,
+ int uid, int pid, int selfReportedDisplayId,
+ IBinder.DeathRecipient clientDeathRecipient) {
+ mClient = client;
+ mFallbackInputConnection = fallbackInputConnection;
+ mUid = uid;
+ mPid = pid;
+ mSelfReportedDisplayId = selfReportedDisplayId;
+ mBinding = new InputBinding(null /*conn*/, mFallbackInputConnection.asBinder(), mUid,
+ mPid);
+ mClientDeathRecipient = clientDeathRecipient;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java b/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
index f0e4b0f5..898d5a5 100644
--- a/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
+++ b/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
@@ -19,6 +19,8 @@
import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.util.ArrayMap;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
@@ -33,9 +35,26 @@
@GuardedBy("ImfLock.class")
private final ArrayList<InputMethodSubtypeHandle> mSubtypeHandles = new ArrayList<>();
+ @UserIdInt
+ private final int mUserId;
+
+ @AnyThread
+ @UserIdInt
+ int getUserId() {
+ return mUserId;
+ }
+
+ HardwareKeyboardShortcutController(
+ @NonNull ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId) {
+ mUserId = userId;
+ reset(methodMap);
+ }
+
@GuardedBy("ImfLock.class")
- void reset(@NonNull InputMethodUtils.InputMethodSettings settings) {
+ void reset(@NonNull ArrayMap<String, InputMethodInfo> methodMap) {
mSubtypeHandles.clear();
+ final InputMethodUtils.InputMethodSettings settings =
+ new InputMethodUtils.InputMethodSettings(methodMap, mUserId);
for (final InputMethodInfo imi : settings.getEnabledInputMethodListLocked()) {
if (!imi.shouldShowInInputMethodPicker()) {
continue;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 24bcb4e..25ec683 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.inputmethod;
+import static android.content.Context.DEVICE_ID_DEFAULT;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
import static android.os.IServiceManager.DUMP_FLAG_PROTO;
@@ -47,6 +48,7 @@
import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE;
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
+import static com.android.server.inputmethod.ClientController.ClientState;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeTargetWindowState;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeVisibilityResult;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
@@ -126,7 +128,6 @@
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.Flags;
import android.view.inputmethod.ImeTracker;
-import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceFileProto;
@@ -272,13 +273,15 @@
@NonNull
private final String[] mNonPreemptibleInputMethods;
+ // TODO(b/314150112): Move this to ClientController.
@UserIdInt
private int mLastSwitchUserId;
final Context mContext;
final Resources mRes;
private final Handler mHandler;
- private final InputMethodSettings mSettings;
+ @NonNull
+ private InputMethodSettings mSettings;
final SettingsObserver mSettingsObserver;
private final SparseBooleanArray mLoggedDeniedGetInputMethodWindowVisibleHeightForUid =
new SparseBooleanArray(0);
@@ -315,12 +318,17 @@
// All known input methods.
final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>();
private final ArrayMap<String, InputMethodInfo> mMethodMap = new ArrayMap<>();
+
// Mapping from deviceId to the device-specific imeId for that device.
+ @GuardedBy("ImfLock.class")
private final SparseArray<String> mVirtualDeviceMethodMap = new SparseArray<>();
- private final InputMethodSubtypeSwitchingController mSwitchingController;
- final HardwareKeyboardShortcutController mHardwareKeyboardShortcutController =
- new HardwareKeyboardShortcutController();
+ // TODO: Instantiate mSwitchingController for each user.
+ @NonNull
+ private InputMethodSubtypeSwitchingController mSwitchingController;
+ // TODO: Instantiate mHardwareKeyboardShortcutController for each user.
+ @NonNull
+ private HardwareKeyboardShortcutController mHardwareKeyboardShortcutController;
/**
* Tracks how many times {@link #mMethodMap} was updated.
@@ -339,6 +347,9 @@
@GuardedBy("ImfLock.class")
private int mDisplayIdToShowIme = INVALID_DISPLAY;
+ @GuardedBy("ImfLock.class")
+ private int mDeviceIdToShowIme = DEVICE_ID_DEFAULT;
+
@Nullable private StatusBarManagerInternal mStatusBarManagerInternal;
private boolean mShowOngoingImeSwitcherForPhones;
@GuardedBy("ImfLock.class")
@@ -383,7 +394,7 @@
/**
* Record session state for an accessibility service.
*/
- private static class AccessibilitySessionState {
+ static class AccessibilitySessionState {
final ClientState mClient;
// Id of the accessibility service.
final int mId;
@@ -407,58 +418,10 @@
}
}
- private static final class ClientDeathRecipient implements IBinder.DeathRecipient {
- private final InputMethodManagerService mImms;
- private final IInputMethodClient mClient;
-
- ClientDeathRecipient(InputMethodManagerService imms, IInputMethodClient client) {
- mImms = imms;
- mClient = client;
- }
-
- @Override
- public void binderDied() {
- mImms.removeClient(mClient);
- }
- }
-
- static final class ClientState {
- final IInputMethodClientInvoker mClient;
- final IRemoteInputConnection mFallbackInputConnection;
- final int mUid;
- final int mPid;
- final int mSelfReportedDisplayId;
- final InputBinding mBinding;
- final ClientDeathRecipient mClientDeathRecipient;
-
- boolean mSessionRequested;
- boolean mSessionRequestedForAccessibility;
- SessionState mCurSession;
- SparseArray<AccessibilitySessionState> mAccessibilitySessions = new SparseArray<>();
-
- @Override
- public String toString() {
- return "ClientState{" + Integer.toHexString(
- System.identityHashCode(this)) + " mUid=" + mUid
- + " mPid=" + mPid + " mSelfReportedDisplayId=" + mSelfReportedDisplayId + "}";
- }
-
- ClientState(IInputMethodClientInvoker client,
- IRemoteInputConnection fallbackInputConnection,
- int uid, int pid, int selfReportedDisplayId,
- ClientDeathRecipient clientDeathRecipient) {
- mClient = client;
- mFallbackInputConnection = fallbackInputConnection;
- mUid = uid;
- mPid = pid;
- mSelfReportedDisplayId = selfReportedDisplayId;
- mBinding = new InputBinding(null, mFallbackInputConnection.asBinder(), mUid, mPid);
- mClientDeathRecipient = clientDeathRecipient;
- }
- }
-
- @GuardedBy("ImfLock.class")
- final ArrayMap<IBinder, ClientState> mClients = new ArrayMap<>();
+ /**
+ * Manages the IME clients.
+ */
+ private final ClientController mClientController;
/**
* Set once the system is ready to run third party code.
@@ -516,6 +479,7 @@
/**
* The client that is currently bound to an input method.
*/
+ // TODO(b/314150112): Move this to ClientController.
@Nullable
private ClientState mCurClient;
@@ -856,8 +820,9 @@
@Nullable
final String mImeSurfaceParentName;
- Entry(ClientState client, EditorInfo editorInfo, String focusedWindowName,
- @SoftInputModeFlags int softInputMode, @SoftInputShowHideReason int reason,
+ Entry(ClientState client, EditorInfo editorInfo,
+ String focusedWindowName, @SoftInputModeFlags int softInputMode,
+ @SoftInputShowHideReason int reason,
boolean inFullscreenMode, String requestWindowName,
@Nullable String imeControlTargetName, @Nullable String imeTargetName,
@Nullable String imeSurfaceParentName) {
@@ -1621,7 +1586,7 @@
if (userId != currentUserId) {
return;
}
- mSettings.switchCurrentUser(currentUserId);
+ mSettings = new InputMethodSettings(mMethodMap, userId);
if (mSystemReady) {
// We need to rebuild IMEs.
buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
@@ -1699,8 +1664,10 @@
AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId);
mSwitchingController =
- InputMethodSubtypeSwitchingController.createInstanceLocked(mSettings, context);
- mHardwareKeyboardShortcutController.reset(mSettings);
+ InputMethodSubtypeSwitchingController.createInstanceLocked(context, mMethodMap,
+ userId);
+ mHardwareKeyboardShortcutController =
+ new HardwareKeyboardShortcutController(mMethodMap, userId);
mMenuController = new InputMethodMenuController(this);
mBindingController =
bindingControllerForTesting != null
@@ -1710,6 +1677,7 @@
mVisibilityStateComputer = new ImeVisibilityStateComputer(this);
mVisibilityApplier = new DefaultImeVisibilityApplier(this);
+ mClientController = new ClientController(mPackageManagerInternal);
mPreventImeStartupUnlessTextEditor = mRes.getBoolean(
com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor);
@@ -1821,11 +1789,7 @@
// ContentObserver should be registered again when the user is changed
mSettingsObserver.registerContentObserverLocked(newUserId);
- // If the system is not ready or the device is not yed unlocked by the user, then we use
- // copy-on-write settings.
- final boolean useCopyOnWriteSettings =
- !mSystemReady || !mUserManagerInternal.isUserUnlockingOrUnlocked(newUserId);
- mSettings.switchCurrentUser(newUserId);
+ mSettings = new InputMethodSettings(mMethodMap, newUserId);
// Additional subtypes should be reset when the user is changed
AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, newUserId);
final String defaultImiId = mSettings.getSelectedInputMethod();
@@ -1867,7 +1831,8 @@
mLastSwitchUserId = newUserId;
if (mIsInteractive && clientToBeReset != null) {
- final ClientState cs = mClients.get(clientToBeReset.asBinder());
+ final ClientState cs =
+ mClientController.mClients.get(clientToBeReset.asBinder());
if (cs == null) {
// The client is already gone.
return;
@@ -1887,7 +1852,6 @@
if (!mSystemReady) {
mSystemReady = true;
final int currentUserId = mSettings.getCurrentUserId();
- mSettings.switchCurrentUser(currentUserId);
mStatusBarManagerInternal =
LocalServices.getService(StatusBarManagerInternal.class);
hideStatusBarIconLocked();
@@ -2205,43 +2169,22 @@
// actually running.
final int callerUid = Binder.getCallingUid();
final int callerPid = Binder.getCallingPid();
+
+ // TODO(b/314150112): Move the death recipient logic to ClientController when moving
+ // removeClient method.
+ final IBinder.DeathRecipient deathRecipient = () -> removeClient(client);
+ final IInputMethodClientInvoker clientInvoker =
+ IInputMethodClientInvoker.create(client, mHandler);
synchronized (ImfLock.class) {
- // TODO: Optimize this linear search.
- final int numClients = mClients.size();
- for (int i = 0; i < numClients; ++i) {
- final ClientState state = mClients.valueAt(i);
- if (state.mUid == callerUid && state.mPid == callerPid
- && state.mSelfReportedDisplayId == selfReportedDisplayId) {
- throw new SecurityException("uid=" + callerUid + "/pid=" + callerPid
- + "/displayId=" + selfReportedDisplayId + " is already registered.");
- }
- }
- final ClientDeathRecipient deathRecipient = new ClientDeathRecipient(this, client);
- try {
- client.asBinder().linkToDeath(deathRecipient, 0 /* flags */);
- } catch (RemoteException e) {
- throw new IllegalStateException(e);
- }
- // We cannot fully avoid race conditions where the client UID already lost the access to
- // the given self-reported display ID, even if the client is not maliciously reporting
- // a fake display ID. Unconditionally returning SecurityException just because the
- // client doesn't pass display ID verification can cause many test failures hence not an
- // option right now. At the same time
- // context.getSystemService(InputMethodManager.class)
- // is expected to return a valid non-null instance at any time if we do not choose to
- // have the client crash. Thus we do not verify the display ID at all here. Instead we
- // later check the display ID every time the client needs to interact with the specified
- // display.
- final IInputMethodClientInvoker clientInvoker =
- IInputMethodClientInvoker.create(client, mHandler);
- mClients.put(client.asBinder(), new ClientState(clientInvoker, inputConnection,
- callerUid, callerPid, selfReportedDisplayId, deathRecipient));
+ mClientController.addClient(clientInvoker, inputConnection, selfReportedDisplayId,
+ deathRecipient, callerUid, callerPid);
}
}
+ // TODO(b/314150112): Move this to ClientController.
void removeClient(IInputMethodClient client) {
synchronized (ImfLock.class) {
- ClientState cs = mClients.remove(client.asBinder());
+ ClientState cs = mClientController.mClients.remove(client.asBinder());
if (cs != null) {
client.asBinder().unlinkToDeath(cs.mClientDeathRecipient, 0 /* flags */);
clearClientSessionLocked(cs);
@@ -2271,6 +2214,7 @@
}
}
+ // TODO(b/314150112): Move this to ClientController.
@GuardedBy("ImfLock.class")
void unbindCurrentClientLocked(@UnbindReason int unbindClientReason) {
if (mCurClient != null) {
@@ -2323,7 +2267,10 @@
}
}
- /** {@code true} when a {@link ClientState} has attached from starting the input connection. */
+ /**
+ * {@code true} when a {@link ClientState} has attached from starting the
+ * input connection.
+ */
@GuardedBy("ImfLock.class")
boolean hasAttachedClient() {
return mCurClient != null;
@@ -2464,11 +2411,7 @@
@StartInputReason int startInputReason,
int unverifiedTargetSdkVersion,
@NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
- // If no method is currently selected, do nothing.
- final String selectedMethodId = getSelectedMethodIdLocked();
- if (selectedMethodId == null) {
- return InputBindResult.NO_IME;
- }
+ String selectedMethodId = getSelectedMethodIdLocked();
if (!mSystemReady) {
// If the system is not yet ready, we shouldn't be running third
@@ -2493,8 +2436,21 @@
return InputBindResult.NOT_IME_TARGET_WINDOW;
}
final int csDisplayId = cs.mSelfReportedDisplayId;
+ final int oldDisplayIdToShowIme = mDisplayIdToShowIme;
mDisplayIdToShowIme = mVisibilityStateComputer.computeImeDisplayId(winState, csDisplayId);
+ // Potentially override the selected input method if the new display belongs to a virtual
+ // device with a custom IME.
+ if (oldDisplayIdToShowIme != mDisplayIdToShowIme) {
+ final String deviceMethodId = computeCurrentDeviceMethodIdLocked(selectedMethodId);
+ if (deviceMethodId == null) {
+ mVisibilityStateComputer.getImePolicy().setImeHiddenByDisplayPolicy(true);
+ } else if (!Objects.equals(deviceMethodId, selectedMethodId)) {
+ setInputMethodLocked(deviceMethodId, NOT_A_SUBTYPE_ID, mDeviceIdToShowIme);
+ selectedMethodId = deviceMethodId;
+ }
+ }
+
if (mVisibilityStateComputer.getImePolicy().isImeHiddenByDisplayPolicy()) {
hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
null /* resultReceiver */,
@@ -2502,6 +2458,11 @@
return InputBindResult.NO_IME;
}
+ // If no method is currently selected, do nothing.
+ if (selectedMethodId == null) {
+ return InputBindResult.NO_IME;
+ }
+
if (mCurClient != cs) {
prepareClientSwitchLocked(cs);
}
@@ -2568,6 +2529,62 @@
return mBindingController.bindCurrentMethod();
}
+ /**
+ * Update the current deviceId and return the relevant imeId for this device.
+ * 1. If the device changes to virtual and its custom IME is not available, then disable IME.
+ * 2. If the device changes to virtual with valid custom IME, then return the custom IME. If
+ * the old device was default, then store the current imeId so it can be restored.
+ * 3. If the device changes to default, restore the default device IME.
+ * 4. Otherwise keep the current imeId.
+ */
+ @GuardedBy("ImfLock.class")
+ private String computeCurrentDeviceMethodIdLocked(String currentMethodId) {
+ if (mVdmInternal == null) {
+ mVdmInternal = LocalServices.getService(VirtualDeviceManagerInternal.class);
+ }
+ if (mVdmInternal == null || !android.companion.virtual.flags.Flags.vdmCustomIme()) {
+ return currentMethodId;
+ }
+
+ final int oldDeviceId = mDeviceIdToShowIme;
+ mDeviceIdToShowIme = mVdmInternal.getDeviceIdForDisplayId(mDisplayIdToShowIme);
+ if (mDeviceIdToShowIme == oldDeviceId) {
+ return currentMethodId;
+ }
+ if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
+ final String defaultDeviceMethodId = mSettings.getSelectedDefaultDeviceInputMethod();
+ if (DEBUG) {
+ Slog.v(TAG, "Restoring default device input method: " + defaultDeviceMethodId);
+ }
+ return defaultDeviceMethodId;
+ }
+
+ final String deviceMethodId =
+ mVirtualDeviceMethodMap.get(mDeviceIdToShowIme, currentMethodId);
+ if (Objects.equals(deviceMethodId, currentMethodId)) {
+ return currentMethodId;
+ } else if (!mMethodMap.containsKey(deviceMethodId)) {
+ if (DEBUG) {
+ Slog.v(TAG, "Disabling IME on virtual device with id " + mDeviceIdToShowIme
+ + " because its custom input method is not available: " + deviceMethodId);
+ }
+ return null;
+ }
+
+ if (oldDeviceId == DEVICE_ID_DEFAULT) {
+ if (DEBUG) {
+ Slog.v(TAG, "Storing default device input method " + currentMethodId);
+ }
+ mSettings.putSelectedDefaultDeviceInputMethod(currentMethodId);
+ }
+ if (DEBUG) {
+ Slog.v(TAG, "Switching current input method from " + currentMethodId
+ + " to device-specific one " + deviceMethodId + " because the current display "
+ + mDisplayIdToShowIme + " belongs to device with id " + mDeviceIdToShowIme);
+ }
+ return deviceMethodId;
+ }
+
@GuardedBy("ImfLock.class")
void invalidateAutofillSessionLocked() {
mAutofillController.invalidateAutofillSession();
@@ -2897,10 +2914,10 @@
@GuardedBy("ImfLock.class")
void clearClientSessionsLocked() {
if (getCurMethodLocked() != null) {
- final int numClients = mClients.size();
+ final int numClients = mClientController.mClients.size();
for (int i = 0; i < numClients; ++i) {
- clearClientSessionLocked(mClients.valueAt(i));
- clearClientSessionForAccessibilityLocked(mClients.valueAt(i));
+ clearClientSessionLocked(mClientController.mClients.valueAt(i));
+ clearClientSessionForAccessibilityLocked(mClientController.mClients.valueAt(i));
}
finishSessionLocked(mEnabledSession);
@@ -3218,13 +3235,21 @@
// There is no longer an input method set, so stop any current one.
resetCurrentMethodAndClientLocked(UnbindReason.NO_IME);
}
- // Here is not the perfect place to reset the switching controller. Ideally
- // mSwitchingController and mSettings should be able to share the same state.
- // TODO: Make sure that mSwitchingController and mSettings are sharing the
- // the same enabled IMEs list.
- mSwitchingController.resetCircularListLocked(mContext);
- mHardwareKeyboardShortcutController.reset(mSettings);
+ // TODO: Instantiate mSwitchingController for each user.
+ if (mSettings.getCurrentUserId() == mSwitchingController.getUserId()) {
+ mSwitchingController.resetCircularListLocked(mMethodMap);
+ } else {
+ mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
+ mContext, mMethodMap, mSettings.getCurrentUserId());
+ }
+ // TODO: Instantiate mHardwareKeyboardShortcutController for each user.
+ if (mSettings.getCurrentUserId() == mHardwareKeyboardShortcutController.getUserId()) {
+ mHardwareKeyboardShortcutController.reset(mMethodMap);
+ } else {
+ mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController(
+ mMethodMap, mSettings.getCurrentUserId());
+ }
sendOnNavButtonFlagsChangedLocked();
}
@@ -3242,6 +3267,11 @@
@GuardedBy("ImfLock.class")
void setInputMethodLocked(String id, int subtypeId) {
+ setInputMethodLocked(id, subtypeId, DEVICE_ID_DEFAULT);
+ }
+
+ @GuardedBy("ImfLock.class")
+ void setInputMethodLocked(String id, int subtypeId, int deviceId) {
InputMethodInfo info = mMethodMap.get(id);
if (info == null) {
throw getExceptionForUnknownImeId(id);
@@ -3285,6 +3315,14 @@
}
// Changing to a different IME.
+ if (mDeviceIdToShowIme != DEVICE_ID_DEFAULT && deviceId == DEVICE_ID_DEFAULT) {
+ // This change should only be applicable to the default device but the current input
+ // method is a custom one specific to a virtual device. So only update the settings
+ // entry used to restore the default device input method once we want to show the IME
+ // back on the default device.
+ mSettings.putSelectedDefaultDeviceInputMethod(id);
+ return;
+ }
IInputMethodInvoker curMethod = getCurMethodLocked();
if (curMethod != null) {
curMethod.removeStylusHandwritingWindow();
@@ -3414,9 +3452,12 @@
+ " pref is disabled for user: " + userId);
return;
}
- if (!verifyClientAndPackageMatch(client, delegatorPackageName)) {
- Slog.w(TAG, "prepareStylusHandwritingDelegation() fail");
- throw new IllegalArgumentException("Delegator doesn't match Uid");
+ synchronized (ImfLock.class) {
+ if (!mClientController.verifyClientAndPackageMatch(client,
+ delegatorPackageName)) {
+ Slog.w(TAG, "prepareStylusHandwritingDelegation() fail");
+ throw new IllegalArgumentException("Delegator doesn't match Uid");
+ }
}
schedulePrepareStylusHandwritingDelegation(
userId, delegatePackageName, delegatorPackageName);
@@ -3442,30 +3483,17 @@
return true;
}
- private boolean verifyClientAndPackageMatch(
- @NonNull IInputMethodClient client, @NonNull String packageName) {
- ClientState cs;
- synchronized (ImfLock.class) {
- cs = mClients.get(client.asBinder());
- }
- if (cs == null) {
- throw new IllegalArgumentException("unknown client " + client.asBinder());
- }
- return InputMethodUtils.checkIfPackageBelongsToUid(
- mPackageManagerInternal, cs.mUid, packageName);
- }
-
private boolean verifyDelegator(
@NonNull IInputMethodClient client,
@NonNull String delegatePackageName,
@NonNull String delegatorPackageName,
@InputMethodManager.HandwritingDelegateFlags int flags) {
- if (!verifyClientAndPackageMatch(client, delegatePackageName)) {
- Slog.w(TAG, "Delegate package does not belong to the same user. Ignoring"
- + " startStylusHandwriting");
- return false;
- }
synchronized (ImfLock.class) {
+ if (!mClientController.verifyClientAndPackageMatch(client, delegatePackageName)) {
+ Slog.w(TAG, "Delegate package does not belong to the same user. Ignoring"
+ + " startStylusHandwriting");
+ return false;
+ }
boolean homeDelegatorAllowed =
(flags & InputMethodManager.HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED)
!= 0;
@@ -3728,7 +3756,7 @@
return InputBindResult.INVALID_USER;
}
- final ClientState cs = mClients.get(client.asBinder());
+ final ClientState cs = mClientController.mClients.get(client.asBinder());
if (cs == null) {
throw new IllegalArgumentException("unknown client " + client.asBinder());
}
@@ -3902,7 +3930,8 @@
// We need to check if this is the current client with
// focus in the window manager, to allow this call to
// be made before input is started in it.
- final ClientState cs = mClients.get(client.asBinder());
+ final ClientState cs =
+ mClientController.mClients.get(client.asBinder());
if (cs == null) {
ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
throw new IllegalArgumentException("unknown client " + client.asBinder());
@@ -4526,7 +4555,7 @@
ImeTracing.getInstance().startTrace(null /* printwriter */);
ArrayMap<IBinder, ClientState> clients;
synchronized (ImfLock.class) {
- clients = new ArrayMap<>(mClients);
+ clients = new ArrayMap<>(mClientController.mClients);
}
for (ClientState state : clients.values()) {
if (state != null) {
@@ -4544,7 +4573,7 @@
ImeTracing.getInstance().stopTrace(null /* printwriter */);
ArrayMap<IBinder, ClientState> clients;
synchronized (ImfLock.class) {
- clients = new ArrayMap<>(mClients);
+ clients = new ArrayMap<>(mClientController.mClients);
}
for (ClientState state : clients.values()) {
if (state != null) {
@@ -4598,6 +4627,9 @@
}
return;
}
+ if (mSettings.getCurrentUserId() != mSwitchingController.getUserId()) {
+ return;
+ }
final InputMethodInfo imi = mMethodMap.get(getSelectedMethodIdLocked());
if (imi != null) {
mSwitchingController.onUserActionLocked(imi, mCurrentSubtype);
@@ -4835,9 +4867,10 @@
int lastInputMethodSubtypeId =
mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId);
- final List<ImeSubtypeListItem> imList = mSwitchingController
- .getSortedInputMethodAndSubtypeListForImeMenuLocked(
- showAuxSubtypes, isScreenLocked);
+ final List<ImeSubtypeListItem> imList = InputMethodSubtypeSwitchingController
+ .getSortedInputMethodAndSubtypeList(
+ showAuxSubtypes, isScreenLocked, false, mContext,
+ mMethodMap, mSettings.getCurrentUserId());
mMenuController.showInputMethodMenuLocked(showAuxSubtypes, displayId,
lastInputMethodId, lastInputMethodSubtypeId, imList);
}
@@ -5222,12 +5255,20 @@
updateDefaultVoiceImeIfNeededLocked();
- // Here is not the perfect place to reset the switching controller. Ideally
- // mSwitchingController and mSettings should be able to share the same state.
- // TODO: Make sure that mSwitchingController and mSettings are sharing the
- // the same enabled IMEs list.
- mSwitchingController.resetCircularListLocked(mContext);
- mHardwareKeyboardShortcutController.reset(mSettings);
+ // TODO: Instantiate mSwitchingController for each user.
+ if (mSettings.getCurrentUserId() == mSwitchingController.getUserId()) {
+ mSwitchingController.resetCircularListLocked(mMethodMap);
+ } else {
+ mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
+ mContext, mMethodMap, mSettings.getCurrentUserId());
+ }
+ // TODO: Instantiate mHardwareKeyboardShortcutController for each user.
+ if (mSettings.getCurrentUserId() == mHardwareKeyboardShortcutController.getUserId()) {
+ mHardwareKeyboardShortcutController.reset(mMethodMap);
+ } else {
+ mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController(
+ mMethodMap, mSettings.getCurrentUserId());
+ }
sendOnNavButtonFlagsChangedLocked();
@@ -5308,11 +5349,21 @@
StringBuilder builder = new StringBuilder();
if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
builder, enabledInputMethodsList, id)) {
- // Disabled input method is currently selected, switch to another one.
- final String selId = mSettings.getSelectedInputMethod();
- if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
- Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
- resetSelectedInputMethodAndSubtypeLocked("");
+ if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
+ // Disabled input method is currently selected, switch to another one.
+ final String selId = mSettings.getSelectedInputMethod();
+ if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
+ Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
+ resetSelectedInputMethodAndSubtypeLocked("");
+ }
+ } else if (id.equals(mSettings.getSelectedDefaultDeviceInputMethod())) {
+ // Disabled default device IME while using a virtual device one, choose a
+ // new default one but only update the settings.
+ InputMethodInfo newDefaultIme =
+ InputMethodInfoUtils.getMostApplicableDefaultIME(
+ mSettings.getEnabledInputMethodListLocked());
+ mSettings.putSelectedDefaultDeviceInputMethod(
+ newDefaultIme == null ? "" : newDefaultIme.getId());
}
// Previous state was enabled.
return true;
@@ -5652,9 +5703,8 @@
@Override
public void setVirtualDeviceInputMethodForAllUsers(int deviceId, @Nullable String imeId) {
- // TODO(b/287269288): validate that id belongs to a valid virtual device instead.
- Preconditions.checkArgument(deviceId != Context.DEVICE_ID_DEFAULT,
- "DeviceId " + deviceId + " does not belong to a virtual device.");
+ Preconditions.checkArgument(deviceId != DEVICE_ID_DEFAULT,
+ TextUtils.formatSimple("DeviceId %d is not a virtual device id.", deviceId));
synchronized (ImfLock.class) {
if (imeId == null) {
mVirtualDeviceMethodMap.remove(deviceId);
@@ -5768,10 +5818,10 @@
// We only have sessions when we bound to an input method. Remove this session
// from all clients.
if (getCurMethodLocked() != null) {
- final int numClients = mClients.size();
+ final int numClients = mClientController.mClients.size();
for (int i = 0; i < numClients; ++i) {
- clearClientSessionForAccessibilityLocked(mClients.valueAt(i),
- accessibilityConnectionId);
+ clearClientSessionForAccessibilityLocked(
+ mClientController.mClients.valueAt(i), accessibilityConnectionId);
}
AccessibilitySessionState session = mEnabledAccessibilitySessions.get(
accessibilityConnectionId);
@@ -5956,9 +6006,10 @@
info.dump(p, " ");
}
p.println(" ClientStates:");
- final int numClients = mClients.size();
+ // TODO(b/314150112): move client related dump info to ClientController#dump
+ final int numClients = mClientController.mClients.size();
for (int i = 0; i < numClients; ++i) {
- final ClientState ci = mClients.valueAt(i);
+ final ClientState ci = mClientController.mClients.valueAt(i);
p.println(" " + ci + ":");
p.println(" client=" + ci.mClient);
p.println(" fallbackInputConnection=" + ci.mFallbackInputConnection);
@@ -6577,7 +6628,7 @@
boolean isImeTraceEnabled = ImeTracing.getInstance().isEnabled();
ArrayMap<IBinder, ClientState> clients;
synchronized (ImfLock.class) {
- clients = new ArrayMap<>(mClients);
+ clients = new ArrayMap<>(mClientController.mClients);
}
for (ClientState state : clients.values()) {
if (state != null) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
index 431aabd..4439b06 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -16,10 +16,14 @@
package com.android.server.inputmethod;
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.content.Context;
-import android.content.pm.PackageManager;
+import android.os.UserHandle;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Printer;
import android.util.Slog;
@@ -32,7 +36,6 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.Locale;
import java.util.Objects;
/**
@@ -154,79 +157,71 @@
}
}
- private static class InputMethodAndSubtypeList {
- private final Context mContext;
- // Used to load label
- private final PackageManager mPm;
- private final String mSystemLocaleStr;
- private final InputMethodSettings mSettings;
+ static List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList(
+ boolean includeAuxiliarySubtypes, boolean isScreenLocked, boolean forImeMenu,
+ @NonNull Context context, @NonNull ArrayMap<String, InputMethodInfo> methodMap,
+ @UserIdInt int userId) {
+ final Context userAwareContext = context.getUserId() == userId
+ ? context
+ : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
+ final String mSystemLocaleStr = SystemLocaleWrapper.get(userId).get(0).toLanguageTag();
+ final InputMethodSettings settings = new InputMethodSettings(methodMap, userId);
- InputMethodAndSubtypeList(Context context, InputMethodSettings settings) {
- mContext = context;
- mSettings = settings;
- mPm = context.getPackageManager();
- final Locale locale = context.getResources().getConfiguration().locale;
- mSystemLocaleStr = locale != null ? locale.toString() : "";
+ final ArrayList<InputMethodInfo> imis = settings.getEnabledInputMethodListLocked();
+ if (imis.isEmpty()) {
+ return Collections.emptyList();
}
-
- public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList(
- boolean includeAuxiliarySubtypes, boolean isScreenLocked, boolean forImeMenu) {
- final ArrayList<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
- if (imis.isEmpty()) {
- return Collections.emptyList();
+ if (isScreenLocked && includeAuxiliarySubtypes) {
+ if (DEBUG) {
+ Slog.w(TAG, "Auxiliary subtypes are not allowed to be shown in lock screen.");
}
- if (isScreenLocked && includeAuxiliarySubtypes) {
+ includeAuxiliarySubtypes = false;
+ }
+ final ArrayList<ImeSubtypeListItem> imList = new ArrayList<>();
+ final int numImes = imis.size();
+ for (int i = 0; i < numImes; ++i) {
+ final InputMethodInfo imi = imis.get(i);
+ if (forImeMenu && !imi.shouldShowInInputMethodPicker()) {
+ continue;
+ }
+ final List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList =
+ settings.getEnabledInputMethodSubtypeListLocked(imi, true);
+ final ArraySet<String> enabledSubtypeSet = new ArraySet<>();
+ for (InputMethodSubtype subtype : explicitlyOrImplicitlyEnabledSubtypeList) {
+ enabledSubtypeSet.add(String.valueOf(subtype.hashCode()));
+ }
+ final CharSequence imeLabel = imi.loadLabel(userAwareContext.getPackageManager());
+ if (enabledSubtypeSet.size() > 0) {
+ final int subtypeCount = imi.getSubtypeCount();
if (DEBUG) {
- Slog.w(TAG, "Auxiliary subtypes are not allowed to be shown in lock screen.");
+ Slog.v(TAG, "Add subtypes: " + subtypeCount + ", " + imi.getId());
}
- includeAuxiliarySubtypes = false;
- }
- final ArrayList<ImeSubtypeListItem> imList = new ArrayList<>();
- final int numImes = imis.size();
- for (int i = 0; i < numImes; ++i) {
- final InputMethodInfo imi = imis.get(i);
- if (forImeMenu && !imi.shouldShowInInputMethodPicker()) {
- continue;
- }
- final List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList =
- mSettings.getEnabledInputMethodSubtypeListLocked(imi, true);
- final ArraySet<String> enabledSubtypeSet = new ArraySet<>();
- for (InputMethodSubtype subtype : explicitlyOrImplicitlyEnabledSubtypeList) {
- enabledSubtypeSet.add(String.valueOf(subtype.hashCode()));
- }
- final CharSequence imeLabel = imi.loadLabel(mPm);
- if (enabledSubtypeSet.size() > 0) {
- final int subtypeCount = imi.getSubtypeCount();
- if (DEBUG) {
- Slog.v(TAG, "Add subtypes: " + subtypeCount + ", " + imi.getId());
- }
- for (int j = 0; j < subtypeCount; ++j) {
- final InputMethodSubtype subtype = imi.getSubtypeAt(j);
- final String subtypeHashCode = String.valueOf(subtype.hashCode());
- // We show all enabled IMEs and subtypes when an IME is shown.
- if (enabledSubtypeSet.contains(subtypeHashCode)
- && (includeAuxiliarySubtypes || !subtype.isAuxiliary())) {
- final CharSequence subtypeLabel =
- subtype.overridesImplicitlyEnabledSubtype() ? null : subtype
- .getDisplayName(mContext, imi.getPackageName(),
- imi.getServiceInfo().applicationInfo);
- imList.add(new ImeSubtypeListItem(imeLabel,
- subtypeLabel, imi, j, subtype.getLocale(), mSystemLocaleStr));
+ for (int j = 0; j < subtypeCount; ++j) {
+ final InputMethodSubtype subtype = imi.getSubtypeAt(j);
+ final String subtypeHashCode = String.valueOf(subtype.hashCode());
+ // We show all enabled IMEs and subtypes when an IME is shown.
+ if (enabledSubtypeSet.contains(subtypeHashCode)
+ && (includeAuxiliarySubtypes || !subtype.isAuxiliary())) {
+ final CharSequence subtypeLabel =
+ subtype.overridesImplicitlyEnabledSubtype() ? null : subtype
+ .getDisplayName(userAwareContext, imi.getPackageName(),
+ imi.getServiceInfo().applicationInfo);
+ imList.add(new ImeSubtypeListItem(imeLabel,
+ subtypeLabel, imi, j, subtype.getLocale(), mSystemLocaleStr));
- // Removing this subtype from enabledSubtypeSet because we no
- // longer need to add an entry of this subtype to imList to avoid
- // duplicated entries.
- enabledSubtypeSet.remove(subtypeHashCode);
- }
+ // Removing this subtype from enabledSubtypeSet because we no
+ // longer need to add an entry of this subtype to imList to avoid
+ // duplicated entries.
+ enabledSubtypeSet.remove(subtypeHashCode);
}
- } else {
- imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_ID, null,
- mSystemLocaleStr));
}
+ } else {
+ imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_ID, null,
+ mSystemLocaleStr));
}
- Collections.sort(imList);
- return imList;
}
+ Collections.sort(imList);
+ return imList;
}
private static int calculateSubtypeId(InputMethodInfo imi, InputMethodSubtype subtype) {
@@ -479,18 +474,32 @@
}
}
- private final InputMethodSettings mSettings;
- private InputMethodAndSubtypeList mSubtypeList;
+ private final Context mContext;
+ @UserIdInt
+ private final int mUserId;
private ControllerImpl mController;
- private InputMethodSubtypeSwitchingController(InputMethodSettings settings, Context context) {
- mSettings = settings;
- resetCircularListLocked(context);
+ private InputMethodSubtypeSwitchingController(@NonNull Context context,
+ @NonNull ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId) {
+ mContext = context;
+ mUserId = userId;
+ mController = ControllerImpl.createFrom(null,
+ getSortedInputMethodAndSubtypeList(
+ false /* includeAuxiliarySubtypes */, false /* isScreenLocked */,
+ false /* forImeMenu */, context, methodMap, userId));
}
+ @NonNull
public static InputMethodSubtypeSwitchingController createInstanceLocked(
- InputMethodSettings settings, Context context) {
- return new InputMethodSubtypeSwitchingController(settings, context);
+ @NonNull Context context,
+ @NonNull ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId) {
+ return new InputMethodSubtypeSwitchingController(context, methodMap, userId);
+ }
+
+ @AnyThread
+ @UserIdInt
+ int getUserId() {
+ return mUserId;
}
public void onUserActionLocked(InputMethodInfo imi, InputMethodSubtype subtype) {
@@ -503,12 +512,12 @@
mController.onUserActionLocked(imi, subtype);
}
- public void resetCircularListLocked(Context context) {
- mSubtypeList = new InputMethodAndSubtypeList(context, mSettings);
+ public void resetCircularListLocked(
+ @NonNull ArrayMap<String, InputMethodInfo> methodMap) {
mController = ControllerImpl.createFrom(mController,
- mSubtypeList.getSortedInputMethodAndSubtypeList(
+ getSortedInputMethodAndSubtypeList(
false /* includeAuxiliarySubtypes */, false /* isScreenLocked */,
- false /* forImeMenu */));
+ false /* forImeMenu */, mContext, methodMap, mUserId));
}
public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, InputMethodInfo imi,
@@ -522,12 +531,6 @@
return mController.getNextInputMethod(onlyCurrentIme, imi, subtype);
}
- public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeListForImeMenuLocked(
- boolean includingAuxiliarySubtypes, boolean isScreenLocked) {
- return mSubtypeList.getSortedInputMethodAndSubtypeList(
- includingAuxiliarySubtypes, isScreenLocked, true /* forImeMenu */);
- }
-
public void dump(final Printer pw) {
if (mController != null) {
mController.dump(pw);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index a0b55ed..fb57c09 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -51,6 +51,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.StringJoiner;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -214,7 +215,7 @@
private final ArrayMap<String, InputMethodInfo> mMethodMap;
@UserIdInt
- private int mCurrentUserId;
+ private final int mCurrentUserId;
private static void buildEnabledInputMethodsSettingString(
StringBuilder builder, Pair<String, ArrayList<String>> ime) {
@@ -228,19 +229,13 @@
InputMethodSettings(ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId) {
mMethodMap = methodMap;
- switchCurrentUser(userId);
- }
-
- /**
- * Must be called when the current user is changed.
- *
- * @param userId The user ID.
- */
- void switchCurrentUser(@UserIdInt int userId) {
- if (DEBUG) {
- Slog.d(TAG, "--- Switch the current user from " + mCurrentUserId + " to " + userId);
- }
mCurrentUserId = userId;
+ String ime = getSelectedInputMethod();
+ String defaultDeviceIme = getSelectedDefaultDeviceInputMethod();
+ if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
+ putSelectedInputMethod(defaultDeviceIme);
+ putSelectedDefaultDeviceInputMethod(null);
+ }
}
private void putString(@NonNull String key, @Nullable String str) {
@@ -636,6 +631,24 @@
return imi;
}
+ @Nullable
+ String getSelectedDefaultDeviceInputMethod() {
+ final String imi = getString(Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null);
+ if (DEBUG) {
+ Slog.d(TAG, "getSelectedDefaultDeviceInputMethodStr: " + imi + ", "
+ + mCurrentUserId);
+ }
+ return imi;
+ }
+
+ void putSelectedDefaultDeviceInputMethod(String imeId) {
+ if (DEBUG) {
+ Slog.d(TAG, "putSelectedDefaultDeviceInputMethodStr: " + imeId + ", "
+ + mCurrentUserId);
+ }
+ putString(Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, imeId);
+ }
+
void putDefaultVoiceInputMethod(String imeId) {
if (DEBUG) {
Slog.d(TAG, "putDefaultVoiceInputMethodStr: " + imeId + ", " + mCurrentUserId);
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index 07b333a..393e7ef 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -198,7 +198,12 @@
mIsConnected = true;
service = mService;
}
- service.onSessionActiveStateChanged(MediaSession2Record.this);
+
+ // TODO (b/318745416): Add support for FGS in MediaSession2. Passing a
+ // null playback state means the owning process will not be allowed to
+ // run in the foreground.
+ service.onSessionActiveStateChanged(MediaSession2Record.this,
+ /* playbackState= */ null);
}
@Override
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index cce66e2..53f780e 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -1144,7 +1144,7 @@
mIsActive = active;
long token = Binder.clearCallingIdentity();
try {
- mService.onSessionActiveStateChanged(MediaSessionRecord.this);
+ mService.onSessionActiveStateChanged(MediaSessionRecord.this, mPlaybackState);
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 2affdfc..2cd3ab1 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -260,7 +260,8 @@
return mGlobalPrioritySession != null && mGlobalPrioritySession.isActive();
}
- void onSessionActiveStateChanged(MediaSessionRecordImpl record) {
+ void onSessionActiveStateChanged(
+ MediaSessionRecordImpl record, @Nullable PlaybackState playbackState) {
synchronized (mLock) {
FullUserRecord user = getFullUserRecordLocked(record.getUserId());
if (user == null) {
@@ -287,7 +288,9 @@
user.mPriorityStack.onSessionActiveStateChanged(record);
}
setForegroundServiceAllowance(
- record, /* allowRunningInForeground= */ record.isActive());
+ record,
+ /* allowRunningInForeground= */ record.isActive()
+ && (playbackState == null || playbackState.isActive()));
mHandler.postSessionsChanged(record);
}
}
@@ -386,7 +389,9 @@
user.mPriorityStack.onPlaybackStateChanged(record, shouldUpdatePriority);
if (playbackState != null) {
setForegroundServiceAllowance(
- record, playbackState.shouldAllowServiceToRunInForeground());
+ record,
+ /* allowRunningInForeground= */ playbackState.isActive()
+ && record.isActive());
}
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index 6b7db2d..a6f71c2 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -94,8 +94,6 @@
private static final float DEFAULT_VOLUME = 1.0f;
// TODO (b/291899544): remove for release
- private static final String POLITE_STRATEGY1 = "rule1";
- private static final String POLITE_STRATEGY2 = "rule2";
private static final int DEFAULT_NOTIFICATION_COOLDOWN_ENABLED = 1;
private static final int DEFAULT_NOTIFICATION_COOLDOWN_ENABLED_FOR_WORK = 0;
private static final int DEFAULT_NOTIFICATION_COOLDOWN_ALL = 1;
@@ -146,7 +144,6 @@
private boolean mNotificationCooldownApplyToAll;
private boolean mNotificationCooldownVibrateUnlocked;
- private boolean mEnablePoliteNotificationsFeature;
private final PolitenessStrategy mStrategy;
private int mCurrentWorkProfileId = UserHandle.USER_NULL;
@@ -192,9 +189,7 @@
.build();
mInCallNotificationVolume = resources.getFloat(R.dimen.config_inCallNotificationVolume);
- mEnablePoliteNotificationsFeature = Flags.politeNotifications();
-
- if (mEnablePoliteNotificationsFeature) {
+ if (Flags.politeNotifications()) {
mStrategy = getPolitenessStrategy();
} else {
mStrategy = null;
@@ -205,21 +200,23 @@
}
private PolitenessStrategy getPolitenessStrategy() {
- final String politenessStrategy = mFlagResolver.getStringValue(
- NotificationFlags.NOTIF_COOLDOWN_RULE);
-
- if (POLITE_STRATEGY2.equals(politenessStrategy)) {
- return new Strategy2(mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
+ if (Flags.crossAppPoliteNotifications()) {
+ PolitenessStrategy appStrategy = new StrategyPerApp(
+ mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
- mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2));
- } else {
- if (!POLITE_STRATEGY1.equals(politenessStrategy)) {
- Log.w(TAG, "Invalid cooldown strategy: " + politenessStrategy + ". Defaulting to "
- + POLITE_STRATEGY1);
- }
+ mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
+ mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET));
- return new Strategy1(mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
+ return new StrategyGlobal(
+ mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
+ mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2),
+ mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
+ mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
+ appStrategy);
+ } else {
+ return new StrategyPerApp(
+ mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
@@ -266,7 +263,7 @@
mContext.getContentResolver().registerContentObserver(
SettingsObserver.NOTIFICATION_LIGHT_PULSE_URI, false, mSettingsObserver,
UserHandle.USER_ALL);
- if (mEnablePoliteNotificationsFeature) {
+ if (Flags.politeNotifications()) {
mContext.getContentResolver().registerContentObserver(
SettingsObserver.NOTIFICATION_COOLDOWN_ENABLED_URI, false, mSettingsObserver,
UserHandle.USER_ALL);
@@ -280,7 +277,7 @@
}
private void loadUserSettings() {
- if (mEnablePoliteNotificationsFeature) {
+ if (Flags.politeNotifications()) {
try {
mCurrentWorkProfileId = getManagedProfileId(ActivityManager.getCurrentUser());
@@ -301,11 +298,14 @@
mContext.getContentResolver(),
Settings.System.NOTIFICATION_COOLDOWN_ALL, DEFAULT_NOTIFICATION_COOLDOWN_ALL,
UserHandle.USER_CURRENT) != 0;
- mNotificationCooldownVibrateUnlocked = Settings.System.getIntForUser(
- mContext.getContentResolver(),
- Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
- DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
- UserHandle.USER_CURRENT) != 0;
+ mStrategy.setApplyCooldownPerPackage(mNotificationCooldownApplyToAll);
+ if (Flags.vibrateWhileUnlocked()) {
+ mNotificationCooldownVibrateUnlocked = Settings.System.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
+ DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
+ UserHandle.USER_CURRENT) != 0;
+ }
} catch (Exception e) {
Log.e(TAG, "Failed to read Settings: " + e);
}
@@ -482,10 +482,10 @@
getPolitenessState(record));
}
record.setAudiblyAlerted(buzz || beep);
- if (mEnablePoliteNotificationsFeature) {
+ if (Flags.politeNotifications()) {
// Update last alert time
if (buzz || beep) {
- record.getChannel().setLastNotificationUpdateTimeMs(System.currentTimeMillis());
+ mStrategy.setLastNotificationUpdateTimeMs(record, System.currentTimeMillis());
}
}
return buzzBeepBlinkLoggingCode;
@@ -618,7 +618,7 @@
private boolean isPoliteNotificationFeatureEnabled(final NotificationRecord record) {
// Check feature flag
- if (!mEnablePoliteNotificationsFeature) {
+ if (!Flags.politeNotifications()) {
return false;
}
@@ -1064,9 +1064,13 @@
// Volume for muted state
protected final float mVolumeMuted;
+ protected boolean mApplyPerPackage;
+ protected final Map<String, Long> mLastUpdatedTimestampByPackage;
+
public PolitenessStrategy(int timeoutPolite, int timeoutMuted, int volumePolite,
int volumeMuted) {
mVolumeStates = new HashMap<>();
+ mLastUpdatedTimestampByPackage = new HashMap<>();
this.mTimeoutPolite = timeoutPolite;
this.mTimeoutMuted = timeoutMuted;
@@ -1076,10 +1080,38 @@
abstract void onNotificationPosted(NotificationRecord record);
+ /**
+ * Set true if the cooldown strategy should apply per app(package).
+ * Otherwise apply per conversation channel.
+ * @param applyPerPackage if the cooldown should be applied per app
+ */
+ void setApplyCooldownPerPackage(boolean applyPerPackage) {
+ mApplyPerPackage = applyPerPackage;
+ }
+
+ boolean shouldIgnoreNotification(final NotificationRecord record) {
+ // Ignore group summaries
+ return (record.getSbn().isGroup() && record.getSbn().getNotification()
+ .isGroupSummary());
+ }
+
+ /**
+ * Get the key that determines the grouping for the cooldown behavior.
+ *
+ * @param record the notification being posted
+ * @return the key to group this notification under
+ */
String getChannelKey(final NotificationRecord record) {
- // use conversationId if it's a conversation
+ // Use conversationId if it's a conversation
String channelId = record.getChannel().getConversationId() != null
? record.getChannel().getConversationId() : record.getChannel().getId();
+
+ // Use only the package name to apply cooldown per app, unless the user explicitly
+ // changed the channel notification sound => treat separately
+ if (mApplyPerPackage && !record.getChannel().hasUserSetSound()) {
+ channelId = "";
+ }
+
return record.getSbn().getNormalizedUserId() + ":" + record.getSbn().getPackageName()
+ ":" + channelId;
}
@@ -1121,12 +1153,59 @@
final String key = getChannelKey(record);
// reset to default state after user interaction
mVolumeStates.put(key, POLITE_STATE_DEFAULT);
- record.getChannel().setLastNotificationUpdateTimeMs(0);
+ setLastNotificationUpdateTimeMs(record, 0);
}
public final @PolitenessState int getPolitenessState(final NotificationRecord record) {
return mVolumeStates.getOrDefault(getChannelKey(record), POLITE_STATE_DEFAULT);
}
+
+ void setLastNotificationUpdateTimeMs(final NotificationRecord record,
+ long timestampMillis) {
+ record.getChannel().setLastNotificationUpdateTimeMs(timestampMillis);
+ mLastUpdatedTimestampByPackage.put(record.getSbn().getPackageName(), timestampMillis);
+ }
+
+ long getLastNotificationUpdateTimeMs(final NotificationRecord record) {
+ if (record.getChannel().hasUserSetSound() || !mApplyPerPackage) {
+ return record.getChannel().getLastNotificationUpdateTimeMs();
+ } else {
+ return mLastUpdatedTimestampByPackage.getOrDefault(record.getSbn().getPackageName(),
+ 0L);
+ }
+ }
+
+ @PolitenessState int getNextState(@PolitenessState final int currState,
+ final long timeSinceLastNotif) {
+ @PolitenessState int nextState = currState;
+ switch (currState) {
+ case POLITE_STATE_DEFAULT:
+ if (timeSinceLastNotif < mTimeoutPolite) {
+ nextState = POLITE_STATE_POLITE;
+ }
+ break;
+ case POLITE_STATE_POLITE:
+ if (timeSinceLastNotif < mTimeoutMuted) {
+ nextState = POLITE_STATE_MUTED;
+ } else if (timeSinceLastNotif > mTimeoutPolite) {
+ nextState = POLITE_STATE_DEFAULT;
+ } else {
+ nextState = POLITE_STATE_POLITE;
+ }
+ break;
+ case POLITE_STATE_MUTED:
+ if (timeSinceLastNotif > mTimeoutMuted) {
+ nextState = POLITE_STATE_POLITE;
+ } else {
+ nextState = POLITE_STATE_MUTED;
+ }
+ break;
+ default:
+ Log.w(TAG, "getNextState unexpected volume state: " + currState);
+ break;
+ }
+ return nextState;
+ }
}
// TODO b/270456865: Only one of the two strategies will be released.
@@ -1143,72 +1222,51 @@
* after timeoutMuted.
* - Transitions back to the default state after a user interaction with a notification.
*/
- public static class Strategy1 extends PolitenessStrategy {
+ private static class StrategyPerApp extends PolitenessStrategy {
// Keep track of the number of notifications posted per channel
private final Map<String, Integer> mNumPosted;
// Reset to default state if number of posted notifications exceed this value when muted
private final int mMaxPostedForReset;
- public Strategy1(int timeoutPolite, int timeoutMuted, int volumePolite, int volumeMuted,
- int maxPosted) {
+ public StrategyPerApp(int timeoutPolite, int timeoutMuted, int volumePolite,
+ int volumeMuted, int maxPosted) {
super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted);
mNumPosted = new HashMap<>();
mMaxPostedForReset = maxPosted;
if (DEBUG) {
- Log.i(TAG, "Strategy1: " + timeoutPolite + " " + timeoutMuted);
+ Log.i(TAG, "StrategyPerApp: " + timeoutPolite + " " + timeoutMuted);
}
}
@Override
public void onNotificationPosted(final NotificationRecord record) {
- long timeSinceLastNotif = System.currentTimeMillis()
- - record.getChannel().getLastNotificationUpdateTimeMs();
+ if (shouldIgnoreNotification(record)) {
+ return;
+ }
+
+ long timeSinceLastNotif =
+ System.currentTimeMillis() - getLastNotificationUpdateTimeMs(record);
final String key = getChannelKey(record);
- @PolitenessState int volState = getPolitenessState(record);
+ @PolitenessState final int currState = getPolitenessState(record);
+ @PolitenessState int nextState = getNextState(currState, timeSinceLastNotif);
+ // Reset to default state if number of posted notifications exceed this value when muted
int numPosted = mNumPosted.getOrDefault(key, 0) + 1;
mNumPosted.put(key, numPosted);
-
- switch (volState) {
- case POLITE_STATE_DEFAULT:
- if (timeSinceLastNotif < mTimeoutPolite) {
- volState = POLITE_STATE_POLITE;
- }
- break;
- case POLITE_STATE_POLITE:
- if (timeSinceLastNotif < mTimeoutMuted) {
- volState = POLITE_STATE_MUTED;
- } else if (timeSinceLastNotif > mTimeoutPolite) {
- volState = POLITE_STATE_DEFAULT;
- } else {
- volState = POLITE_STATE_POLITE;
- }
- break;
- case POLITE_STATE_MUTED:
- if (timeSinceLastNotif > mTimeoutMuted) {
- volState = POLITE_STATE_POLITE;
- } else {
- volState = POLITE_STATE_MUTED;
- }
- if (numPosted >= mMaxPostedForReset) {
- volState = POLITE_STATE_DEFAULT;
- mNumPosted.put(key, 0);
- }
- break;
- default:
- Log.w(TAG, "onNotificationPosted unexpected volume state: " + volState);
- break;
+ if (currState == POLITE_STATE_MUTED && numPosted >= mMaxPostedForReset) {
+ nextState = POLITE_STATE_DEFAULT;
+ mNumPosted.put(key, 0);
}
if (DEBUG) {
Log.i(TAG, "onNotificationPosted time delta: " + timeSinceLastNotif + " vol state: "
- + volState + " key: " + key + " numposted " + numPosted);
+ + nextState + " key: " + key + " numposted " + numPosted);
}
- mVolumeStates.put(key, volState);
+ mVolumeStates.put(key, nextState);
}
@Override
@@ -1219,61 +1277,98 @@
}
/**
- * Polite notification strategy 2:
- * - Transitions from default (loud) => muted state if a notification
- * alerts the same channel before timeoutPolite.
- * - Transitions from polite => default state if a notification
- * alerts the same channel before timeoutMuted.
- * - Transitions from muted => default state if a notification alerts after timeoutMuted,
- * otherwise transitions to the polite state.
- * - Transitions back to the default state after a user interaction with a notification.
+ * Global (cross-app) strategy.
*/
- public static class Strategy2 extends PolitenessStrategy {
- public Strategy2(int timeoutPolite, int timeoutMuted, int volumePolite, int volumeMuted) {
+ private static class StrategyGlobal extends PolitenessStrategy {
+ private static final String COMMON_KEY = "cross_app_common_key";
+
+ private final PolitenessStrategy mAppStrategy;
+ private long mLastNotificationTimestamp = 0;
+
+ public StrategyGlobal(int timeoutPolite, int timeoutMuted, int volumePolite,
+ int volumeMuted, PolitenessStrategy appStrategy) {
super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted);
+ mAppStrategy = appStrategy;
+
if (DEBUG) {
- Log.i(TAG, "Strategy2: " + timeoutPolite + " " + timeoutMuted);
+ Log.i(TAG, "StrategyGlobal: " + timeoutPolite + " " + timeoutMuted);
}
}
@Override
- public void onNotificationPosted(final NotificationRecord record) {
- long timeSinceLastNotif = System.currentTimeMillis()
- - record.getChannel().getLastNotificationUpdateTimeMs();
+ void onNotificationPosted(NotificationRecord record) {
+ if (shouldIgnoreNotification(record)) {
+ return;
+ }
+
+ long timeSinceLastNotif =
+ System.currentTimeMillis() - getLastNotificationUpdateTimeMs(record);
final String key = getChannelKey(record);
- @PolitenessState int volState = getPolitenessState(record);
-
- switch (volState) {
- case POLITE_STATE_DEFAULT:
- if (timeSinceLastNotif < mTimeoutPolite) {
- volState = POLITE_STATE_MUTED;
- }
- break;
- case POLITE_STATE_POLITE:
- if (timeSinceLastNotif > mTimeoutMuted) {
- volState = POLITE_STATE_DEFAULT;
- }
- break;
- case POLITE_STATE_MUTED:
- if (timeSinceLastNotif > mTimeoutMuted) {
- volState = POLITE_STATE_DEFAULT;
- } else {
- volState = POLITE_STATE_POLITE;
- }
- break;
- default:
- Log.w(TAG, "onNotificationPosted unexpected volume state: " + volState);
- break;
- }
+ @PolitenessState final int currState = getPolitenessState(record);
+ @PolitenessState int nextState = getNextState(currState, timeSinceLastNotif);
if (DEBUG) {
- Log.i(TAG, "onNotificationPosted time delta: " + timeSinceLastNotif + " vol state: "
- + volState + " key: " + key);
+ Log.i(TAG, "StrategyGlobal onNotificationPosted time delta: " + timeSinceLastNotif
+ + " vol state: " + nextState + " key: " + key);
}
- mVolumeStates.put(key, volState);
+ mVolumeStates.put(key, nextState);
+
+ mAppStrategy.onNotificationPosted(record);
+ }
+
+ @Override
+ public float getSoundVolume(final NotificationRecord record) {
+ final @PolitenessState int globalVolState = getPolitenessState(record);
+ final @PolitenessState int appVolState = mAppStrategy.getPolitenessState(record);
+
+ // Prioritize the most polite outcome
+ if (globalVolState > appVolState) {
+ return super.getSoundVolume(record);
+ } else {
+ return mAppStrategy.getSoundVolume(record);
+ }
+ }
+
+ @Override
+ public void onUserInteraction(final NotificationRecord record) {
+ super.onUserInteraction(record);
+ mAppStrategy.onUserInteraction(record);
+ }
+
+ @Override
+ String getChannelKey(final NotificationRecord record) {
+ // If the user explicitly changed the channel notification sound:
+ // handle as a separate channel
+ if (record.getChannel().hasUserSetSound()) {
+ return super.getChannelKey(record);
+ } else {
+ // Use one global key per user
+ return record.getSbn().getNormalizedUserId() + ":" + COMMON_KEY;
+ }
+ }
+
+ @Override
+ public void setLastNotificationUpdateTimeMs(NotificationRecord record,
+ long timestampMillis) {
+ super.setLastNotificationUpdateTimeMs(record, timestampMillis);
+ mLastNotificationTimestamp = timestampMillis;
+ }
+
+ long getLastNotificationUpdateTimeMs(final NotificationRecord record) {
+ if (record.getChannel().hasUserSetSound()) {
+ return super.getLastNotificationUpdateTimeMs(record);
+ } else {
+ return mLastNotificationTimestamp;
+ }
+ }
+
+ @Override
+ void setApplyCooldownPerPackage(boolean applyPerPackage) {
+ super.setApplyCooldownPerPackage(applyPerPackage);
+ mAppStrategy.setApplyCooldownPerPackage(applyPerPackage);
}
}
@@ -1338,7 +1433,7 @@
updateLightsLocked();
}
}
- if (mEnablePoliteNotificationsFeature) {
+ if (Flags.politeNotifications()) {
if (NOTIFICATION_COOLDOWN_ENABLED_URI.equals(uri)) {
mNotificationCooldownEnabled = Settings.System.getIntForUser(
mContext.getContentResolver(),
@@ -1363,13 +1458,16 @@
Settings.System.NOTIFICATION_COOLDOWN_ALL,
DEFAULT_NOTIFICATION_COOLDOWN_ALL, UserHandle.USER_CURRENT)
!= 0;
+ mStrategy.setApplyCooldownPerPackage(mNotificationCooldownApplyToAll);
}
- if (NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED_URI.equals(uri)) {
- mNotificationCooldownVibrateUnlocked = Settings.System.getIntForUser(
+ if (Flags.vibrateWhileUnlocked()) {
+ if (NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED_URI.equals(uri)) {
+ mNotificationCooldownVibrateUnlocked = Settings.System.getIntForUser(
mContext.getContentResolver(),
Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
UserHandle.USER_CURRENT) != 0;
+ }
}
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationHistoryManager.java b/services/core/java/com/android/server/notification/NotificationHistoryManager.java
index 6a46048..e3880c3 100644
--- a/services/core/java/com/android/server/notification/NotificationHistoryManager.java
+++ b/services/core/java/com/android/server/notification/NotificationHistoryManager.java
@@ -118,6 +118,10 @@
}
}
+ public void onUserAdded(@UserIdInt int userId) {
+ mSettingsObserver.update(null, userId);
+ }
+
public void onUserStopped(@UserIdInt int userId) {
synchronized (mLock) {
mUserUnlockedStates.put(userId, false);
@@ -401,9 +405,7 @@
false, this, UserHandle.USER_ALL);
synchronized (mLock) {
for (UserInfo userInfo : mUserManager.getUsers()) {
- if (!userInfo.isProfile()) {
- update(null, userInfo.id);
- }
+ update(null, userInfo.id);
}
}
}
@@ -424,10 +426,7 @@
boolean historyEnabled = Settings.Secure.getIntForUser(resolver,
Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, userId)
!= 0;
- int[] profiles = mUserManager.getProfileIds(userId, true);
- for (int profileId : profiles) {
- onHistoryEnabledChanged(profileId, historyEnabled);
- }
+ onHistoryEnabledChanged(userId, historyEnabled);
}
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 7fbc085..9ed3559 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2025,6 +2025,8 @@
if (!mUserProfiles.isProfileUser(userId)) {
allowDefaultApprovedServices(userId);
}
+ mHistoryManager.onUserAdded(userId);
+ mSettingsObserver.update(null, userId);
}
} else if (action.equals(Intent.ACTION_USER_REMOVED)) {
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
@@ -2137,13 +2139,8 @@
mPreferencesHelper.updateBubblesEnabled();
}
if (uri == null || NOTIFICATION_HISTORY_ENABLED.equals(uri)) {
- final IntArray userIds = mUserProfiles.getCurrentProfileIds();
-
- for (int i = 0; i < userIds.size(); i++) {
- mArchive.updateHistoryEnabled(userIds.get(i),
- Settings.Secure.getIntForUser(resolver,
- Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0,
- userIds.get(i)) == 1);
+ for (UserInfo userInfo : mUm.getUsers()) {
+ update(uri, userInfo.id);
}
}
if (uri == null || NOTIFICATION_SHOW_MEDIA_ON_QUICK_SETTINGS_URI.equals(uri)) {
@@ -2164,6 +2161,17 @@
}
}
}
+
+ public void update(Uri uri, int userId) {
+ ContentResolver resolver = getContext().getContentResolver();
+ if (uri == null || NOTIFICATION_HISTORY_ENABLED.equals(uri)) {
+ mArchive.updateHistoryEnabled(userId,
+ Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0,
+ userId) == 1);
+ // note: this setting is also handled in NotificationHistoryManager
+ }
+ }
}
private SettingsObserver mSettingsObserver;
diff --git a/services/core/java/com/android/server/notification/ZenAdapters.java b/services/core/java/com/android/server/notification/ZenAdapters.java
index 2a65aff..91df04c 100644
--- a/services/core/java/com/android/server/notification/ZenAdapters.java
+++ b/services/core/java/com/android/server/notification/ZenAdapters.java
@@ -16,6 +16,7 @@
package com.android.server.notification;
+import android.app.Flags;
import android.app.NotificationManager.Policy;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenPolicy;
@@ -57,6 +58,12 @@
.showStatusBarIcons(policy.showStatusBarIcons());
}
+ if (Flags.modesApi()) {
+ zenPolicyBuilder.allowChannels(
+ policy.allowPriorityChannels()
+ ? ZenPolicy.CHANNEL_TYPE_PRIORITY : ZenPolicy.CHANNEL_TYPE_NONE);
+ }
+
return zenPolicyBuilder.build();
}
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index 49db7fc..47967db 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -56,4 +56,11 @@
namespace: "systemui"
description: "When this flag is on, NMS will no longer call removeMessage() and hasCallbacks() on Handler"
bug: "311051285"
+}
+
+flag {
+ name: "notification_test"
+ namespace: "systemui"
+ description: "Timing test, no functionality"
+ bug: "316931130"
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index f6e7ef3..9ce3cb3 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -41,14 +41,13 @@
import android.system.StructStat;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.proto.ProtoInputStream;
-import android.util.proto.ProtoParseException;
import com.android.internal.annotations.GuardedBy;
import com.android.server.BootReceiver;
import com.android.server.ServiceThread;
import com.android.server.os.TombstoneProtos.Cause;
import com.android.server.os.TombstoneProtos.Tombstone;
+import com.android.server.os.protobuf.CodedInputStream;
import libcore.io.IoUtils;
@@ -130,18 +129,21 @@
return;
}
- String processName = "UNKNOWN";
final boolean isProtoFile = filename.endsWith(".pb");
- File protoPath = isProtoFile ? path : new File(path.getAbsolutePath() + ".pb");
-
- Optional<TombstoneFile> parsedTombstone = handleProtoTombstone(protoPath, isProtoFile);
- if (parsedTombstone.isPresent()) {
- processName = parsedTombstone.get().getProcessName();
+ if (!isProtoFile) {
+ return;
}
- BootReceiver.addTombstoneToDropBox(mContext, path, isProtoFile, processName, mTmpFileLock);
+
+ Optional<ParsedTombstone> parsedTombstone = handleProtoTombstone(path, true);
+ if (parsedTombstone.isPresent()) {
+ BootReceiver.addTombstoneToDropBox(
+ mContext, path, parsedTombstone.get().getTombstone(),
+ parsedTombstone.get().getProcessName(), mTmpFileLock);
+ }
}
- private Optional<TombstoneFile> handleProtoTombstone(File path, boolean addToList) {
+ private Optional<ParsedTombstone> handleProtoTombstone(
+ File path, boolean addToList) {
final String filename = path.getName();
if (!filename.endsWith(".pb")) {
Slog.w(TAG, "unexpected tombstone name: " + path);
@@ -171,7 +173,7 @@
return Optional.empty();
}
- final Optional<TombstoneFile> parsedTombstone = TombstoneFile.parse(pfd);
+ final Optional<ParsedTombstone> parsedTombstone = TombstoneFile.parse(pfd);
if (!parsedTombstone.isPresent()) {
IoUtils.closeQuietly(pfd);
return Optional.empty();
@@ -184,7 +186,7 @@
previous.dispose();
}
- mTombstones.put(number, parsedTombstone.get());
+ mTombstones.put(number, parsedTombstone.get().getTombstoneFile());
}
}
@@ -332,6 +334,27 @@
}
}
+ static class ParsedTombstone {
+ TombstoneFile mTombstoneFile;
+ Tombstone mTombstone;
+ ParsedTombstone(TombstoneFile tombstoneFile, Tombstone tombstone) {
+ mTombstoneFile = tombstoneFile;
+ mTombstone = tombstone;
+ }
+
+ public String getProcessName() {
+ return mTombstoneFile.getProcessName();
+ }
+
+ public TombstoneFile getTombstoneFile() {
+ return mTombstoneFile;
+ }
+
+ public Tombstone getTombstone() {
+ return mTombstone;
+ }
+ }
+
static class TombstoneFile {
final ParcelFileDescriptor mPfd;
@@ -414,67 +437,21 @@
}
}
- static Optional<TombstoneFile> parse(ParcelFileDescriptor pfd) {
- final FileInputStream is = new FileInputStream(pfd.getFileDescriptor());
- final ProtoInputStream stream = new ProtoInputStream(is);
+ static Optional<ParsedTombstone> parse(ParcelFileDescriptor pfd) {
+ Tombstone tombstoneProto;
+ try (FileInputStream is = new FileInputStream(pfd.getFileDescriptor())) {
+ final byte[] tombstoneBytes = is.readAllBytes();
- int pid = 0;
- int uid = 0;
- String processName = null;
- String crashReason = "";
- String selinuxLabel = "";
-
- try {
- while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
- switch (stream.getFieldNumber()) {
- case (int) Tombstone.PID:
- pid = stream.readInt(Tombstone.PID);
- break;
-
- case (int) Tombstone.UID:
- uid = stream.readInt(Tombstone.UID);
- break;
-
- case (int) Tombstone.COMMAND_LINE:
- if (processName == null) {
- processName = stream.readString(Tombstone.COMMAND_LINE);
- }
- break;
-
- case (int) Tombstone.CAUSES:
- if (!crashReason.equals("")) {
- // Causes appear in decreasing order of likelihood. For now we only
- // want the most likely crash reason here, so ignore all others.
- break;
- }
- long token = stream.start(Tombstone.CAUSES);
- cause:
- while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
- switch (stream.getFieldNumber()) {
- case (int) Cause.HUMAN_READABLE:
- crashReason = stream.readString(Cause.HUMAN_READABLE);
- break cause;
-
- default:
- break;
- }
- }
- stream.end(token);
- break;
-
- case (int) Tombstone.SELINUX_LABEL:
- selinuxLabel = stream.readString(Tombstone.SELINUX_LABEL);
- break;
-
- default:
- break;
- }
- }
- } catch (IOException | ProtoParseException ex) {
+ tombstoneProto = Tombstone.parseFrom(
+ CodedInputStream.newInstance(tombstoneBytes));
+ } catch (IOException ex) {
Slog.e(TAG, "Failed to parse tombstone", ex);
return Optional.empty();
}
+ int pid = tombstoneProto.getPid();
+ int uid = tombstoneProto.getUid();
+
if (!UserHandle.isApp(uid)) {
Slog.e(TAG, "Tombstone's UID (" + uid + ") not an app, ignoring");
return Optional.empty();
@@ -491,6 +468,7 @@
final int userId = UserHandle.getUserId(uid);
final int appId = UserHandle.getAppId(uid);
+ String selinuxLabel = tombstoneProto.getSelinuxLabel();
if (!selinuxLabel.startsWith("u:r:untrusted_app")) {
Slog.e(TAG, "Tombstone has invalid selinux label (" + selinuxLabel + "), ignoring");
return Optional.empty();
@@ -502,11 +480,30 @@
result.mAppId = appId;
result.mPid = pid;
result.mUid = uid;
- result.mProcessName = processName == null ? "" : processName;
+ result.mProcessName = getCmdLineProcessName(tombstoneProto);
result.mTimestampMs = timestampMs;
- result.mCrashReason = crashReason;
+ result.mCrashReason = getCrashReason(tombstoneProto);
- return Optional.of(result);
+ return Optional.of(new ParsedTombstone(result, tombstoneProto));
+ }
+
+ private static String getCmdLineProcessName(Tombstone tombstoneProto) {
+ for (String cmdline : tombstoneProto.getCommandLineList()) {
+ if (cmdline != null) {
+ return cmdline;
+ }
+ }
+ return "";
+ }
+
+ private static String getCrashReason(Tombstone tombstoneProto) {
+ for (Cause cause : tombstoneProto.getCausesList()) {
+ if (cause.getHumanReadable() != null
+ && !cause.getHumanReadable().equals("")) {
+ return cause.getHumanReadable();
+ }
+ }
+ return "";
}
public IParcelFileDescriptorRetriever getPfdRetriever() {
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index e830d28..e984e9c 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -262,6 +262,7 @@
// Deliver LOCKED_BOOT_COMPLETED first
Intent lockedBcIntent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED)
.setPackage(packageName);
+ lockedBcIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
if (includeStopped) {
lockedBcIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
}
@@ -275,6 +276,7 @@
// Deliver BOOT_COMPLETED only if user is unlocked
if (mUmInternal.isUserUnlockingOrUnlocked(userId)) {
Intent bcIntent = new Intent(Intent.ACTION_BOOT_COMPLETED).setPackage(packageName);
+ bcIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
if (includeStopped) {
bcIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
}
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index c920ca8..588c629 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -487,7 +487,7 @@
// Do not uninstall the APK if an app should be cached
boolean keepUninstalledPackage =
mPm.shouldKeepUninstalledPackageLPr(packageName);
- if (ps.isInstalledOrHasDataOnAnyOtherUser(
+ if (ps.isInstalledOnAnyOtherUser(
mUserManagerInternal.getUserIds(), userId) || keepUninstalledPackage) {
// Other users still have this package installed, so all
// we need to do is clear this user's data and save that
@@ -533,7 +533,7 @@
// artifacts are not stored in the same directory as the APKs
deleteArtDexoptArtifacts(packageName);
}
- deleteInstalledPackageLIF(ps, deleteCodeAndResources, flags, allUserHandles,
+ deleteInstalledPackageLIF(ps, userId, deleteCodeAndResources, flags, allUserHandles,
outInfo, writeSettings);
}
@@ -554,7 +554,7 @@
}
@GuardedBy("mPm.mInstallLock")
- private void deleteInstalledPackageLIF(PackageSetting ps,
+ private void deleteInstalledPackageLIF(PackageSetting ps, int userId,
boolean deleteCodeAndResources, int flags, @NonNull int[] allUserHandles,
@NonNull PackageRemovedInfo outInfo, boolean writeSettings) {
synchronized (mPm.mLock) {
@@ -567,7 +567,7 @@
// Delete package data from internal structures and also remove data if flag is set
mRemovePackageHelper.removePackageDataLIF(
- ps, allUserHandles, outInfo, flags, writeSettings);
+ ps, userId, allUserHandles, outInfo, flags, writeSettings);
// Delete application code and resources only for parent packages
if (deleteCodeAndResources) {
@@ -677,8 +677,8 @@
flags |= PackageManager.DELETE_KEEP_DATA;
}
synchronized (mPm.mInstallLock) {
- deleteInstalledPackageLIF(deletedPs, true, flags, allUserHandles, outInfo,
- writeSettings);
+ deleteInstalledPackageLIF(deletedPs, UserHandle.USER_ALL, true, flags, allUserHandles,
+ outInfo, writeSettings);
}
}
diff --git a/services/core/java/com/android/server/pm/InitAppsHelper.java b/services/core/java/com/android/server/pm/InitAppsHelper.java
index 41d0176..22951d5 100644
--- a/services/core/java/com/android/server/pm/InitAppsHelper.java
+++ b/services/core/java/com/android/server/pm/InitAppsHelper.java
@@ -180,7 +180,9 @@
// priority of system overlays.
final ArrayMap<String, File> apkInApexPreInstalledPaths = new ArrayMap<>();
for (ApexManager.ActiveApexInfo apexInfo : mApexManager.getActiveApexInfos()) {
- for (String packageName : mApexManager.getApksInApex(apexInfo.apexModuleName)) {
+ final String apexPackageName = mApexManager.getActivePackageNameForApexModuleName(
+ apexInfo.apexModuleName);
+ for (String packageName : mApexManager.getApksInApex(apexPackageName)) {
apkInApexPreInstalledPaths.put(packageName, apexInfo.preInstalledApexPath);
}
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index b638d30..dd9541e 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -175,6 +175,7 @@
import com.android.server.art.model.ArtFlags;
import com.android.server.art.model.DexoptParams;
import com.android.server.art.model.DexoptResult;
+import com.android.server.criticalevents.CriticalEventLog;
import com.android.server.pm.Installer.LegacyDexoptDisabledException;
import com.android.server.pm.dex.ArtManagerService;
import com.android.server.pm.dex.DexManager;
@@ -957,6 +958,7 @@
final Set<String> scannedPackages = new ArraySet<>(requests.size());
final Map<String, Settings.VersionInfo> versionInfos = new ArrayMap<>(requests.size());
final Map<String, Boolean> createdAppId = new ArrayMap<>(requests.size());
+ CriticalEventLog.getInstance().logInstallPackagesStarted();
boolean success = false;
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackagesLI");
@@ -3264,9 +3266,9 @@
Slog.e(TAG, "updateAllSharedLibrariesLPw failed: " + e.getMessage());
}
}
- mAppDataHelper.prepareAppDataAfterInstallLIF(pkg);
-
setPackageInstalledForSystemPackage(pkg, allUserHandles, origUserHandles, writeSettings);
+
+ mAppDataHelper.prepareAppDataAfterInstallLIF(pkg);
}
private void setPackageInstalledForSystemPackage(@NonNull AndroidPackage pkg,
@@ -4590,7 +4592,9 @@
private void assertPackageWithSharedUserIdIsPrivileged(AndroidPackage pkg)
throws PackageManagerException {
- if (!AndroidPackageLegacyUtils.isPrivileged(pkg) && (pkg.getSharedUserId() != null)) {
+ if (!AndroidPackageLegacyUtils.isPrivileged(pkg)
+ && (pkg.getSharedUserId() != null)
+ && !pkg.isLeavingSharedUser()) {
SharedUserSetting sharedUserSetting = null;
try {
synchronized (mPm.mLock) {
@@ -4630,7 +4634,8 @@
if (((scanFlags & SCAN_AS_PRIVILEGED) == 0)
&& !AndroidPackageLegacyUtils.isPrivileged(pkg)
&& (pkg.getSharedUserId() != null)
- && !skipVendorPrivilegeScan) {
+ && !skipVendorPrivilegeScan
+ && !pkg.isLeavingSharedUser()) {
SharedUserSetting sharedUserSetting = null;
synchronized (mPm.mLock) {
try {
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index dcfc855d..376b061 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -25,6 +25,7 @@
import static android.content.pm.ArchivedActivityInfo.bytesFromBitmap;
import static android.content.pm.ArchivedActivityInfo.drawableToBitmap;
import static android.content.pm.PackageInstaller.EXTRA_UNARCHIVE_STATUS;
+import static android.content.pm.PackageInstaller.STATUS_PENDING_USER_ACTION;
import static android.content.pm.PackageInstaller.UNARCHIVAL_OK;
import static android.content.pm.PackageManager.DELETE_ARCHIVE;
import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
@@ -72,10 +73,12 @@
import android.os.IBinder;
import android.os.ParcelableException;
import android.os.Process;
+import android.os.RemoteException;
import android.os.SELinux;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ExceptionUtils;
+import android.util.Pair;
import android.util.Slog;
import com.android.internal.R;
@@ -93,7 +96,9 @@
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
@@ -140,15 +145,23 @@
private final Context mContext;
private final PackageManagerService mPm;
+ private final AppStateHelper mAppStateHelper;
+
@Nullable
private LauncherApps mLauncherApps;
@Nullable
private AppOpsManager mAppOpsManager;
+ /* IntentSender store that maps key: {userId, appPackageName} to respective existing attached
+ unarchival intent sender. */
+ private final Map<Pair<Integer, String>, IntentSender> mLauncherIntentSenders;
+
PackageArchiver(Context context, PackageManagerService mPm) {
this.mContext = context;
this.mPm = mPm;
+ this.mAppStateHelper = new AppStateHelper(mContext);
+ this.mLauncherIntentSenders = new HashMap<>();
}
/** Returns whether a package is archived for a user. */
@@ -235,37 +248,32 @@
// Return early as the calling UID does not match caller package's UID.
return START_CLASS_NOT_FOUND;
}
+
String currentLauncherPackageName = getCurrentLauncherPackageName(userId);
if ((currentLauncherPackageName == null || !callerPackageName.equals(
currentLauncherPackageName)) && callingUid != Process.SHELL_UID) {
// TODO(b/311619990): Remove dependency on SHELL_UID for testing
Slog.e(TAG, TextUtils.formatSimple(
- "callerPackageName: %s does not qualify for archival of package: " + "%s!",
+ "callerPackageName: %s does not qualify for unarchival of package: " + "%s!",
callerPackageName, packageName));
return START_PERMISSION_DENIED;
}
- // TODO(b/302114464): Handle edge cases & also divert to a dialog based on
- // permissions + compat options
- Slog.i(TAG, TextUtils.formatSimple("Unarchival is starting for: %s", packageName));
- try {
- final IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
- @Override
- public void send(int code, Intent intent, String resolvedType,
- IBinder allowlistToken,
- IIntentReceiver finishedReceiver, String requiredPermission,
- Bundle options) {
- // TODO(b/302114464): Handle intent sender status codes
- }
- };
+ Slog.i(TAG, TextUtils.formatSimple("Unarchival is starting for: %s", packageName));
+
+ try {
+ // TODO(b/311709794) Make showUnarchivalConfirmation dependent on the compat options.
requestUnarchive(packageName, callerPackageName,
- new IntentSender((IIntentSender) mLocalSender), UserHandle.of(userId));
+ getOrCreateUnarchiveIntentSender(userId, packageName),
+ UserHandle.of(userId),
+ false /* showUnarchivalConfirmation= */);
} catch (Throwable t) {
Slog.e(TAG, TextUtils.formatSimple(
"Unexpected error occurred while unarchiving package %s: %s.", packageName,
t.getLocalizedMessage()));
return START_ABORTED;
}
+
return START_SUCCESS;
}
@@ -321,6 +329,20 @@
return true;
}
+ private IntentSender getOrCreateUnarchiveIntentSender(int userId, String packageName) {
+ Pair<Integer, String> key = Pair.create(userId, packageName);
+ synchronized (mLauncherIntentSenders) {
+ IntentSender intentSender = mLauncherIntentSenders.get(key);
+ if (intentSender != null) {
+ return intentSender;
+ }
+ IntentSender unarchiveIntentSender = new IntentSender(
+ (IIntentSender) new UnarchiveIntentSender());
+ mLauncherIntentSenders.put(key, unarchiveIntentSender);
+ return unarchiveIntentSender;
+ }
+ }
+
/** Creates archived state for the package and user. */
private CompletableFuture<ArchiveState> createArchiveState(String packageName, int userId)
throws PackageManager.NameNotFoundException {
@@ -553,6 +575,15 @@
@NonNull String callerPackageName,
@NonNull IntentSender statusReceiver,
@NonNull UserHandle userHandle) {
+ requestUnarchive(packageName, callerPackageName, statusReceiver, userHandle,
+ false /* showUnarchivalConfirmation= */);
+ }
+
+ private void requestUnarchive(
+ @NonNull String packageName,
+ @NonNull String callerPackageName,
+ @NonNull IntentSender statusReceiver,
+ @NonNull UserHandle userHandle, boolean showUnarchivalConfirmation) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(callerPackageName);
Objects.requireNonNull(statusReceiver);
@@ -597,8 +628,8 @@
+ "an unarchival.");
}
- if (!hasInstallPackages) {
- requestUnarchiveConfirmation(packageName, statusReceiver);
+ if (!hasInstallPackages || showUnarchivalConfirmation) {
+ requestUnarchiveConfirmation(packageName, statusReceiver, userHandle);
return;
}
@@ -622,7 +653,8 @@
() -> unarchiveInternal(packageName, userHandle, installerPackage, draftSessionId));
}
- private void requestUnarchiveConfirmation(String packageName, IntentSender statusReceiver) {
+ private void requestUnarchiveConfirmation(String packageName, IntentSender statusReceiver,
+ UserHandle user) {
final Intent dialogIntent = new Intent(ACTION_UNARCHIVE_DIALOG);
dialogIntent.putExtra(EXTRA_UNARCHIVE_INTENT_SENDER, statusReceiver);
dialogIntent.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName);
@@ -632,6 +664,7 @@
broadcastIntent.putExtra(EXTRA_UNARCHIVE_STATUS,
PackageInstaller.STATUS_PENDING_USER_ACTION);
broadcastIntent.putExtra(Intent.EXTRA_INTENT, dialogIntent);
+ broadcastIntent.putExtra(Intent.EXTRA_USER, user);
sendIntent(statusReceiver, packageName, /* message= */ "", broadcastIntent);
}
@@ -656,6 +689,7 @@
int installerUid = mPm.snapshotComputer().getPackageUid(installerPackage, 0, userId);
// Handles case of repeated unarchival calls for the same package.
+ // TODO(b/316881759) Allow attaching multiple intentSenders to one session.
int existingSessionId = mPm.mInstallerService.getExistingDraftSessionId(installerUid,
sessionParams,
userId);
@@ -849,7 +883,13 @@
void notifyUnarchivalListener(int status, String installerPackageName, String appPackageName,
long requiredStorageBytes, @Nullable PendingIntent userActionIntent,
- IntentSender unarchiveIntentSender, int userId) {
+ @Nullable IntentSender unarchiveIntentSender, int userId) {
+ if (unarchiveIntentSender == null) {
+ // Maybe this can happen if the installer calls reportUnarchivalStatus twice in quick
+ // succession.
+ return;
+ }
+
final Intent broadcastIntent = new Intent();
broadcastIntent.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, appPackageName);
broadcastIntent.putExtra(EXTRA_UNARCHIVE_STATUS, status);
@@ -863,6 +903,7 @@
return;
}
broadcastIntent.putExtra(Intent.EXTRA_INTENT, dialogIntent);
+ broadcastIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId));
}
final BroadcastOptions options = BroadcastOptions.makeBasic();
@@ -874,6 +915,10 @@
options.toBundle());
} catch (IntentSender.SendIntentException e) {
Slog.e(TAG, TextUtils.formatSimple("Failed to send unarchive intent"), e);
+ } finally {
+ synchronized (mLauncherIntentSenders) {
+ mLauncherIntentSenders.remove(Pair.create(userId, appPackageName));
+ }
}
}
@@ -883,6 +928,7 @@
long requiredStorageBytes, PendingIntent userActionIntent, int userId) {
final Intent dialogIntent = new Intent(ACTION_UNARCHIVE_ERROR_DIALOG);
dialogIntent.putExtra(EXTRA_UNARCHIVE_STATUS, status);
+ dialogIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId));
if (requiredStorageBytes > 0) {
dialogIntent.putExtra(EXTRA_REQUIRED_BYTES, requiredStorageBytes);
}
@@ -1118,4 +1164,25 @@
return activities.toArray(new ArchivedActivityParcel[activities.size()]);
}
+
+ private class UnarchiveIntentSender extends IIntentSender.Stub {
+ @Override
+ public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
+ IIntentReceiver finishedReceiver, String requiredPermission, Bundle options)
+ throws RemoteException {
+ int status = intent.getExtras().getInt(PackageInstaller.EXTRA_UNARCHIVE_STATUS,
+ STATUS_PENDING_USER_ACTION);
+ if (status == UNARCHIVAL_OK) {
+ return;
+ }
+ Intent extraIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent.class);
+ UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle.class);
+ if (extraIntent != null && user != null
+ && mAppStateHelper.isAppTopVisible(
+ getCurrentLauncherPackageName(user.getIdentifier()))) {
+ extraIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivityAsUser(extraIntent, user);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index cbd65a4..0a23dfb 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -291,9 +291,7 @@
@NonNull
private final RequestThrottle mSettingsWriteRequest = new RequestThrottle(IoThread.getHandler(),
() -> {
- synchronized (mSessions) {
- return writeSessionsLocked();
- }
+ return writeSessions();
});
public PackageInstallerService(Context context, PackageManagerService pm,
@@ -600,9 +598,16 @@
mHistoricalSessionsByInstaller.get(installerUid) + 1);
}
- @GuardedBy("mSessions")
- private boolean writeSessionsLocked() {
- if (LOGD) Slog.v(TAG, "writeSessionsLocked()");
+ private boolean writeSessions() {
+ if (LOGD) Slog.v(TAG, "writeSessions()");
+ final PackageInstallerSession[] sessions;
+ synchronized (mSessions) {
+ final int size = mSessions.size();
+ sessions = new PackageInstallerSession[size];
+ for (int i = 0; i < size; i++) {
+ sessions[i] = mSessions.valueAt(i);
+ }
+ }
FileOutputStream fos = null;
try {
@@ -611,9 +616,7 @@
final TypedXmlSerializer out = Xml.resolveSerializer(fos);
out.startDocument(null, true);
out.startTag(null, TAG_SESSIONS);
- final int size = mSessions.size();
- for (int i = 0; i < size; i++) {
- final PackageInstallerSession session = mSessions.valueAt(i);
+ for (var session : sessions) {
session.write(out, mSessionsDir);
}
out.endTag(null, TAG_SESSIONS);
diff --git a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
index d05e4c6..cf5de89 100644
--- a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
+++ b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
@@ -48,6 +48,7 @@
class PackageMonitorCallbackHelper {
private static final boolean DEBUG = false;
+ private static final String TAG = "PackageMonitorCallbackHelper";
@NonNull
private final Object mLock = new Object();
@@ -243,25 +244,33 @@
return;
}
int registerUid = registerUser.getUid();
+ if (allowUids != null && registerUid != Process.SYSTEM_UID
+ && !ArrayUtils.contains(allowUids, registerUid)) {
+ if (DEBUG) {
+ Slog.w(TAG, "Skip invoke PackageMonitorCallback for " + intent.getAction()
+ + ", uid " + registerUid);
+ }
+ return;
+ }
+ Intent newIntent = intent;
if (filterExtrasFunction != null) {
final Bundle extras = intent.getExtras();
if (extras != null) {
final Bundle filteredExtras = filterExtrasFunction.apply(registerUid, extras);
- if (filteredExtras != null) {
- intent.replaceExtras(filteredExtras);
+ if (filteredExtras == null) {
+ // caller is unable to access this intent
+ if (DEBUG) {
+ Slog.w(TAG,
+ "Skip invoke PackageMonitorCallback for " + intent.getAction()
+ + " because null filteredExtras");
+ }
+ return;
}
+ newIntent = new Intent(newIntent);
+ newIntent.replaceExtras(filteredExtras);
}
}
- if (allowUids != null && registerUid != Process.SYSTEM_UID
- && !ArrayUtils.contains(allowUids, registerUid)) {
- if (DEBUG) {
- Slog.w("PackageMonitorCallbackHelper",
- "Skip invoke PackageMonitorCallback for " + intent.getAction()
- + ", uid " + registerUid);
- }
- return;
- }
- invokeCallback(callback, intent);
+ invokeCallback(callback, newIntent);
}));
}
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 02ce159..45fc49a 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -825,7 +825,7 @@
return changed;
}
- boolean isInstalledOrHasDataOnAnyOtherUser(int[] allUsers, int currentUser) {
+ boolean isInstalledOnAnyOtherUser(int[] allUsers, int currentUser) {
for (int user: allUsers) {
if (user == currentUser) {
continue;
@@ -834,6 +834,16 @@
if (userState.isInstalled()) {
return true;
}
+ }
+ return false;
+ }
+
+ boolean hasDataOnAnyOtherUser(int[] allUsers, int currentUser) {
+ for (int user: allUsers) {
+ if (user == currentUser) {
+ continue;
+ }
+ final PackageUserStateInternal userState = readUserState(user);
if (userState.dataExists()) {
return true;
}
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 2854453..7bd6a43 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -355,16 +355,22 @@
// Called to clean up disabled system packages
public void removePackageData(final PackageSetting deletedPs, @NonNull int[] allUserHandles) {
synchronized (mPm.mInstallLock) {
- removePackageDataLIF(deletedPs, allUserHandles, new PackageRemovedInfo(),
- /* flags= */ 0, /* writeSettings= */ false);
+ removePackageDataLIF(deletedPs, UserHandle.USER_ALL, allUserHandles,
+ new PackageRemovedInfo(), /* flags= */ 0, /* writeSettings= */ false);
}
}
- /*
+ /**
* This method deletes the package from internal data structures such as mPackages / mSettings.
+ *
+ * @param targetUserId indicates the target user of the deletion. It equals to
+ * {@link UserHandle.USER_ALL} if the deletion was initiated for all users,
+ * otherwise it equals to the specific user id that the deletion was meant
+ * for.
*/
@GuardedBy("mPm.mInstallLock")
- public void removePackageDataLIF(final PackageSetting deletedPs, @NonNull int[] allUserHandles,
+ public void removePackageDataLIF(final PackageSetting deletedPs, int targetUserId,
+ @NonNull int[] allUserHandles,
@NonNull PackageRemovedInfo outInfo, int flags, boolean writeSettings) {
String packageName = deletedPs.getPackageName();
if (DEBUG_REMOVE) Slog.d(TAG, "removePackageDataLI: " + deletedPs);
@@ -372,7 +378,7 @@
final AndroidPackage deletedPkg = deletedPs.getPkg();
// Delete all the data and states related to this package.
- clearPackageStateForUserLIF(deletedPs, UserHandle.USER_ALL, flags);
+ clearPackageStateForUserLIF(deletedPs, targetUserId, flags);
// Delete from mPackages
removePackageLI(packageName, (flags & PackageManager.DELETE_CHATTY) != 0);
@@ -384,7 +390,7 @@
deletedPs.setPkg(null);
}
- if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) {
+ if (shouldDeletePackageSetting(deletedPs, targetUserId, allUserHandles, flags)) {
// Delete from mSettings
final SparseBooleanArray changedUsers = new SparseBooleanArray();
synchronized (mPm.mLock) {
@@ -457,11 +463,32 @@
}
}
+ private static boolean shouldDeletePackageSetting(PackageSetting deletedPs, int userId,
+ int[] allUserHandles, int flags) {
+ if ((flags & PackageManager.DELETE_KEEP_DATA) != 0) {
+ return false;
+ }
+ if (userId == UserHandle.USER_ALL) {
+ // Deleting for ALL. Let's wipe the PackageSetting.
+ return true;
+ }
+ if (deletedPs.hasDataOnAnyOtherUser(allUserHandles, userId)) {
+ // We arrived here because we are uninstalling the package for a specified user, and the
+ // package isn't installed on any other user. Before we proceed to completely delete the
+ // PackageSetting from mSettings, let's first check if data exists on any other user.
+ // If so, do not wipe the PackageSetting.
+ return false;
+ }
+ return true;
+ }
+
void cleanUpResources(@Nullable String packageName, @Nullable File codeFile,
@Nullable String[] instructionSets) {
synchronized (mPm.mInstallLock) {
cleanUpResourcesLI(codeFile, instructionSets);
}
+ // TODO: open logging to help debug, will delete or add debug flag
+ Slog.d(TAG, "cleanUpResources for " + codeFile);
if (packageName == null) {
return;
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index d683855..5d710d2 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1405,7 +1405,9 @@
case AppOpsManager.MODE_ERRORED: {
if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as op"
- + " mode is MODE_ERRORED for " + attributionSource);
+ + " mode is MODE_ERRORED. Permission check was requested for: "
+ + attributionSource + " and op transaction was invoked for "
+ + current);
}
return PermissionChecker.PERMISSION_HARD_DENIED;
}
diff --git a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
index 752eb53..17c901e 100644
--- a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
+++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
@@ -254,6 +254,14 @@
String packageName = verifications.valueAt(index).second;
AndroidPackage pkg = mConnection.getPackage(packageName);
+ if (pkg == null) {
+ if (DEBUG_BROADCASTS) {
+ Slog.d(TAG,
+ "Skip sendBroadcasts because null AndroidPackage for " + packageName);
+ }
+ continue;
+ }
+
String hostsString = buildHostsString(pkg);
Intent intent = new Intent(Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION)
diff --git a/services/core/java/com/android/server/policy/Android.bp b/services/core/java/com/android/server/policy/Android.bp
new file mode 100644
index 0000000..fa55bf0
--- /dev/null
+++ b/services/core/java/com/android/server/policy/Android.bp
@@ -0,0 +1,10 @@
+aconfig_declarations {
+ name: "policy_flags",
+ package: "com.android.server.policy",
+ srcs: ["*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "policy_flags_lib",
+ aconfig_declarations: "policy_flags",
+}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index edce3ec..bf669fb 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1301,7 +1301,7 @@
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.THEATER_MODE_ON, 0);
if (!interactive) {
- wakeUpFromWakeKey(eventTime, KEYCODE_POWER);
+ wakeUpFromWakeKey(eventTime, KEYCODE_POWER, /* isDown= */ false);
}
} else {
Slog.i(TAG, "Toggling theater mode on.");
@@ -1317,7 +1317,7 @@
case MULTI_PRESS_POWER_BRIGHTNESS_BOOST:
Slog.i(TAG, "Starting brightness boost.");
if (!interactive) {
- wakeUpFromWakeKey(eventTime, KEYCODE_POWER);
+ wakeUpFromWakeKey(eventTime, KEYCODE_POWER, /* isDown= */ false);
}
mPowerManager.boostScreenBrightness(eventTime);
break;
@@ -4211,6 +4211,14 @@
return redoLayout;
}
+ /**
+ * Shows the keyguard without immediately locking the device.
+ */
+ @Override
+ public void showDismissibleKeyguard() {
+ mKeyguardDelegate.showDismissibleKeyguard();
+ }
+
// There are several different flavors of "assistant" that can be launched from
// various parts of the UI.
@@ -5177,7 +5185,8 @@
public int interceptMotionBeforeQueueingNonInteractive(int displayId, int source, int action,
long whenNanos, int policyFlags) {
if ((policyFlags & FLAG_WAKE) != 0) {
- if (mWindowWakeUpPolicy.wakeUpFromMotion(whenNanos / 1000000)) {
+ if (mWindowWakeUpPolicy.wakeUpFromMotion(
+ whenNanos / 1000000, source, action == MotionEvent.ACTION_DOWN)) {
// Woke up. Pass motion events to user.
return ACTION_PASS_TO_USER;
}
@@ -5191,7 +5200,8 @@
// there will be no dream to intercept the touch and wake into ambient. The device should
// wake up in this case.
if (isTheaterModeEnabled() && (policyFlags & FLAG_WAKE) != 0) {
- if (mWindowWakeUpPolicy.wakeUpFromMotion(whenNanos / 1000000)) {
+ if (mWindowWakeUpPolicy.wakeUpFromMotion(
+ whenNanos / 1000000, source, action == MotionEvent.ACTION_DOWN)) {
// Woke up. Pass motion events to user.
return ACTION_PASS_TO_USER;
}
@@ -5526,11 +5536,14 @@
}
private void wakeUpFromWakeKey(KeyEvent event) {
- wakeUpFromWakeKey(event.getEventTime(), event.getKeyCode());
+ wakeUpFromWakeKey(
+ event.getEventTime(),
+ event.getKeyCode(),
+ event.getAction() == KeyEvent.ACTION_DOWN);
}
- private void wakeUpFromWakeKey(long eventTime, int keyCode) {
- if (mWindowWakeUpPolicy.wakeUpFromKey(eventTime, keyCode)) {
+ private void wakeUpFromWakeKey(long eventTime, int keyCode, boolean isDown) {
+ if (mWindowWakeUpPolicy.wakeUpFromKey(eventTime, keyCode, isDown)) {
final boolean keyCanLaunchHome = keyCode == KEYCODE_HOME || keyCode == KEYCODE_POWER;
// Start HOME with "reason" extra if sleeping for more than mWakeUpToLastStateTimeout
if (shouldWakeUpWithHomeIntent() && keyCanLaunchHome) {
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 3016b39..2174fd6 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -178,6 +178,12 @@
int applyKeyguardOcclusionChange();
/**
+ * Shows the keyguard immediately if not already shown.
+ * Does NOT immediately request the device to lock.
+ */
+ void showDismissibleKeyguard();
+
+ /**
* Interface to the Window Manager state associated with a particular
* window. You can hold on to an instance of this interface from the call
* to prepareAddWindow() until removeWindow().
diff --git a/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java b/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
index 392d0d4..a790950 100644
--- a/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
@@ -24,6 +24,9 @@
import static android.os.PowerManager.WAKE_REASON_WAKE_MOTION;
import static android.view.KeyEvent.KEYCODE_POWER;
+import static com.android.server.policy.Flags.supportInputWakeupDelegate;
+
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.os.PowerManager;
@@ -31,7 +34,11 @@
import android.os.SystemClock;
import android.provider.Settings;
import android.util.Slog;
+import android.view.KeyEvent;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.Clock;
+import com.android.server.LocalServices;
/** Policy controlling the decision and execution of window-related wake ups. */
class WindowWakeUpPolicy {
@@ -41,18 +48,27 @@
private final Context mContext;
private final PowerManager mPowerManager;
+ private final Clock mClock;
private final boolean mAllowTheaterModeWakeFromKey;
private final boolean mAllowTheaterModeWakeFromPowerKey;
private final boolean mAllowTheaterModeWakeFromMotion;
- private final boolean mAllowTheaterModeWakeFromMotionWhenNotDreaming;
private final boolean mAllowTheaterModeWakeFromCameraLens;
private final boolean mAllowTheaterModeWakeFromLidSwitch;
private final boolean mAllowTheaterModeWakeFromWakeGesture;
+ // The policy will handle input-based wake ups if this delegate is null.
+ @Nullable private WindowWakeUpPolicyInternal.InputWakeUpDelegate mInputWakeUpDelegate;
+
WindowWakeUpPolicy(Context context) {
+ this(context, Clock.SYSTEM_CLOCK);
+ }
+
+ @VisibleForTesting
+ WindowWakeUpPolicy(Context context, Clock clock) {
mContext = context;
mPowerManager = context.getSystemService(PowerManager.class);
+ mClock = clock;
final Resources res = context.getResources();
mAllowTheaterModeWakeFromKey = res.getBoolean(
@@ -62,14 +78,26 @@
com.android.internal.R.bool.config_allowTheaterModeWakeFromPowerKey);
mAllowTheaterModeWakeFromMotion = res.getBoolean(
com.android.internal.R.bool.config_allowTheaterModeWakeFromMotion);
- mAllowTheaterModeWakeFromMotionWhenNotDreaming = res.getBoolean(
- com.android.internal.R.bool.config_allowTheaterModeWakeFromMotionWhenNotDreaming);
mAllowTheaterModeWakeFromCameraLens = res.getBoolean(
com.android.internal.R.bool.config_allowTheaterModeWakeFromCameraLens);
mAllowTheaterModeWakeFromLidSwitch = res.getBoolean(
com.android.internal.R.bool.config_allowTheaterModeWakeFromLidSwitch);
mAllowTheaterModeWakeFromWakeGesture = res.getBoolean(
com.android.internal.R.bool.config_allowTheaterModeWakeFromGesture);
+ if (supportInputWakeupDelegate()) {
+ LocalServices.addService(WindowWakeUpPolicyInternal.class, new LocalService());
+ }
+ }
+
+ private final class LocalService implements WindowWakeUpPolicyInternal {
+ @Override
+ public void setInputWakeUpDelegate(@Nullable InputWakeUpDelegate delegate) {
+ if (!supportInputWakeupDelegate()) {
+ Slog.w(TAG, "Input wake up delegates not supported.");
+ return;
+ }
+ mInputWakeUpDelegate = delegate;
+ }
}
/**
@@ -77,31 +105,49 @@
*
* @param eventTime the timestamp of the event in {@link SystemClock#uptimeMillis()}.
* @param keyCode the {@link android.view.KeyEvent} key code of the key event.
+ * @param isDown {@code true} if the event's action is {@link KeyEvent#ACTION_DOWN}.
* @return {@code true} if the policy allows the requested wake up and the request has been
* executed; {@code false} otherwise.
*/
- boolean wakeUpFromKey(long eventTime, int keyCode) {
+ boolean wakeUpFromKey(long eventTime, int keyCode, boolean isDown) {
final boolean wakeAllowedDuringTheaterMode =
keyCode == KEYCODE_POWER
? mAllowTheaterModeWakeFromPowerKey
: mAllowTheaterModeWakeFromKey;
- return wakeUp(
+ if (!canWakeUp(wakeAllowedDuringTheaterMode)) {
+ if (DEBUG) Slog.d(TAG, "Unable to wake up from " + KeyEvent.keyCodeToString(keyCode));
+ return false;
+ }
+ if (mInputWakeUpDelegate != null
+ && mInputWakeUpDelegate.wakeUpFromKey(eventTime, keyCode, isDown)) {
+ return true;
+ }
+ wakeUp(
eventTime,
- wakeAllowedDuringTheaterMode,
keyCode == KEYCODE_POWER ? WAKE_REASON_POWER_BUTTON : WAKE_REASON_WAKE_KEY,
keyCode == KEYCODE_POWER ? "POWER" : "KEY");
+ return true;
}
/**
* Wakes up from a motion event.
*
* @param eventTime the timestamp of the event in {@link SystemClock#uptimeMillis()}.
+ * @param isDown {@code true} if the event's action is {@link MotionEvent#ACTION_DOWN}.
* @return {@code true} if the policy allows the requested wake up and the request has been
* executed; {@code false} otherwise.
*/
- boolean wakeUpFromMotion(long eventTime) {
- return wakeUp(
- eventTime, mAllowTheaterModeWakeFromMotion, WAKE_REASON_WAKE_MOTION, "MOTION");
+ boolean wakeUpFromMotion(long eventTime, int source, boolean isDown) {
+ if (!canWakeUp(mAllowTheaterModeWakeFromMotion)) {
+ if (DEBUG) Slog.d(TAG, "Unable to wake up from motion.");
+ return false;
+ }
+ if (mInputWakeUpDelegate != null
+ && mInputWakeUpDelegate.wakeUpFromMotion(eventTime, source, isDown)) {
+ return true;
+ }
+ wakeUp(eventTime, WAKE_REASON_WAKE_MOTION, "MOTION");
+ return true;
}
/**
@@ -112,11 +158,12 @@
* executed; {@code false} otherwise.
*/
boolean wakeUpFromCameraCover(long eventTime) {
- return wakeUp(
- eventTime,
- mAllowTheaterModeWakeFromCameraLens,
- WAKE_REASON_CAMERA_LAUNCH,
- "CAMERA_COVER");
+ if (!canWakeUp(mAllowTheaterModeWakeFromCameraLens)) {
+ if (DEBUG) Slog.d(TAG, "Unable to wake up from camera cover.");
+ return false;
+ }
+ wakeUp(eventTime, WAKE_REASON_CAMERA_LAUNCH, "CAMERA_COVER");
+ return true;
}
/**
@@ -126,11 +173,12 @@
* executed; {@code false} otherwise.
*/
boolean wakeUpFromLid() {
- return wakeUp(
- SystemClock.uptimeMillis(),
- mAllowTheaterModeWakeFromLidSwitch,
- WAKE_REASON_LID,
- "LID");
+ if (!canWakeUp(mAllowTheaterModeWakeFromLidSwitch)) {
+ if (DEBUG) Slog.d(TAG, "Unable to wake up from lid.");
+ return false;
+ }
+ wakeUp(mClock.uptimeMillis(), WAKE_REASON_LID, "LID");
+ return true;
}
/**
@@ -140,11 +188,12 @@
* executed; {@code false} otherwise.
*/
boolean wakeUpFromPowerKeyCameraGesture() {
- return wakeUp(
- SystemClock.uptimeMillis(),
- mAllowTheaterModeWakeFromPowerKey,
- WAKE_REASON_CAMERA_LAUNCH,
- "CAMERA_GESTURE_PREVENT_LOCK");
+ if (!canWakeUp(mAllowTheaterModeWakeFromPowerKey)) {
+ if (DEBUG) Slog.d(TAG, "Unable to wake up from power key camera gesture.");
+ return false;
+ }
+ wakeUp(mClock.uptimeMillis(), WAKE_REASON_CAMERA_LAUNCH, "CAMERA_GESTURE_PREVENT_LOCK");
+ return true;
}
/**
@@ -154,23 +203,23 @@
* executed; {@code false} otherwise.
*/
boolean wakeUpFromWakeGesture() {
- return wakeUp(
- SystemClock.uptimeMillis(),
- mAllowTheaterModeWakeFromWakeGesture,
- WAKE_REASON_GESTURE,
- "GESTURE");
+ if (!canWakeUp(mAllowTheaterModeWakeFromWakeGesture)) {
+ if (DEBUG) Slog.d(TAG, "Unable to wake up from gesture.");
+ return false;
+ }
+ wakeUp(mClock.uptimeMillis(), WAKE_REASON_GESTURE, "GESTURE");
+ return true;
}
- private boolean wakeUp(
- long wakeTime, boolean wakeInTheaterMode, @WakeReason int reason, String details) {
+ private boolean canWakeUp(boolean wakeInTheaterMode) {
final boolean isTheaterModeEnabled =
Settings.Global.getInt(
mContext.getContentResolver(), Settings.Global.THEATER_MODE_ON, 0) == 1;
- if (!wakeInTheaterMode && isTheaterModeEnabled) {
- if (DEBUG) Slog.d(TAG, "Unable to wake up from " + details);
- return false;
- }
+ return wakeInTheaterMode || !isTheaterModeEnabled;
+ }
+
+ /** Wakes up {@link PowerManager}. */
+ private void wakeUp(long wakeTime, @WakeReason int reason, String details) {
mPowerManager.wakeUp(wakeTime, reason, "android.policy:" + details);
- return true;
}
}
diff --git a/services/core/java/com/android/server/policy/WindowWakeUpPolicyInternal.java b/services/core/java/com/android/server/policy/WindowWakeUpPolicyInternal.java
new file mode 100644
index 0000000..66a0035
--- /dev/null
+++ b/services/core/java/com/android/server/policy/WindowWakeUpPolicyInternal.java
@@ -0,0 +1,75 @@
+/*
+ * 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.policy;
+
+import android.annotation.Nullable;
+import android.os.SystemClock;
+
+import com.android.internal.annotations.Keep;
+import com.android.server.LocalServices;
+
+/** Policy controlling the decision and execution of window-related wake ups. */
+@Keep
+public interface WindowWakeUpPolicyInternal {
+
+ /**
+ * A delegate that can choose to intercept Input-related wake ups.
+ *
+ * <p>This delegate is not meant to control policy decisions on whether or not to wake up. The
+ * policy makes that decision, and forwards the wake up request to the delegate as necessary.
+ * Therefore, the role of the delegate is to handle the actual "waking" of the device in
+ * response to the respective input event.
+ */
+ @Keep
+ interface InputWakeUpDelegate {
+ /**
+ * Wakes up the device in response to a key event.
+ *
+ * @param eventTime the timestamp of the event in {@link SystemClock#uptimeMillis()}.
+ * @param keyCode the {@link android.view.KeyEvent} key code of the key event.
+ * @param isDown {@code true} if the event's action is {@link KeyEvent#ACTION_DOWN}.
+ * @return {@code true} if the delegate handled the wake up. {@code false} if the delegate
+ * decided not to handle the wake up. The policy will execute the wake up in this case.
+ */
+ boolean wakeUpFromKey(long eventTime, int keyCode, boolean isDown);
+ /**
+ * Wakes up the device in response to a motion event.
+ *
+ * @param eventTime the timestamp of the event in {@link SystemClock#uptimeMillis()}.
+ * @param source the {@link android.view.InputDevice} source that caused the event.
+ * @param isDown {@code true} if the event's action is {@link MotionEvent#ACTION_DOWN}.
+ * @return {@code true} if the delegate handled the wake up. {@code false} if the delegate
+ * decided not to handle the wake up. The policy will execute the wake up in this case.
+ */
+ boolean wakeUpFromMotion(long eventTime, int source, boolean isDown);
+ }
+
+ /**
+ * Allows injecting a delegate for controlling input-based wake ups.
+ *
+ * <p>A delegate can be injected to the policy by system_server components only, and should be
+ * done via the {@link LocalServices} interface.
+ *
+ * <p>There can at most be one active delegate. If there's no delegate set (or if a {@code null}
+ * delegate is set), the policy will handle waking up the device in response to input events.
+ *
+ * @param delegate an implementation of {@link InputWakeUpDelegate} that handles input-based
+ * wake up requests. {@code null} to let the policy handle these wake ups.
+ */
+ @Keep
+ void setInputWakeUpDelegate(@Nullable InputWakeUpDelegate delegate);
+}
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index 495e239..d0b70c3 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -410,6 +410,15 @@
}
}
+ /**
+ * Request to show the keyguard immediately without immediately locking the device.
+ */
+ public void showDismissibleKeyguard() {
+ if (mKeyguardService != null) {
+ mKeyguardService.showDismissibleKeyguard();
+ }
+ }
+
public void setCurrentUser(int newUserId) {
if (mKeyguardService != null) {
mKeyguardService.setCurrentUser(newUserId);
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
index 774e261..cd789ea 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
@@ -209,6 +209,16 @@
}
}
+ // Binder interface
+ @Override
+ public void showDismissibleKeyguard() {
+ try {
+ mService.showDismissibleKeyguard();
+ } catch (RemoteException e) {
+ Slog.w(TAG , "Remote Exception", e);
+ }
+ }
+
@Override // Binder interface
public void setSwitchingUser(boolean switching) {
try {
diff --git a/services/core/java/com/android/server/policy/window_policy_flags.aconfig b/services/core/java/com/android/server/policy/window_policy_flags.aconfig
new file mode 100644
index 0000000..ed981e0
--- /dev/null
+++ b/services/core/java/com/android/server/policy/window_policy_flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.policy"
+
+flag {
+ name: "support_input_wakeup_delegate"
+ namespace: "wear_frameworks"
+ description: "Whether or not window policy allows injecting input wake-up delegate."
+ bug: "298055811"
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 3ecc985..652cf18 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -411,6 +411,11 @@
mWakeLockLog.onWakeLockReleased(tag, ownerUid);
}
+ /** Shows the keyguard without requesting the device to immediately lock. */
+ public void showDismissibleKeyguard() {
+ mPolicy.showDismissibleKeyguard();
+ }
+
private int getBatteryStatsWakeLockMonitorType(int flags) {
switch (flags & PowerManager.WAKE_LOCK_LEVEL_MASK) {
case PowerManager.PARTIAL_WAKE_LOCK:
@@ -958,7 +963,8 @@
final boolean vibrate = Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.CHARGING_VIBRATION_ENABLED, 1, userId) != 0;
if (vibrate) {
- mVibrator.vibrate(CHARGING_VIBRATION_EFFECT,
+ mVibrator.vibrate(Process.SYSTEM_UID, mContext.getOpPackageName(),
+ CHARGING_VIBRATION_EFFECT, /* reason= */ "Charging started",
HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES);
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index ec5172f..2128c991 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -115,6 +115,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.internal.display.BrightnessSynchronizer;
+import com.android.internal.foldables.FoldGracePeriodProvider;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FrameworkStatsLog;
@@ -308,6 +309,7 @@
private final Context mContext;
private final ServiceThread mHandlerThread;
private final Handler mHandler;
+ private final FoldGracePeriodProvider mFoldGracePeriodProvider;
private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
@Nullable
private final BatterySaverStateMachine mBatterySaverStateMachine;
@@ -1007,6 +1009,10 @@
return new InattentiveSleepWarningController();
}
+ FoldGracePeriodProvider createFoldGracePeriodProvider() {
+ return new FoldGracePeriodProvider();
+ }
+
public SystemPropertiesWrapper createSystemPropertiesWrapper() {
return new SystemPropertiesWrapper() {
@Override
@@ -1147,6 +1153,7 @@
mHandler = injector.createHandler(mHandlerThread.getLooper(),
new PowerManagerHandlerCallback());
mConstants = new Constants(mHandler);
+ mFoldGracePeriodProvider = injector.createFoldGracePeriodProvider();
mAmbientDisplayConfiguration = mInjector.createAmbientDisplayConfiguration(context);
mAmbientDisplaySuppressionController =
mInjector.createAmbientDisplaySuppressionController(
@@ -6933,8 +6940,15 @@
+ ") doesn't exist");
}
if ((flags & PowerManager.GO_TO_SLEEP_FLAG_SOFT_SLEEP) != 0) {
- if (powerGroup.hasWakeLockKeepingScreenOnLocked()) {
- continue;
+ if (mFoldGracePeriodProvider.isEnabled()) {
+ if (!powerGroup.hasWakeLockKeepingScreenOnLocked()) {
+ mNotifier.showDismissibleKeyguard();
+ }
+ continue; // never actually goes to sleep for SOFT_SLEEP
+ } else {
+ if (powerGroup.hasWakeLockKeepingScreenOnLocked()) {
+ continue;
+ }
}
}
if (isNoDoze) {
diff --git a/services/core/java/com/android/server/trust/TEST_MAPPING b/services/core/java/com/android/server/trust/TEST_MAPPING
index fa46acd..0de7c28 100644
--- a/services/core/java/com/android/server/trust/TEST_MAPPING
+++ b/services/core/java/com/android/server/trust/TEST_MAPPING
@@ -12,6 +12,19 @@
]
}
],
+ "postsubmit": [
+ {
+ "name": "FrameworksMockingServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.trust"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ],
"trust-tablet": [
{
"name": "TrustTests",
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 9a85c42..5d716fc 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -74,6 +74,7 @@
import android.view.WindowManagerGlobal;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.util.DumpUtils;
@@ -1439,6 +1440,13 @@
if (biometricManager == null) {
return new long[0];
}
+ if (android.security.Flags.fixUnlockedDeviceRequiredKeysV2()
+ && mLockPatternUtils.isProfileWithUnifiedChallenge(userId)) {
+ // Profiles with unified challenge have their own set of biometrics, but the device
+ // unlock happens via the parent user. In this case Keystore needs to be given the list
+ // of biometric SIDs from the parent user, not the profile.
+ userId = resolveProfileParent(userId);
+ }
return biometricManager.getAuthenticatorIds(userId);
}
@@ -1758,6 +1766,11 @@
}
};
+ @VisibleForTesting
+ void waitForIdle() {
+ mHandler.runWithScissors(() -> {}, 0);
+ }
+
private boolean isTrustUsuallyManagedInternal(int userId) {
synchronized (mTrustUsuallyManagedForUser) {
int i = mTrustUsuallyManagedForUser.indexOfKey(userId);
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 1577cef..2d584c4 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -98,6 +98,8 @@
import android.view.animation.Interpolator;
import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.TraceBuffer;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -297,6 +299,18 @@
}
}
+ /** It is only used by unit test. */
+ @VisibleForTesting
+ Surface forceShowMagnifierSurface(int displayId) {
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ displayMagnifier.mMagnifedViewport.mWindow.setAlpha(DisplayMagnifier.MagnifiedViewport
+ .ViewportWindow.AnimationController.MAX_ALPHA);
+ return displayMagnifier.mMagnifedViewport.mWindow.mSurface;
+ }
+ return null;
+ }
+
void onWindowLayersChanged(int displayId) {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK
| FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
@@ -448,6 +462,7 @@
}
}
+ // TODO(b/318327737): Remove parameter 't' when removing flag DRAW_IN_WM_LOCK.
void drawMagnifiedRegionBorderIfNeeded(int displayId, SurfaceControl.Transaction t) {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
mAccessibilityTracing.logTrace(
@@ -1106,11 +1121,19 @@
}
void setMagnifiedRegionBorderShown(boolean shown, boolean animate) {
- if (shown) {
+ if (ViewportWindow.DRAW_IN_WM_LOCK) {
+ if (shown) {
+ mFullRedrawNeeded = true;
+ mOldMagnificationRegion.set(0, 0, 0, 0);
+ }
+ mWindow.setShown(shown, animate);
+ return;
+ }
+ if (mWindow.setShown(shown, animate)) {
mFullRedrawNeeded = true;
+ // Clear the old region, so recomputeBounds will refresh the current region.
mOldMagnificationRegion.set(0, 0, 0, 0);
}
- mWindow.setShown(shown, animate);
}
void getMagnifiedFrameInContentCoords(Rect rect) {
@@ -1130,7 +1153,11 @@
void drawWindowIfNeeded(SurfaceControl.Transaction t) {
recomputeBounds();
- mWindow.drawIfNeeded(t);
+ if (ViewportWindow.DRAW_IN_WM_LOCK) {
+ mWindow.drawOrRemoveIfNeeded(t);
+ return;
+ }
+ mWindow.postDrawIfNeeded();
}
void destroyWindow() {
@@ -1158,23 +1185,28 @@
mWindow.dump(pw, prefix);
}
- private final class ViewportWindow {
+ private final class ViewportWindow implements Runnable {
private static final String SURFACE_TITLE = "Magnification Overlay";
+ // TODO(b/318327737): Remove if it is stable.
+ static final boolean DRAW_IN_WM_LOCK = !Flags.drawMagnifierBorderOutsideWmlock();
private final Region mBounds = new Region();
private final Rect mDirtyRect = new Rect();
private final Paint mPaint = new Paint();
private final SurfaceControl mSurfaceControl;
+ /** After initialization, it should only be accessed from animation thread. */
+ private final SurfaceControl.Transaction mTransaction;
private final BLASTBufferQueue mBlastBufferQueue;
private final Surface mSurface;
private final AnimationController mAnimationController;
private boolean mShown;
+ private boolean mLastSurfaceShown;
private int mAlpha;
- private boolean mInvalidated;
+ private volatile boolean mInvalidated;
ViewportWindow(Context context) {
SurfaceControl surfaceControl = null;
@@ -1202,6 +1234,7 @@
InputMonitor.setTrustedOverlayInputInfo(mSurfaceControl, t,
mDisplayContent.getDisplayId(), "Magnification Overlay");
t.apply();
+ mTransaction = t;
mSurface = mBlastBufferQueue.createSurface();
mAnimationController = new AnimationController(context,
@@ -1219,10 +1252,11 @@
mInvalidated = true;
}
- void setShown(boolean shown, boolean animate) {
+ /** Returns {@code true} if the state is changed to shown. */
+ boolean setShown(boolean shown, boolean animate) {
synchronized (mService.mGlobalLock) {
if (mShown == shown) {
- return;
+ return false;
}
mShown = shown;
mAnimationController.onFrameShownStateChanged(shown, animate);
@@ -1230,6 +1264,7 @@
Slog.i(LOG_TAG, "ViewportWindow shown: " + mShown);
}
}
+ return shown;
}
@SuppressWarnings("unused")
@@ -1285,7 +1320,22 @@
mService.scheduleAnimationLocked();
}
- void drawIfNeeded(SurfaceControl.Transaction t) {
+ void postDrawIfNeeded() {
+ if (mInvalidated) {
+ mService.mAnimationHandler.post(this);
+ }
+ }
+
+ @Override
+ public void run() {
+ drawOrRemoveIfNeeded(mTransaction);
+ }
+
+ /**
+ * This method must only be called by animation handler directly to make sure
+ * thread safe and there is no lock held outside.
+ */
+ private void drawOrRemoveIfNeeded(SurfaceControl.Transaction t) {
// Drawing variables (alpha, dirty rect, and bounds) access is synchronized
// using WindowManagerGlobalLock. Grab copies of these values before
// drawing on the canvas so that drawing can be performed outside of the lock.
@@ -1293,6 +1343,14 @@
Rect drawingRect = null;
Region drawingBounds = null;
synchronized (mService.mGlobalLock) {
+ if (!DRAW_IN_WM_LOCK && mBlastBufferQueue.mNativeObject == 0) {
+ // Complete removal since releaseSurface has been called.
+ if (mSurface.isValid()) {
+ mTransaction.remove(mSurfaceControl).apply();
+ mSurface.release();
+ }
+ return;
+ }
if (!mInvalidated) {
return;
}
@@ -1314,6 +1372,7 @@
}
}
+ final boolean showSurface;
// Draw without holding WindowManagerGlobalLock.
if (alpha > 0) {
Canvas canvas = null;
@@ -1329,18 +1388,38 @@
mPaint.setAlpha(alpha);
canvas.drawPath(drawingBounds.getBoundaryPath(), mPaint);
mSurface.unlockCanvasAndPost(canvas);
- t.show(mSurfaceControl);
+ if (DRAW_IN_WM_LOCK) {
+ t.show(mSurfaceControl);
+ return;
+ }
+ showSurface = true;
} else {
- t.hide(mSurfaceControl);
+ if (DRAW_IN_WM_LOCK) {
+ t.hide(mSurfaceControl);
+ return;
+ }
+ showSurface = false;
+ }
+
+ if (showSurface && !mLastSurfaceShown) {
+ mTransaction.show(mSurfaceControl).apply();
+ mLastSurfaceShown = true;
+ } else if (!showSurface && mLastSurfaceShown) {
+ mTransaction.hide(mSurfaceControl).apply();
+ mLastSurfaceShown = false;
}
}
+ @GuardedBy("mService.mGlobalLock")
void releaseSurface() {
- if (mBlastBufferQueue != null) {
- mBlastBufferQueue.destroy();
+ mBlastBufferQueue.destroy();
+ if (DRAW_IN_WM_LOCK) {
+ mService.mTransactionFactory.get().remove(mSurfaceControl).apply();
+ mSurface.release();
+ return;
}
- mService.mTransactionFactory.get().remove(mSurfaceControl).apply();
- mSurface.release();
+ // Post to perform cleanup on the thread which handles mSurface.
+ mService.mAnimationHandler.post(this);
}
void dump(PrintWriter pw, String prefix) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index b8a92bb..3a792d0 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -183,6 +183,8 @@
import static com.android.server.wm.ActivityRecordProto.REPORTED_DRAWN;
import static com.android.server.wm.ActivityRecordProto.REPORTED_VISIBLE;
import static com.android.server.wm.ActivityRecordProto.SHOULD_FORCE_ROTATE_FOR_CAMERA_COMPAT;
+import static com.android.server.wm.ActivityRecordProto.SHOULD_IGNORE_ORIENTATION_REQUEST_LOOP;
+import static com.android.server.wm.ActivityRecordProto.SHOULD_OVERRIDE_FORCE_RESIZE_APP;
import static com.android.server.wm.ActivityRecordProto.SHOULD_OVERRIDE_MIN_ASPECT_RATIO;
import static com.android.server.wm.ActivityRecordProto.SHOULD_REFRESH_ACTIVITY_FOR_CAMERA_COMPAT;
import static com.android.server.wm.ActivityRecordProto.SHOULD_REFRESH_ACTIVITY_VIA_PAUSE_FOR_CAMERA_COMPAT;
@@ -2179,7 +2181,8 @@
// The result receiver is the transition receiver, which will handle the shared element
// exit transition.
mHasSceneTransition = options.getAnimationType() == ANIM_SCENE_TRANSITION
- && options.getResultReceiver() != null;
+ && options.getSceneTransitionInfo() != null
+ && options.getSceneTransitionInfo().getResultReceiver() != null;
final PendingIntent usageReport = options.getUsageTimeReport();
if (usageReport != null) {
appTimeTracker = new AppTimeTracker(usageReport);
@@ -5183,16 +5186,15 @@
return mPendingOptions;
}
- ActivityOptions takeOptions() {
- if (DEBUG_TRANSITION) Slog.i(TAG, "Taking options for " + this + " callers="
- + Debug.getCallers(6));
+ ActivityOptions.SceneTransitionInfo takeSceneTransitionInfo() {
+ if (DEBUG_TRANSITION) {
+ Slog.i(TAG, "Taking SceneTransitionInfo for " + this + " callers="
+ + Debug.getCallers(6));
+ }
if (mPendingOptions == null) return null;
final ActivityOptions opts = mPendingOptions;
mPendingOptions = null;
- // Strip sensitive information from options before sending it to app.
- opts.setRemoteTransition(null);
- opts.setRemoteAnimationAdapter(null);
- return opts;
+ return opts.getSceneTransitionInfo();
}
RemoteTransition takeRemoteTransition() {
@@ -6158,7 +6160,7 @@
try {
mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
- StartActivityItem.obtain(token, takeOptions()));
+ StartActivityItem.obtain(token, takeSceneTransitionInfo()));
} catch (Exception e) {
Slog.w(TAG, "Exception thrown sending start: " + intent.getComponent(), e);
}
@@ -6263,8 +6265,11 @@
void handleAlreadyVisible() {
try {
- if (returningOptions != null) {
- app.getThread().scheduleOnNewActivityOptions(token, returningOptions.toBundle());
+ if (returningOptions != null
+ && returningOptions.getAnimationType() == ANIM_SCENE_TRANSITION
+ && returningOptions.getSceneTransitionInfo() != null) {
+ app.getThread().scheduleOnNewSceneTransitionInfo(token,
+ returningOptions.getSceneTransitionInfo());
}
} catch(RemoteException e) {
}
@@ -6613,10 +6618,6 @@
return hasProcess() && !app.isCrashing() && !app.isNotResponding();
}
- void startFreezingScreenLocked(int configChanges) {
- startFreezingScreenLocked(app, configChanges);
- }
-
void startFreezingScreenLocked(WindowProcessController app, int configChanges) {
if (mayFreezeScreenLocked(app)) {
if (getParent() == null) {
@@ -8100,12 +8101,8 @@
* Set the last reported configuration to the client. Should be called whenever
* a new merged configuration is sent to the client for this activity.
*/
- void setLastReportedConfiguration(@NonNull MergedConfiguration config) {
- setLastReportedConfiguration(config.getGlobalConfiguration(),
- config.getOverrideConfiguration());
- }
-
- private void setLastReportedConfiguration(Configuration global, Configuration override) {
+ void setLastReportedConfiguration(@NonNull Configuration global,
+ @NonNull Configuration override) {
mLastReportedConfiguration.setConfiguration(global, override);
}
@@ -9639,7 +9636,7 @@
configChangeFlags |= changes;
if (mVisible && mAtmService.mTmpUpdateConfigurationResult.mIsUpdating
&& !mTransitionController.isShellTransitionsEnabled()) {
- startFreezingScreenLocked(mAtmService.mTmpUpdateConfigurationResult.changes);
+ startFreezingScreenLocked(app, mAtmService.mTmpUpdateConfigurationResult.changes);
}
final boolean displayMayChange = mTmpConfig.windowConfiguration.getDisplayRotation()
!= getWindowConfiguration().getDisplayRotation()
@@ -10337,6 +10334,10 @@
mLetterboxUiController.shouldRefreshActivityViaPauseForCameraCompat());
proto.write(SHOULD_OVERRIDE_MIN_ASPECT_RATIO,
mLetterboxUiController.shouldOverrideMinAspectRatio());
+ proto.write(SHOULD_IGNORE_ORIENTATION_REQUEST_LOOP,
+ mLetterboxUiController.shouldIgnoreOrientationRequestLoop());
+ proto.write(SHOULD_OVERRIDE_FORCE_RESIZE_APP,
+ mLetterboxUiController.shouldOverrideForceResizeApp());
}
@Override
@@ -10624,7 +10625,8 @@
boolean isSyncFinished(BLASTSyncEngine.SyncGroup group) {
if (task != null && task.mSharedStartingData != null) {
final WindowState startingWin = task.topStartingWindow();
- if (startingWin != null && startingWin.isSyncFinished(group)) {
+ if (startingWin != null && startingWin.mSyncState == SYNC_STATE_READY
+ && mDisplayContent.mUnknownAppVisibilityController.allResolved()) {
// The sync is ready if a drawn starting window covered the task.
return true;
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 1872f6e..ec0e3e7 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -134,7 +134,6 @@
import android.os.WorkSource;
import android.provider.MediaStore;
import android.util.ArrayMap;
-import android.util.MergedConfiguration;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -820,8 +819,6 @@
proc.pauseConfigurationDispatch();
try {
- r.startFreezingScreenLocked(proc, 0);
-
// schedule launch ticks to collect information about slow apps.
r.startLaunchTickingLocked();
r.lastLaunchTime = SystemClock.uptimeMillis();
@@ -908,13 +905,9 @@
r.intent.getComponent().getPackageName(), NOTIFY_PACKAGE_USE_ACTIVITY);
mService.getAppWarningsLocked().onStartActivity(r);
- // Because we could be starting an Activity in the system process this may not go
- // across a Binder interface which would create a new Configuration. Consequently
- // we have to always create a new Configuration here.
final Configuration procConfig = proc.prepareConfigurationForLaunchingActivity();
- final MergedConfiguration mergedConfiguration = new MergedConfiguration(
- procConfig, r.getMergedOverrideConfiguration());
- r.setLastReportedConfiguration(mergedConfiguration);
+ final Configuration overrideConfig = r.getMergedOverrideConfiguration();
+ r.setLastReportedConfiguration(procConfig, overrideConfig);
logIfTransactionTooLarge(r.intent, r.getSavedState());
@@ -932,13 +925,10 @@
final int deviceId = getDeviceIdForDisplayId(r.getDisplayId());
final LaunchActivityItem launchActivityItem = LaunchActivityItem.obtain(r.token,
r.intent, System.identityHashCode(r), r.info,
- // TODO: Have this take the merged configuration instead of separate global
- // and override configs.
- mergedConfiguration.getGlobalConfiguration(),
- mergedConfiguration.getOverrideConfiguration(), deviceId,
+ procConfig, overrideConfig, deviceId,
r.getFilteredReferrer(r.launchedFromPackage), task.voiceInteractor,
proc.getReportedProcState(), r.getSavedState(), r.getPersistentSavedState(),
- results, newIntents, r.takeOptions(), isTransitionForward,
+ results, newIntents, r.takeSceneTransitionInfo(), isTransitionForward,
proc.createProfilerInfoIfNeeded(), r.assistToken, activityClientController,
r.shareableActivityToken, r.getLaunchedFromBubble(), fragmentToken);
diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
index 975fdc0..0f9e5b0 100644
--- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
+++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
@@ -179,6 +179,12 @@
if (physicalDisplayUpdated) {
onDisplayUpdated(transition, fromRotation, startBounds);
} else {
+ final TransitionRequestInfo.DisplayChange displayChange =
+ getCurrentDisplayChange(fromRotation, startBounds);
+ mDisplayContent.mTransitionController.requestStartTransition(transition,
+ /* startTask= */ null, /* remoteTransition= */ null, displayChange);
+ mDisplayContent.mTransitionController.setDisplaySyncMethod(displayChange,
+ mDisplayContent);
transition.setAllReady();
}
});
@@ -204,15 +210,8 @@
return new DisplayInfo(mNonOverrideDisplayInfo);
}
- /**
- * Called when physical display is updated, this could happen e.g. on foldable
- * devices when the physical underlying display is replaced. This method should be called
- * when the new display info is already applied to the WM hierarchy.
- *
- * @param fromRotation rotation before the display change
- * @param startBounds display bounds before the display change
- */
- private void onDisplayUpdated(@NonNull Transition transition, int fromRotation,
+ @NonNull
+ private TransitionRequestInfo.DisplayChange getCurrentDisplayChange(int fromRotation,
@NonNull Rect startBounds) {
final Rect endBounds = new Rect(0, 0, mDisplayContent.mInitialDisplayWidth,
mDisplayContent.mInitialDisplayHeight);
@@ -224,6 +223,23 @@
displayChange.setEndAbsBounds(endBounds);
displayChange.setStartRotation(fromRotation);
displayChange.setEndRotation(toRotation);
+ return displayChange;
+ }
+
+ /**
+ * Called when physical display is updated, this could happen e.g. on foldable
+ * devices when the physical underlying display is replaced. This method should be called
+ * when the new display info is already applied to the WM hierarchy.
+ *
+ * @param fromRotation rotation before the display change
+ * @param startBounds display bounds before the display change
+ */
+ private void onDisplayUpdated(@NonNull Transition transition, int fromRotation,
+ @NonNull Rect startBounds) {
+ final int toRotation = mDisplayContent.getRotation();
+
+ final TransitionRequestInfo.DisplayChange displayChange =
+ getCurrentDisplayChange(fromRotation, startBounds);
displayChange.setPhysicalDisplayChanged(true);
mDisplayContent.mTransitionController.requestStartTransition(transition,
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 460a68f..63ca592 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -40,6 +40,7 @@
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_CONSUME_IME_INSETS;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
@@ -959,15 +960,17 @@
case TYPE_BASE_APPLICATION:
- // A non-translucent main app window isn't allowed to fit insets, as it would create
- // a hole on the display!
+ // A non-translucent main app window isn't allowed to fit insets or display cutouts,
+ // as it would create a hole on the display!
if (attrs.isFullscreen() && win.mActivityRecord != null
&& win.mActivityRecord.fillsParent()
- && (win.mAttrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0
- && attrs.getFitInsetsTypes() != 0) {
+ && (attrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0
+ && (attrs.getFitInsetsTypes() != 0
+ || (attrs.privateFlags & PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED) != 0
+ && attrs.layoutInDisplayCutoutMode
+ != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS)) {
throw new IllegalArgumentException("Illegal attributes: Main activity window"
- + " that isn't translucent trying to fit insets: "
- + attrs.getFitInsetsTypes()
+ + " that isn't translucent trying to fit insets or display cutouts."
+ " attrs=" + attrs);
}
break;
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 68bd326..47972b3 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -513,7 +513,6 @@
* timer and activity is not letterboxed for fixed orientation
* </ul>
*/
- @VisibleForTesting
boolean shouldIgnoreOrientationRequestLoop() {
if (!shouldEnableWithOptInOverrideAndOptOutProperty(
/* gatingCondition */ mLetterboxConfiguration
diff --git a/services/core/java/com/android/server/wm/SensitiveContentPackages.java b/services/core/java/com/android/server/wm/SensitiveContentPackages.java
new file mode 100644
index 0000000..3862b82
--- /dev/null
+++ b/services/core/java/com/android/server/wm/SensitiveContentPackages.java
@@ -0,0 +1,83 @@
+/*
+ * 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.wm;
+
+import android.annotation.NonNull;
+import android.util.ArraySet;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Cache of distinct package/uid pairs that require being blocked from screen capture. This class is
+ * not threadsafe and any call site should hold {@link WindowManagerGlobalLock}
+ */
+public class SensitiveContentPackages {
+ private final ArraySet<PackageInfo> mProtectedPackages = new ArraySet<>();
+
+ /** Returns {@code true} if package/uid pair should be blocked from screen capture */
+ public boolean shouldBlockScreenCaptureForApp(String pkg, int uid) {
+ for (int i = 0; i < mProtectedPackages.size(); i++) {
+ PackageInfo info = mProtectedPackages.valueAt(i);
+ if (info != null && info.mPkg.equals(pkg) && info.mUid == uid) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Replaces the set of package/uid pairs to set that should be blocked from screen capture */
+ public void setShouldBlockScreenCaptureForApp(@NonNull Set<PackageInfo> packageInfos) {
+ mProtectedPackages.clear();
+ mProtectedPackages.addAll(packageInfos);
+ }
+
+ void dump(PrintWriter pw) {
+ final String innerPrefix = " ";
+ pw.println("SensitiveContentPackages:");
+ pw.println(innerPrefix + "Packages that should block screen capture ("
+ + mProtectedPackages.size() + "):");
+ for (PackageInfo info : mProtectedPackages) {
+ pw.println(innerPrefix + " package=" + info.mPkg + " uid=" + info.mUid);
+ }
+ }
+
+ /** Helper class that represents a package/uid pair */
+ public static class PackageInfo {
+ private String mPkg;
+ private int mUid;
+
+ public PackageInfo(String pkg, int uid) {
+ this.mPkg = pkg;
+ this.mUid = uid;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof PackageInfo)) return false;
+ PackageInfo that = (PackageInfo) o;
+ return mUid == that.mUid && Objects.equals(mPkg, that.mPkg);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPkg, mUid);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 3b06343..a7a6bf2 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3699,6 +3699,16 @@
}
wc.assignLayer(t, layer++);
+ // Boost the adjacent TaskFragment for dimmer if needed.
+ final TaskFragment taskFragment = wc.asTaskFragment();
+ if (taskFragment != null && taskFragment.isEmbedded()
+ && taskFragment.isVisibleRequested()) {
+ final TaskFragment adjacentTf = taskFragment.getAdjacentTaskFragment();
+ if (adjacentTf != null && adjacentTf.shouldBoostDimmer()) {
+ adjacentTf.assignLayer(t, layer++);
+ }
+ }
+
// Place the decor surface just above the owner TaskFragment.
if (mDecorSurfaceContainer != null && !decorSurfacePlaced
&& wc == mDecorSurfaceContainer.mOwnerTaskFragment) {
@@ -4785,6 +4795,7 @@
}
if (top.isAttached()) {
top.setWindowingMode(WINDOWING_MODE_UNDEFINED);
+ top.mWaitForEnteringPinnedMode = false;
}
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 93cce2a..f56759f 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
@@ -38,6 +39,7 @@
import static android.os.Process.SYSTEM_UID;
import static android.os.UserHandle.USER_NULL;
import static android.view.Display.INVALID_DISPLAY;
+import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
import static android.view.WindowManager.TRANSIT_NONE;
@@ -57,6 +59,7 @@
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TRANSITION;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.ActivityTaskManagerService.checkPermission;
import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity;
import static com.android.server.wm.IdentifierProto.HASH_CODE;
import static com.android.server.wm.IdentifierProto.TITLE;
@@ -80,7 +83,9 @@
import android.app.servertransaction.NewIntentItem;
import android.app.servertransaction.PauseActivityItem;
import android.app.servertransaction.ResumeActivityItem;
+import android.content.PermissionChecker;
import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
@@ -744,7 +749,17 @@
// The system is trusted to embed other apps securely and for all users.
return UserHandle.getAppId(uid) == SYSTEM_UID
// Activities from the same UID can be embedded freely by the host.
- || a.isUid(uid);
+ || a.isUid(uid)
+ // Apps which have the signature MANAGE_ACTIVITY_TASK permission are trusted.
+ || hasManageTaskPermission(uid);
+ }
+
+ /**
+ * Checks if a particular app uid has the {@link MANAGE_ACTIVITY_TASKS} permission.
+ */
+ private static boolean hasManageTaskPermission(int uid) {
+ return checkPermission(MANAGE_ACTIVITY_TASKS, PermissionChecker.PID_UNKNOWN, uid)
+ == PackageManager.PERMISSION_GRANTED;
}
/**
@@ -2981,6 +2996,30 @@
}, false /* traverseTopToBottom */);
}
+ boolean shouldBoostDimmer() {
+ if (asTask() != null || !isDimmingOnParentTask()) {
+ // early return if not embedded or should not dim on parent Task.
+ return false;
+ }
+
+ final TaskFragment adjacentTf = getAdjacentTaskFragment();
+ if (adjacentTf == null) {
+ // early return if no adjacent TF.
+ return false;
+ }
+
+ if (getParent().mChildren.indexOf(adjacentTf) < getParent().mChildren.indexOf(this)) {
+ // early return if this TF already has higher z-ordering.
+ return false;
+ }
+
+ // boost if there's an Activity window that has FLAG_DIM_BEHIND flag.
+ return forAllWindows(
+ (w) -> (w.mAttrs.flags & FLAG_DIM_BEHIND) != 0 && w.mActivityRecord != null
+ && w.mActivityRecord.isEmbedded() && (w.mActivityRecord.isVisibleRequested()
+ || w.mActivityRecord.isVisible()), true);
+ }
+
@Override
Dimmer getDimmer() {
// If this is in an embedded TaskFragment and we want the dim applies on the TaskFragment.
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index c3aca6f..708d63e 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -634,8 +634,8 @@
}
/** Sets the sync method for the display change. */
- private void setDisplaySyncMethod(@NonNull TransitionRequestInfo.DisplayChange displayChange,
- @NonNull Transition displayTransition, @NonNull DisplayContent displayContent) {
+ void setDisplaySyncMethod(@NonNull TransitionRequestInfo.DisplayChange displayChange,
+ @NonNull DisplayContent displayContent) {
final Rect startBounds = displayChange.getStartAbsBounds();
final Rect endBounds = displayChange.getEndAbsBounds();
if (startBounds == null || endBounds == null) return;
@@ -686,7 +686,7 @@
trigger != null ? trigger.asTask() : null, remoteTransition, displayChange);
if (newTransition != null && displayChange != null && trigger != null
&& trigger.asDisplayContent() != null) {
- setDisplaySyncMethod(displayChange, newTransition, trigger.asDisplayContent());
+ setDisplaySyncMethod(displayChange, trigger.asDisplayContent());
}
}
if (trigger != null) {
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 33ef3c5..a9f0554 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -183,18 +183,21 @@
&& (mWallpaperTarget == w || w.isDrawFinishedLw())) {
if (DEBUG_WALLPAPER) Slog.v(TAG, "Found wallpaper target: " + w);
mFindResults.setWallpaperTarget(w);
+ mFindResults.setIsWallpaperTargetForLetterbox(w.hasWallpaperForLetterboxBackground());
if (w == mWallpaperTarget && w.isAnimating(TRANSITION | PARENTS)) {
// The current wallpaper target is animating, so we'll look behind it for
// another possible target and figure out what is going on later.
if (DEBUG_WALLPAPER) Slog.v(TAG,
"Win " + w + ": token animating, looking behind.");
}
- mFindResults.setIsWallpaperTargetForLetterbox(w.hasWallpaperForLetterboxBackground());
// While the keyguard is going away, both notification shade and a normal activity such
// as a launcher can satisfy criteria for a wallpaper target. In this case, we should
// chose the normal activity, otherwise wallpaper becomes invisible when a new animation
// starts before the keyguard going away animation finishes.
- return w.mActivityRecord != null;
+ if (w.mActivityRecord == null && mDisplayContent.isKeyguardGoingAway()) {
+ return false;
+ }
+ return true;
}
return false;
};
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index e28262d..bdea1bc 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -3938,7 +3938,13 @@
@Nullable
BLASTSyncEngine.SyncGroup getSyncGroup() {
if (mSyncGroup != null) return mSyncGroup;
- if (mParent != null) return mParent.getSyncGroup();
+ WindowContainer<?> parent = mParent;
+ while (parent != null) {
+ if (parent.mSyncGroup != null) {
+ return parent.mSyncGroup;
+ }
+ parent = parent.mParent;
+ }
return null;
}
@@ -3972,7 +3978,7 @@
* @param cancel If true, this is being finished because it is leaving the sync group rather
* than due to the sync group completing.
*/
- void finishSync(Transaction outMergedTransaction, BLASTSyncEngine.SyncGroup group,
+ void finishSync(Transaction outMergedTransaction, @Nullable BLASTSyncEngine.SyncGroup group,
boolean cancel) {
if (mSyncState == SYNC_STATE_NONE) return;
final BLASTSyncEngine.SyncGroup syncGroup = getSyncGroup();
@@ -4000,7 +4006,8 @@
if (!isVisibleRequested()) {
return true;
}
- if (mSyncState == SYNC_STATE_NONE) {
+ if (mSyncState == SYNC_STATE_NONE && getSyncGroup() != null) {
+ Slog.i(TAG, "prepareSync in isSyncFinished: " + this);
prepareSync();
}
if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW) {
@@ -4059,16 +4066,18 @@
}
if (newParent == null) {
// This is getting removed.
+ final BLASTSyncEngine.SyncGroup syncGroup = getSyncGroup();
if (oldParent.mSyncState != SYNC_STATE_NONE) {
// In order to keep the transaction in sync, merge it into the parent.
- finishSync(oldParent.mSyncTransaction, getSyncGroup(), true /* cancel */);
- } else if (mSyncGroup != null) {
- // This is watched directly by the sync-group, so merge this transaction into
- // into the sync-group so it isn't lost
- finishSync(mSyncGroup.getOrphanTransaction(), mSyncGroup, true /* cancel */);
+ finishSync(oldParent.mSyncTransaction, syncGroup, true /* cancel */);
+ } else if (syncGroup != null) {
+ // This is watched by the sync-group, so merge this transaction into the
+ // sync-group for not losing the operations in the transaction.
+ finishSync(syncGroup.getOrphanTransaction(), syncGroup, true /* cancel */);
} else {
- throw new IllegalStateException("This container is in sync mode without a sync"
- + " group: " + this);
+ Slog.wtf(TAG, this + " is in sync mode without a sync group");
+ // Make sure the removal transaction take effect.
+ finishSync(getPendingTransaction(), null /* group */, true /* cancel */);
}
return;
} else if (mSyncGroup == null) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 516d37c..22b690e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -51,6 +51,7 @@
import com.android.internal.policy.KeyInterceptionInfo;
import com.android.server.input.InputManagerService;
import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.wm.SensitiveContentPackages.PackageInfo;
import java.lang.annotation.Retention;
import java.util.List;
@@ -1012,4 +1013,12 @@
*/
public abstract void setOrientationRequestPolicy(boolean respected,
int[] fromOrientations, int[] toOrientations);
+
+ /**
+ * Set whether screen capture should be disabled for all windows of a specific app windows based
+ * on sensitive content protections.
+ *
+ * @param packageInfos set of {@link PackageInfo} whose windows should be blocked from capture
+ */
+ public abstract void setShouldBlockScreenCaptureForApp(@NonNull Set<PackageInfo> packageInfos);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 502912a..c63cc43 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -123,6 +123,7 @@
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS;
+import static com.android.server.wm.SensitiveContentPackages.PackageInfo;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
@@ -312,6 +313,7 @@
import android.window.WindowContextInfo;
import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IResultReceiver;
import com.android.internal.policy.IKeyguardDismissCallback;
@@ -366,6 +368,7 @@
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
@@ -1053,6 +1056,9 @@
SystemPerformanceHinter mSystemPerformanceHinter;
+ @GuardedBy("mGlobalLock")
+ final SensitiveContentPackages mSensitiveContentPackages = new SensitiveContentPackages();
+
/** Listener to notify activity manager about app transitions. */
final WindowManagerInternal.AppTransitionListener mActivityManagerAppTransitionNotifier
= new WindowManagerInternal.AppTransitionListener() {
@@ -1797,7 +1803,12 @@
// Don't do layout here, the window must call
// relayout to be displayed, so we'll do it there.
- win.getParent().assignChildLayers();
+ if (win.mActivityRecord != null && win.mActivityRecord.isEmbedded()) {
+ // Assign child layers from the parent Task if the Activity is embedded.
+ win.getTask().assignChildLayers();
+ } else {
+ win.getParent().assignChildLayers();
+ }
if (focusChanged) {
displayContent.getInputMonitor().setInputFocusLw(displayContent.mCurrentFocus,
@@ -1931,12 +1942,13 @@
/**
* Set whether screen capture is disabled for all windows of a specific user from
- * the device policy cache.
+ * the device policy cache, or specific windows based on sensitive content protections.
*/
@Override
public void refreshScreenCaptureDisabled() {
int callingUid = Binder.getCallingUid();
- if (callingUid != SYSTEM_UID) {
+ // MY_UID (Process.myUid()) should always be SYSTEM_UID here, but using MY_UID for tests
+ if (callingUid != MY_UID) {
throw new SecurityException("Only system can call refreshScreenCaptureDisabled.");
}
@@ -7169,6 +7181,7 @@
}
mSystemPerformanceHinter.dump(pw, "");
mTrustedPresentationListenerController.dump(pw);
+ mSensitiveContentPackages.dump(pw);
}
}
@@ -8550,6 +8563,14 @@
InputTarget inputTarget = WindowManagerService.this.getInputTargetFromToken(inputToken);
return inputTarget == null ? null : inputTarget.getWindowToken();
}
+
+ @Override
+ public void setShouldBlockScreenCaptureForApp(Set<PackageInfo> packageInfos) {
+ synchronized (mGlobalLock) {
+ mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(packageInfos);
+ WindowManagerService.this.refreshScreenCaptureDisabled();
+ }
+ }
}
private final class ImeTargetVisibilityPolicyImpl extends ImeTargetVisibilityPolicy {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 0b43be7..56f2bc3 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1896,6 +1896,14 @@
if ((mAttrs.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
return true;
}
+
+ if (com.android.server.notification.Flags.sensitiveNotificationAppProtection()) {
+ if (mWmService.mSensitiveContentPackages
+ .shouldBlockScreenCaptureForApp(getOwningPackage(), getOwningUid())) {
+ return true;
+ }
+ }
+
return !DevicePolicyCache.getInstance().isScreenCaptureAllowed(mShowUserId);
}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index dfa9dce..775570c 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -37,6 +37,7 @@
"com_android_server_adb_AdbDebuggingManager.cpp",
"com_android_server_am_BatteryStatsService.cpp",
"com_android_server_biometrics_SurfaceToNativeHandleConverter.cpp",
+ "com_android_server_BootReceiver.cpp",
"com_android_server_ConsumerIrService.cpp",
"com_android_server_companion_virtual_InputController.cpp",
"com_android_server_devicepolicy_CryptoTestHelper.cpp",
@@ -94,6 +95,16 @@
header_libs: [
"bionic_libc_platform_headers",
],
+
+ static_libs: [
+ "libunwindstack",
+ ],
+
+ whole_static_libs: [
+ "libdebuggerd_tombstone_proto_to_text",
+ ],
+
+ runtime_libs: ["libdexfile"],
}
cc_defaults {
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index cc08488..15eb7c6 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -33,3 +33,7 @@
# Bug component : 158088 = per-file *AnrTimer*
per-file *AnrTimer* = file:/PERFORMANCE_OWNERS
+
+# Bug component : 158088 = per-file com_android_server_utils_AnrTimer*.java
+per-file com_android_server_utils_AnrTimer*.java = file:/PERFORMANCE_OWNERS
+per-file com_android_server_BootReceiver.cpp = file:/STABILITY_OWNERS
diff --git a/services/core/jni/com_android_server_BootReceiver.cpp b/services/core/jni/com_android_server_BootReceiver.cpp
new file mode 100644
index 0000000..3892d28
--- /dev/null
+++ b/services/core/jni/com_android_server_BootReceiver.cpp
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+
+#include <libdebuggerd/tombstone.h>
+#include <nativehelper/JNIHelp.h>
+
+#include <sstream>
+
+#include "jni.h"
+#include "tombstone.pb.h"
+
+namespace android {
+
+static void writeToString(std::stringstream& ss, const std::string& line, bool should_log) {
+ ss << line << std::endl;
+}
+
+static jstring com_android_server_BootReceiver_getTombstoneText(JNIEnv* env, jobject,
+ jbyteArray tombstoneBytes) {
+ Tombstone tombstone;
+ tombstone.ParseFromArray(env->GetByteArrayElements(tombstoneBytes, 0),
+ env->GetArrayLength(tombstoneBytes));
+
+ std::stringstream tombstoneString;
+
+ tombstone_proto_to_text(tombstone,
+ std::bind(&writeToString, std::ref(tombstoneString),
+ std::placeholders::_1, std::placeholders::_2));
+
+ return env->NewStringUTF(tombstoneString.str().c_str());
+}
+
+static const JNINativeMethod sMethods[] = {
+ /* name, signature, funcPtr */
+ {"getTombstoneText", "([B)Ljava/lang/String;",
+ (jstring*)com_android_server_BootReceiver_getTombstoneText},
+};
+
+int register_com_android_server_BootReceiver(JNIEnv* env) {
+ return jniRegisterNativeMethods(env, "com/android/server/BootReceiver", sMethods,
+ NELEM(sMethods));
+}
+
+} // namespace android
diff --git a/services/core/jni/com_android_server_utils_AnrTimer.cpp b/services/core/jni/com_android_server_utils_AnrTimer.cpp
index 97b18fa..1e48ace 100644
--- a/services/core/jni/com_android_server_utils_AnrTimer.cpp
+++ b/services/core/jni/com_android_server_utils_AnrTimer.cpp
@@ -486,9 +486,11 @@
void remove(AnrTimerService const* service) {
AutoMutex _l(lock_);
timer_id_t front = headTimerId();
- for (auto i = running_.begin(); i != running_.end(); i++) {
+ for (auto i = running_.begin(); i != running_.end(); ) {
if (i->service == service) {
- running_.erase(i);
+ i = running_.erase(i);
+ } else {
+ i++;
}
}
if (front != headTimerId()) restartLocked();
diff --git a/services/core/jni/gnss/Android.bp b/services/core/jni/gnss/Android.bp
index f3ba484f62..e72259f 100644
--- a/services/core/jni/gnss/Android.bp
+++ b/services/core/jni/gnss/Android.bp
@@ -68,5 +68,6 @@
"android.hardware.gnss@2.1",
"android.hardware.gnss.measurement_corrections@1.0",
"android.hardware.gnss.visibility_control@1.0",
+ "android_location_flags_c_lib",
],
}
diff --git a/services/core/jni/gnss/Utils.cpp b/services/core/jni/gnss/Utils.cpp
index 571534f..274e3b7 100644
--- a/services/core/jni/gnss/Utils.cpp
+++ b/services/core/jni/gnss/Utils.cpp
@@ -20,6 +20,7 @@
#include <android/hardware/gnss/1.0/IGnss.h>
#include <android/hardware/gnss/2.0/IGnss.h>
+#include <android_location_flags.h>
#include <utils/SystemClock.h>
/*
* Save a pointer to JavaVm to attach/detach threads executing
@@ -27,6 +28,8 @@
*/
JavaVM* android::ScopedJniThreadAttach::sJvm;
+namespace location_flags = android::location::flags;
+
namespace android {
namespace {
@@ -194,7 +197,12 @@
flags = static_cast<uint32_t>(location.elapsedRealtime.flags);
if (flags & android::hardware::gnss::ElapsedRealtime::HAS_TIMESTAMP_NS) {
- SET(ElapsedRealtimeNanos, location.elapsedRealtime.timestampNs);
+ if (location_flags::replace_future_elapsed_realtime_jni() &&
+ location.elapsedRealtime.timestampNs > android::elapsedRealtimeNano()) {
+ SET(ElapsedRealtimeNanos, android::elapsedRealtimeNano());
+ } else {
+ SET(ElapsedRealtimeNanos, location.elapsedRealtime.timestampNs);
+ }
} else {
SET(ElapsedRealtimeNanos, android::elapsedRealtimeNano());
}
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index f3158d1..a4b1f84 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -52,10 +52,10 @@
int register_android_server_HardwarePropertiesManagerService(JNIEnv* env);
int register_android_server_SyntheticPasswordManager(JNIEnv* env);
int register_android_hardware_display_DisplayViewport(JNIEnv* env);
-int register_android_server_utils_AnrTimer(JNIEnv *env);
int register_android_server_am_OomConnection(JNIEnv* env);
int register_android_server_am_CachedAppOptimizer(JNIEnv* env);
int register_android_server_am_LowMemDetector(JNIEnv* env);
+int register_android_server_utils_AnrTimer(JNIEnv *env);
int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(JNIEnv* env);
int register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(JNIEnv* env);
int register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(JNIEnv* env);
@@ -66,6 +66,7 @@
int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env);
int register_android_server_companion_virtual_InputController(JNIEnv* env);
int register_android_server_app_GameManagerService(JNIEnv* env);
+int register_com_android_server_BootReceiver(JNIEnv* env);
int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env);
int register_com_android_server_display_DisplayControl(JNIEnv* env);
int register_com_android_server_SystemClockTime(JNIEnv* env);
@@ -114,10 +115,10 @@
register_android_server_storage_AppFuse(env);
register_android_server_SyntheticPasswordManager(env);
register_android_hardware_display_DisplayViewport(env);
- register_android_server_utils_AnrTimer(env);
register_android_server_am_OomConnection(env);
register_android_server_am_CachedAppOptimizer(env);
register_android_server_am_LowMemDetector(env);
+ register_android_server_utils_AnrTimer(env);
register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(env);
register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(env);
register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(env);
@@ -128,6 +129,7 @@
register_android_server_sensor_SensorService(vm, env);
register_android_server_companion_virtual_InputController(env);
register_android_server_app_GameManagerService(env);
+ register_com_android_server_BootReceiver(env);
register_com_android_server_wm_TaskFpsCallbackController(env);
register_com_android_server_display_DisplayControl(env);
register_com_android_server_SystemClockTime(env);
diff --git a/services/core/xsd/display-layout-config/display-layout-config.xsd b/services/core/xsd/display-layout-config/display-layout-config.xsd
index 4e95465..73c13ce 100644
--- a/services/core/xsd/display-layout-config/display-layout-config.xsd
+++ b/services/core/xsd/display-layout-config/display-layout-config.xsd
@@ -49,7 +49,7 @@
<xs:complexType name="display">
<xs:sequence>
- <xs:element name="address" type="xs:nonNegativeInteger"/>
+ <xs:group ref="displayReference" minOccurs="1" maxOccurs="1"/>
<xs:element name="position" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="brightnessThrottlingMapId" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="powerThrottlingMapId" type="xs:string" minOccurs="0" maxOccurs="1" />
@@ -67,4 +67,11 @@
</xs:simpleType>
</xs:attribute>
</xs:complexType>
+
+ <xs:group name="displayReference">
+ <xs:choice>
+ <xs:element name="address" type="xs:nonNegativeInteger" minOccurs="0"/>
+ <xs:element name="port" type="xs:nonNegativeInteger" minOccurs="0"/>
+ </xs:choice>
+ </xs:group>
</xs:schema>
diff --git a/services/core/xsd/display-layout-config/schema/current.txt b/services/core/xsd/display-layout-config/schema/current.txt
index 195cae5..0d2f6a8 100644
--- a/services/core/xsd/display-layout-config/schema/current.txt
+++ b/services/core/xsd/display-layout-config/schema/current.txt
@@ -3,22 +3,24 @@
public class Display {
ctor public Display();
- method public java.math.BigInteger getAddress();
+ method public java.math.BigInteger getAddress_optional();
method public String getBrightnessThrottlingMapId();
method public String getDisplayGroup();
method public java.math.BigInteger getLeadDisplayAddress();
+ method public java.math.BigInteger getPort_optional();
method public String getPosition();
method public String getPowerThrottlingMapId();
method public String getRefreshRateThermalThrottlingMapId();
method public String getRefreshRateZoneId();
method public boolean isDefaultDisplay();
method public boolean isEnabled();
- method public void setAddress(java.math.BigInteger);
+ method public void setAddress_optional(java.math.BigInteger);
method public void setBrightnessThrottlingMapId(String);
method public void setDefaultDisplay(boolean);
method public void setDisplayGroup(String);
method public void setEnabled(boolean);
method public void setLeadDisplayAddress(java.math.BigInteger);
+ method public void setPort_optional(java.math.BigInteger);
method public void setPosition(String);
method public void setPowerThrottlingMapId(String);
method public void setRefreshRateThermalThrottlingMapId(String);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index e0232b1..14dc0eb 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -32,6 +32,7 @@
import android.annotation.Nullable;
import android.app.AppGlobals;
import android.app.BroadcastOptions;
+import android.app.admin.BooleanPolicyValue;
import android.app.admin.DevicePolicyIdentifiers;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyState;
@@ -142,6 +143,67 @@
mAdminPolicySize = new SparseArray<>();
}
+ private void maybeForceEnforcementRefreshLocked(@NonNull PolicyDefinition<?> policyDefinition) {
+ try {
+ if (shouldForceEnforcementRefresh(policyDefinition)) {
+ // This is okay because it's only true for user restrictions which are all <Boolean>
+ forceEnforcementRefreshLocked((PolicyDefinition<Boolean>) policyDefinition);
+ }
+ } catch (Throwable e) {
+ // Catch any possible exceptions just to be on the safe side
+ Log.e(TAG, "Exception throw during maybeForceEnforcementRefreshLocked", e);
+ }
+ }
+
+ private boolean shouldForceEnforcementRefresh(@NonNull PolicyDefinition<?> policyDefinition) {
+ // These are all "not nullable" but for the purposes of maximum safety for a lightly tested
+ // change we check here
+ if (policyDefinition == null) {
+ return false;
+ }
+ PolicyKey policyKey = policyDefinition.getPolicyKey();
+ if (policyKey == null) {
+ return false;
+ }
+
+ if (policyKey instanceof UserRestrictionPolicyKey) {
+ // b/307481299 We must force all user restrictions to re-sync local
+ // + global on each set/clear
+ return true;
+ }
+
+ return false;
+ }
+
+ private void forceEnforcementRefreshLocked(PolicyDefinition<Boolean> policyDefinition) {
+ Binder.withCleanCallingIdentity(() -> {
+ // Sync global state
+ PolicyValue<Boolean> globalValue = new BooleanPolicyValue(false);
+ try {
+ PolicyState<Boolean> policyState = getGlobalPolicyStateLocked(policyDefinition);
+ globalValue = policyState.getCurrentResolvedPolicy();
+ } catch (IllegalArgumentException e) {
+ // Expected for local-only policies
+ }
+
+ enforcePolicy(policyDefinition, globalValue, UserHandle.USER_ALL);
+
+ // Loop through each user and sync that user's state
+ for (UserInfo user : mUserManager.getUsers()) {
+ PolicyValue<Boolean> localValue = new BooleanPolicyValue(false);
+ try {
+ PolicyState<Boolean> localPolicyState = getLocalPolicyStateLocked(
+ policyDefinition, user.id);
+ localValue = localPolicyState.getCurrentResolvedPolicy();
+ } catch (IllegalArgumentException e) {
+ // Expected for global-only policies
+ }
+
+ enforcePolicy(policyDefinition, localValue, user.id);
+ }
+ });
+ }
+
/**
* Set the policy for the provided {@code policyDefinition} (see {@link PolicyDefinition}) and
* {@code enforcingAdmin} to the provided {@code value}.
@@ -188,6 +250,7 @@
// No need to notify admins as no new policy is actually enforced, we're just filling in
// the data structures.
if (!skipEnforcePolicy) {
+ maybeForceEnforcementRefreshLocked(policyDefinition);
if (policyChanged) {
onLocalPolicyChangedLocked(policyDefinition, enforcingAdmin, userId);
}
@@ -278,6 +341,7 @@
Objects.requireNonNull(enforcingAdmin);
synchronized (mLock) {
+ maybeForceEnforcementRefreshLocked(policyDefinition);
if (!hasLocalPolicyLocked(policyDefinition, userId)) {
return;
}
@@ -451,6 +515,7 @@
// No need to notify admins as no new policy is actually enforced, we're just filling in
// the data structures.
if (!skipEnforcePolicy) {
+ maybeForceEnforcementRefreshLocked(policyDefinition);
if (policyChanged) {
onGlobalPolicyChangedLocked(policyDefinition, enforcingAdmin);
}
@@ -506,6 +571,7 @@
boolean policyChanged = policyState.removePolicy(enforcingAdmin);
+ maybeForceEnforcementRefreshLocked(policyDefinition);
if (policyChanged) {
onGlobalPolicyChangedLocked(policyDefinition, enforcingAdmin);
}
diff --git a/services/java/com/android/server/SystemConfigService.java b/services/java/com/android/server/SystemConfigService.java
index 6e82907..fd21a32 100644
--- a/services/java/com/android/server/SystemConfigService.java
+++ b/services/java/com/android/server/SystemConfigService.java
@@ -21,6 +21,8 @@
import android.Manifest;
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+import android.os.Binder;
import android.os.ISystemConfig;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -108,6 +110,15 @@
"Caller must hold " + Manifest.permission.QUERY_ALL_PACKAGES);
return new ArrayList<>(SystemConfig.getInstance().getDefaultVrComponents());
}
+
+ @Override
+ public List<String> getPreventUserDisablePackages() {
+ PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+ return SystemConfig.getInstance().getPreventUserDisablePackages().stream()
+ .filter(preventUserDisablePackage ->
+ pmi.canQueryPackage(Binder.getCallingUid(), preventUserDisablePackage))
+ .collect(toList());
+ }
};
public SystemConfigService(Context context) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 1185a4e..86ad494 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2965,6 +2965,12 @@
t.traceEnd();
}
+ if (com.android.server.notification.Flags.sensitiveNotificationAppProtection()) {
+ t.traceBegin("StartSensitiveContentProtectionManager");
+ mSystemServiceManager.startService(SensitiveContentProtectionManagerService.class);
+ t.traceEnd();
+ }
+
// These are needed to propagate to the runnable below.
final NetworkManagementService networkManagementF = networkManagement;
final NetworkPolicyManagerService networkPolicyF = networkPolicy;
diff --git a/services/print/Android.bp b/services/print/Android.bp
index 5b4349a..0dfceaa 100644
--- a/services/print/Android.bp
+++ b/services/print/Android.bp
@@ -19,4 +19,7 @@
defaults: ["platform_service_defaults"],
srcs: [":services.print-sources"],
libs: ["services.core"],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/services/robotests/Android.bp b/services/robotests/Android.bp
index 52eae21..a70802a 100644
--- a/services/robotests/Android.bp
+++ b/services/robotests/Android.bp
@@ -57,9 +57,13 @@
],
static_libs: [
"androidx.test.ext.truth",
+ "Settings-robo-testutils",
+ "SettingsLib-robo-testutils",
],
instrumentation_for: "FrameworksServicesLib",
+
+ upstream: true,
}
filegroup {
diff --git a/services/robotests/backup/Android.bp b/services/robotests/backup/Android.bp
index 8b9efb3..569786b 100644
--- a/services/robotests/backup/Android.bp
+++ b/services/robotests/backup/Android.bp
@@ -57,6 +57,8 @@
// Include the testing libraries
libs: [
"mockito-robolectric-prebuilt",
+ "Settings-robo-testutils",
+ "SettingsLib-robo-testutils",
"platform-test-annotations",
"testng",
"truth",
@@ -64,4 +66,6 @@
instrumentation_for: "BackupFrameworksServicesLib",
+ upstream: true,
+
}
diff --git a/services/robotests/backup/config/robolectric.properties b/services/robotests/backup/config/robolectric.properties
index 850557a..1ebf6d4 100644
--- a/services/robotests/backup/config/robolectric.properties
+++ b/services/robotests/backup/config/robolectric.properties
@@ -1 +1,3 @@
-sdk=NEWEST_SDK
\ No newline at end of file
+sdk=NEWEST_SDK
+looperMode=LEGACY
+shadows=com.android.server.testing.shadows.FrameworkShadowLooper
diff --git a/services/robotests/backup/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java b/services/robotests/backup/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java
index ee5a534..6839a06 100644
--- a/services/robotests/backup/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java
@@ -57,6 +57,7 @@
ShadowBackupDataOutput.class,
ShadowEnvironment.class,
ShadowFullBackup.class,
+ ShadowSigningInfo.class,
})
public class AppMetadataBackupWriterTest {
private static final String TEST_PACKAGE = "com.test.package";
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt b/services/robotests/backup/src/com/android/server/backup/fullbackup/ShadowSigningInfo.java
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
copy to services/robotests/backup/src/com/android/server/backup/fullbackup/ShadowSigningInfo.java
index efc7431..53d807c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
+++ b/services/robotests/backup/src/com/android/server/backup/fullbackup/ShadowSigningInfo.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2018 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.
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.impl.custom.di.bound
+package com.android.server.backup.fullbackup;
-import javax.inject.Qualifier
+import static android.os.Build.VERSION_CODES.P;
-/** User associated with current custom tile binding. */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class CustomTileUser
+import android.content.pm.SigningInfo;
+
+import org.robolectric.annotation.Implements;
+
+@Implements(value = SigningInfo.class, minSdk = P)
+public class ShadowSigningInfo {
+}
diff --git a/services/robotests/src/com/android/server/location/gnss/NtpNetworkTimeHelperTest.java b/services/robotests/src/com/android/server/location/gnss/NtpNetworkTimeHelperTest.java
index 4949091..0092763 100644
--- a/services/robotests/src/com/android/server/location/gnss/NtpNetworkTimeHelperTest.java
+++ b/services/robotests/src/com/android/server/location/gnss/NtpNetworkTimeHelperTest.java
@@ -35,6 +35,7 @@
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.LooperMode;
import org.robolectric.shadows.ShadowLooper;
import java.util.concurrent.CountDownLatch;
@@ -45,6 +46,7 @@
*/
@RunWith(RobolectricTestRunner.class)
@Presubmit
+@LooperMode(LooperMode.Mode.LEGACY)
public class NtpNetworkTimeHelperTest {
private static final long MOCK_NTP_TIME = 1519930775453L;
diff --git a/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowLooper.java b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowLooper.java
index 16d16cd..3681bd4 100644
--- a/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowLooper.java
+++ b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowLooper.java
@@ -21,12 +21,15 @@
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
+import org.robolectric.shadows.LooperShadowPicker;
+import org.robolectric.shadows.ShadowLegacyLooper;
import org.robolectric.shadows.ShadowLooper;
+import org.robolectric.shadows.ShadowPausedLooper;
import java.util.Optional;
-@Implements(value = Looper.class)
-public class FrameworkShadowLooper extends ShadowLooper {
+@Implements(value = Looper.class, shadowPicker = FrameworkShadowLooper.Picker.class)
+public class FrameworkShadowLooper extends ShadowLegacyLooper {
@RealObject private Looper mLooper;
private Optional<Boolean> mIsCurrentThread = Optional.empty();
@@ -45,4 +48,10 @@
}
return Thread.currentThread() == mLooper.getThread();
}
+
+ public static class Picker extends LooperShadowPicker<ShadowLooper> {
+ public Picker() {
+ super(FrameworkShadowLooper.class, ShadowPausedLooper.class);
+ }
+ }
}
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java b/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java
index 4a99486..1da6759 100644
--- a/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java
@@ -95,7 +95,6 @@
sPackageAppEnabledStates.put(packageName, Integer.valueOf(newState)); // flags unused here.
}
- @Override
protected PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId)
throws NameNotFoundException {
if (!sPackageInfos.containsKey(packageName)) {
diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp
index ffe6dc5..56423b9 100644
--- a/services/tests/InputMethodSystemServerTests/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/Android.bp
@@ -40,6 +40,7 @@
"frameworks-base-testutils",
"mockito-target-extended-minus-junit4",
"platform-test-annotations",
+ "ravenwood-junit",
"services.core",
"service-permission.stubs.system_server",
"servicestests-core-utils",
@@ -66,6 +67,28 @@
},
}
+android_ravenwood_test {
+ name: "FrameworksInputMethodSystemServerTests_host",
+ static_libs: [
+ "androidx.annotation_annotation",
+ "androidx.test.rules",
+ "framework",
+ "mockito_ravenwood",
+ "ravenwood-runtime",
+ "ravenwood-utils",
+ "services",
+ ],
+ libs: [
+ "android.test.base",
+ "android.test.runner",
+ ],
+ srcs: [
+ "src/com/android/server/inputmethod/**/ClientControllerTest.java",
+ ],
+ sdk_version: "test_current",
+ auto_gen_config: true,
+}
+
android_test {
name: "FrameworksImeTests",
defaults: [
@@ -88,6 +111,7 @@
"frameworks-base-testutils",
"mockito-target-extended-minus-junit4",
"platform-test-annotations",
+ "ravenwood-junit",
"services.core",
"service-permission.stubs.system_server",
"servicestests-core-utils",
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java
new file mode 100644
index 0000000..3c8f5c9
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.inputmethod;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.PackageManagerInternal;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.view.Display;
+import android.view.inputmethod.InputBinding;
+
+import com.android.internal.inputmethod.IInputMethodClient;
+import com.android.internal.inputmethod.IRemoteInputConnection;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+
+// This test is designed to run on both device and host (Ravenwood) side.
+public final class ClientControllerTest {
+ private static final int ANY_DISPLAY_ID = Display.DEFAULT_DISPLAY;
+ private static final int ANY_CALLER_UID = 1;
+ private static final int ANY_CALLER_PID = 1;
+
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setProvideMainThread(true).build();
+
+ @Mock
+ private PackageManagerInternal mMockPackageManagerInternal;
+
+ @Mock(extraInterfaces = IBinder.class)
+ private IInputMethodClient mClient;
+
+ @Mock
+ private IRemoteInputConnection mConnection;
+
+ @Mock
+ private IBinder.DeathRecipient mDeathRecipient;
+
+ private Handler mHandler;
+
+ private ClientController mController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mHandler = new Handler(Looper.getMainLooper());
+ mController = new ClientController(mMockPackageManagerInternal);
+ when(mClient.asBinder()).thenReturn((IBinder) mClient);
+ }
+
+ @Test
+ // TODO(b/314150112): Enable host side mode for this test once b/315544364 is fixed.
+ @IgnoreUnderRavenwood(blockedBy = {InputBinding.class, IInputMethodClientInvoker.class})
+ public void testAddClient_cannotAddTheSameClientTwice() {
+ var invoker = IInputMethodClientInvoker.create(mClient, mHandler);
+
+ synchronized (ImfLock.class) {
+ mController.addClient(invoker, mConnection, ANY_DISPLAY_ID, mDeathRecipient,
+ ANY_CALLER_UID, ANY_CALLER_PID);
+
+ SecurityException thrown = assertThrows(SecurityException.class,
+ () -> {
+ synchronized (ImfLock.class) {
+ mController.addClient(invoker, mConnection, ANY_DISPLAY_ID,
+ mDeathRecipient, ANY_CALLER_UID, ANY_CALLER_PID);
+ }
+ });
+ assertThat(thrown.getMessage()).isEqualTo(
+ "uid=1/pid=1/displayId=0 is already registered");
+ }
+ }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index 3199e06..438bea4 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -22,6 +22,7 @@
import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SOFT_INPUT;
import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SWITCH_USER;
import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_SOFT_INPUT;
+import static com.android.server.inputmethod.ClientController.ClientState;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_EXPLICIT;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_NOT_ALWAYS;
@@ -68,8 +69,7 @@
super.setUp();
mVisibilityApplier =
(DefaultImeVisibilityApplier) mInputMethodManagerService.getVisibilityApplier();
- mInputMethodManagerService.setAttachedClientForTesting(
- mock(InputMethodManagerService.ClientState.class));
+ mInputMethodManagerService.setAttachedClientForTesting(mock(ClientState.class));
}
@Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
index 8cc3408..567792e 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
@@ -25,6 +25,7 @@
import androidx.test.filters.SmallTest;
+import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.layout.DisplayIdProducer;
import com.android.server.display.layout.Layout;
@@ -45,6 +46,7 @@
private DeviceStateToLayoutMap mDeviceStateToLayoutMap;
@Mock DisplayIdProducer mDisplayIdProducerMock;
+ @Mock DisplayManagerFlags mMockFlags;
@Before
public void setUp() throws IOException {
@@ -52,7 +54,7 @@
Mockito.when(mDisplayIdProducerMock.getId(false)).thenReturn(1);
- setupDeviceStateToLayoutMap();
+ setupDeviceStateToLayoutMap(getContent());
}
//////////////////
@@ -268,6 +270,41 @@
IllegalArgumentException.class, () -> layout.postProcessLocked());
}
+ @Test
+ public void testPortInLayout_disabledFlag() {
+ Mockito.when(mMockFlags.isPortInDisplayLayoutEnabled()).thenReturn(false);
+ assertThrows("Expected IllegalArgumentException when using <port>",
+ IllegalArgumentException.class,
+ () -> setupDeviceStateToLayoutMap(getPortContent()));
+ }
+
+ @Test
+ public void testPortInLayout_readLayout() throws Exception {
+ Mockito.when(mMockFlags.isPortInDisplayLayoutEnabled()).thenReturn(true);
+ setupDeviceStateToLayoutMap(getPortContent());
+
+ Layout configLayout = mDeviceStateToLayoutMap.get(0);
+
+ Layout testLayout = new Layout();
+ testLayout.createDisplayLocked(DisplayAddress.fromPortAndModel(123, null),
+ /* isDefault= */ true, /* isEnabled= */ true, /* displayGroupName= */ null,
+ mDisplayIdProducerMock, Layout.Display.POSITION_UNKNOWN,
+ /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ null,
+ /* refreshRateZoneId= */ null,
+ /* refreshRateThermalThrottlingMapId= */ null,
+ /* powerThrottlingMapId= */ null);
+ testLayout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(78910L),
+ /* isDefault= */ false, /* isEnabled= */ false, /* displayGroupName= */ null,
+ mDisplayIdProducerMock, Layout.Display.POSITION_UNKNOWN,
+ /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ null,
+ /* refreshRateZoneId= */ null,
+ /* refreshRateThermalThrottlingMapId= */ null,
+ /* powerThrottlingMapId= */ null);
+ testLayout.postProcessLocked();
+
+ assertEquals(testLayout, configLayout);
+ }
+
////////////////////
// Helper Methods //
////////////////////
@@ -287,13 +324,28 @@
/* powerThrottlingMapId= */ null);
}
- private void setupDeviceStateToLayoutMap() throws IOException {
+ private void setupDeviceStateToLayoutMap(String content) throws IOException {
Path tempFile = Files.createTempFile("device_state_layout_map", ".tmp");
- Files.write(tempFile, getContent().getBytes(StandardCharsets.UTF_8));
- mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(mDisplayIdProducerMock,
+ Files.write(tempFile, content.getBytes(StandardCharsets.UTF_8));
+ mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(mDisplayIdProducerMock, mMockFlags,
tempFile.toFile());
}
+ private String getPortContent() {
+ return "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<layouts>\n"
+ + "<layout>\n"
+ + "<state>0</state> \n"
+ + "<display enabled=\"true\" defaultDisplay=\"true\">\n"
+ + "<port>123</port>\n"
+ + "</display>\n"
+ + "<display enabled=\"false\">\n"
+ + "<address>78910</address>\n"
+ + "</display>\n"
+ + "</layout>\n"
+ + "</layouts>\n";
+ }
+
private String getContent() {
return "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ "<layouts>\n"
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 7a84406..e370f55 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -759,6 +759,15 @@
AUTO_BRIGHTNESS_MODE_DEFAULT,
Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM), SMALL_DELTA);
+ assertArrayEquals(new float[]{0.0f, 80},
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
+ AUTO_BRIGHTNESS_MODE_DEFAULT,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_BRIGHT), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.6f, 0.7f},
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
+ AUTO_BRIGHTNESS_MODE_DEFAULT,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_BRIGHT), SMALL_DELTA);
+
assertArrayEquals(new float[]{0.0f, 95},
mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
AUTO_BRIGHTNESS_MODE_DOZE,
@@ -1197,6 +1206,20 @@
+ "</map>\n"
+ "</luxToBrightnessMapping>\n"
+ "<luxToBrightnessMapping>\n"
+ + "<mode>default</mode>\n"
+ + "<setting>bright</setting>\n"
+ + "<map>\n"
+ + "<point>\n"
+ + "<first>0</first>\n"
+ + "<second>0.6</second>\n"
+ + "</point>\n"
+ + "<point>\n"
+ + "<first>80</first>\n"
+ + "<second>0.7</second>\n"
+ + "</point>\n"
+ + "</map>\n"
+ + "</luxToBrightnessMapping>\n"
+ + "<luxToBrightnessMapping>\n"
+ "<mode>doze</mode>\n"
+ "<map>\n"
+ "<point>\n"
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 28ec896..bed6f92 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -49,8 +49,9 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.times;
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 static org.mockito.Mockito.when;
@@ -83,7 +84,6 @@
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
import java.io.File;
import java.io.InputStream;
@@ -111,14 +111,14 @@
private final DisplayIdProducer mIdProducer = (isDefault) ->
isDefault ? DEFAULT_DISPLAY : sNextNonDefaultDisplayId++;
+ private DeviceStateToLayoutMap mDeviceStateToLayoutMapSpy;
+
@Mock LogicalDisplayMapper.Listener mListenerMock;
@Mock Context mContextMock;
@Mock FoldSettingProvider mFoldSettingProviderMock;
@Mock Resources mResourcesMock;
@Mock IPowerManager mIPowerManagerMock;
@Mock IThermalService mIThermalServiceMock;
- @Spy DeviceStateToLayoutMap mDeviceStateToLayoutMapSpy =
- new DeviceStateToLayoutMap(mIdProducer, NON_EXISTING_FILE);
@Mock DisplayManagerFlags mFlagsMock;
@Mock DisplayAdapter mDisplayAdapterMock;
@@ -131,6 +131,8 @@
System.setProperty("dexmaker.share_classloader", "true");
MockitoAnnotations.initMocks(this);
+ mDeviceStateToLayoutMapSpy =
+ spy(new DeviceStateToLayoutMap(mIdProducer, mFlagsMock, NON_EXISTING_FILE));
mDisplayDeviceRepo = new DisplayDeviceRepository(
new DisplayManagerService.SyncRoot(),
new PersistentDataStore(new PersistentDataStore.Injector() {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index 60a0c03..a0e5fd8 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -3434,6 +3434,11 @@
return mSensorManagerInternal;
}
+ @Override
+ public VotesStatsReporter getVotesStatsReporter(boolean refreshRateVotingTelemetryEnabled) {
+ return null;
+ }
+
protected Display createDisplay(int id) {
return new Display(DisplayManagerGlobal.getInstance(), id, mDisplayInfo,
ApplicationProvider.getApplicationContext().getResources());
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java
index f677401..2815fcb 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java
@@ -58,7 +58,7 @@
private RegisteringFakesInjector mInjector = new RegisteringFakesInjector();
private final TestHandler mHandler = new TestHandler(null);
- private final VotesStorage mStorage = new VotesStorage(() -> {});
+ private final VotesStorage mStorage = new VotesStorage(() -> {}, null);
@Before
public void setUp() {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java
index 50e2392..1f6f1a4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java
@@ -51,7 +51,7 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mVotesStorage = new VotesStorage(mVotesListener);
+ mVotesStorage = new VotesStorage(mVotesListener, null);
}
@Test
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 113511e..321d945 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -73,6 +73,7 @@
// TODO: remove once Android migrates to JUnit 4.12, which provides assertThrows
"testng",
"compatibility-device-util-axt",
+ "flag-junit",
],
libs: [
diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java
new file mode 100644
index 0000000..b363fd4
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java
@@ -0,0 +1,381 @@
+/*
+ * 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;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.media.projection.MediaProjectionInfo;
+import android.media.projection.MediaProjectionManager;
+import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.NotificationListenerService.RankingMap;
+import android.service.notification.StatusBarNotification;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper.RunWithLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.wm.SensitiveContentPackages.PackageInfo;
+import com.android.server.wm.WindowManagerInternal;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Collections;
+import java.util.Set;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class SensitiveContentProtectionManagerServiceTest {
+ private static final String NOTIFICATION_KEY_1 = "com.android.server.notification.TEST_KEY_1";
+ private static final String NOTIFICATION_KEY_2 = "com.android.server.notification.TEST_KEY_2";
+
+ private static final String NOTIFICATION_PKG_1 = "com.android.server.notification.one";
+ private static final String NOTIFICATION_PKG_2 = "com.android.server.notification.two";
+
+ private static final int NOTIFICATION_UID_1 = 5;
+ private static final int NOTIFICATION_UID_2 = 6;
+
+ @Rule
+ public final TestableContext mContext =
+ new TestableContext(getInstrumentation().getTargetContext(), null);
+
+ private SensitiveContentProtectionManagerService mSensitiveContentProtectionManagerService;
+
+ @Captor
+ ArgumentCaptor<MediaProjectionManager.Callback> mMediaProjectionCallbackCaptor;
+
+ @Mock
+ private MediaProjectionManager mProjectionManager;
+
+ @Mock
+ private WindowManagerInternal mWindowManager;
+
+ @Mock
+ private StatusBarNotification mNotification1;
+
+ @Mock
+ private StatusBarNotification mNotification2;
+
+ @Mock
+ private RankingMap mRankingMap;
+
+ @Mock
+ private Ranking mSensitiveRanking;
+
+ @Mock
+ private Ranking mNonSensitiveRanking;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mSensitiveContentProtectionManagerService =
+ new SensitiveContentProtectionManagerService(mContext);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener =
+ spy(mSensitiveContentProtectionManagerService.mNotificationListener);
+
+ // Setup RankingMap and two possilbe rankings
+ when(mSensitiveRanking.hasSensitiveContent()).thenReturn(true);
+ when(mNonSensitiveRanking.hasSensitiveContent()).thenReturn(false);
+ doReturn(mRankingMap)
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .getCurrentRanking();
+
+ setupSensitiveNotification();
+
+ mSensitiveContentProtectionManagerService.init(mProjectionManager, mWindowManager);
+
+ // Obtain useful mMediaProjectionCallback
+ verify(mProjectionManager).addCallback(mMediaProjectionCallbackCaptor.capture(), any());
+ }
+
+ @After
+ public void tearDown() {
+ mSensitiveContentProtectionManagerService.onDestroy();
+ }
+
+ private Set<PackageInfo> setupSensitiveNotification() {
+ // Setup Notification Values
+ when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1);
+ when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
+ when(mNotification1.getUid()).thenReturn(NOTIFICATION_UID_1);
+
+ when(mNotification2.getKey()).thenReturn(NOTIFICATION_KEY_2);
+ when(mNotification2.getPackageName()).thenReturn(NOTIFICATION_PKG_2);
+ when(mNotification2.getUid()).thenReturn(NOTIFICATION_UID_1);
+
+ StatusBarNotification[] mNotifications =
+ new StatusBarNotification[] {mNotification1, mNotification2};
+ doReturn(mNotifications)
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .getActiveNotifications();
+
+ when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1)))
+ .thenReturn(mSensitiveRanking);
+ when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_2)))
+ .thenReturn(mNonSensitiveRanking);
+
+ return Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1));
+ }
+
+ private Set<PackageInfo> setupMultipleSensitiveNotificationsFromSamePackageAndUid() {
+ // Setup Notification Values
+ when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1);
+ when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
+ when(mNotification1.getUid()).thenReturn(NOTIFICATION_UID_1);
+
+ when(mNotification2.getKey()).thenReturn(NOTIFICATION_KEY_2);
+ when(mNotification2.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
+ when(mNotification2.getUid()).thenReturn(NOTIFICATION_UID_1);
+
+ StatusBarNotification[] mNotifications =
+ new StatusBarNotification[] {mNotification1, mNotification2};
+ doReturn(mNotifications)
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .getActiveNotifications();
+
+ when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1)))
+ .thenReturn(mSensitiveRanking);
+ when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_2)))
+ .thenReturn(mSensitiveRanking);
+
+ return Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1));
+ }
+
+ private Set<PackageInfo> setupMultipleSensitiveNotificationsFromDifferentPackage() {
+ // Setup Notification Values
+ when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1);
+ when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
+ when(mNotification1.getUid()).thenReturn(NOTIFICATION_UID_1);
+
+ when(mNotification2.getKey()).thenReturn(NOTIFICATION_KEY_2);
+ when(mNotification2.getPackageName()).thenReturn(NOTIFICATION_PKG_2);
+ when(mNotification2.getUid()).thenReturn(NOTIFICATION_UID_1);
+
+ StatusBarNotification[] mNotifications =
+ new StatusBarNotification[] {mNotification1, mNotification2};
+ doReturn(mNotifications)
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .getActiveNotifications();
+
+ when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1)))
+ .thenReturn(mSensitiveRanking);
+ when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_2)))
+ .thenReturn(mSensitiveRanking);
+
+ return Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1),
+ new PackageInfo(NOTIFICATION_PKG_2, NOTIFICATION_UID_1));
+ }
+
+ private Set<PackageInfo> setupMultipleSensitiveNotificationsFromDifferentUid() {
+ // Setup Notification Values
+ when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1);
+ when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
+ when(mNotification1.getUid()).thenReturn(NOTIFICATION_UID_1);
+
+ when(mNotification2.getKey()).thenReturn(NOTIFICATION_KEY_2);
+ when(mNotification2.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
+ when(mNotification2.getUid()).thenReturn(NOTIFICATION_UID_2);
+
+ StatusBarNotification[] mNotifications =
+ new StatusBarNotification[] {mNotification1, mNotification2};
+ doReturn(mNotifications)
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .getActiveNotifications();
+
+ when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1)))
+ .thenReturn(mSensitiveRanking);
+ when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_2)))
+ .thenReturn(mSensitiveRanking);
+
+ return Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1),
+ new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_2));
+ }
+
+ private void setupNoSensitiveNotifications() {
+ // Setup Notification Values
+ when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1);
+ when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
+ when(mNotification1.getUid()).thenReturn(NOTIFICATION_UID_1);
+
+ StatusBarNotification[] mNotifications = new StatusBarNotification[] {mNotification1};
+ doReturn(mNotifications)
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .getActiveNotifications();
+
+ when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1)))
+ .thenReturn(mNonSensitiveRanking);
+ }
+
+ private void setupNoNotifications() {
+ // Setup Notification Values
+ StatusBarNotification[] mNotifications = new StatusBarNotification[] {};
+ doReturn(mNotifications)
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .getActiveNotifications();
+ }
+
+ @Test
+ public void mediaProjectionOnStart_onProjectionStart_setWmBlockedPackages() {
+ Set<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
+
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+
+ verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+ }
+
+ @Test
+ public void mediaProjectionOnStart_noSensitiveNotifications_noBlockedPackages() {
+ setupNoSensitiveNotifications();
+
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+
+ verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ }
+
+ @Test
+ public void mediaProjectionOnStart_noNotifications_noBlockedPackages() {
+ setupNoNotifications();
+
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+
+ verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ }
+
+ @Test
+ public void mediaProjectionOnStart_multipleNotifications_setWmBlockedPackages() {
+ Set<PackageInfo> expectedBlockedPackages =
+ setupMultipleSensitiveNotificationsFromSamePackageAndUid();
+
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+
+ verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+ }
+
+ @Test
+ public void mediaProjectionOnStart_multiplePackages_setWmBlockedPackages() {
+ Set<PackageInfo> expectedBlockedPackages =
+ setupMultipleSensitiveNotificationsFromDifferentPackage();
+
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+
+ verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+ }
+
+ @Test
+ public void mediaProjectionOnStart_multipleUid_setWmBlockedPackages() {
+ Set<PackageInfo> expectedBlockedPackages =
+ setupMultipleSensitiveNotificationsFromDifferentUid();
+
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+
+ verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+ }
+
+ @Test
+ public void mediaProjectionOnStop_onProjectionEnd_clearWmBlockedPackages() {
+ setupSensitiveNotification();
+
+ MediaProjectionInfo mediaProjectionInfo = mock(MediaProjectionInfo.class);
+ mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo);
+ Mockito.reset(mWindowManager);
+
+ mMediaProjectionCallbackCaptor.getValue().onStop(mediaProjectionInfo);
+
+ verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ }
+
+ @Test
+ public void mediaProjectionOnStart_afterOnStop_onProjectionStart_setWmBlockedPackages() {
+ Set<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
+
+ MediaProjectionInfo mediaProjectionInfo = mock(MediaProjectionInfo.class);
+ mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo);
+ mMediaProjectionCallbackCaptor.getValue().onStop(mediaProjectionInfo);
+ Mockito.reset(mWindowManager);
+
+ mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo);
+
+ verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+ }
+
+ @Test
+ public void mediaProjectionOnStart_getActiveNotificationsThrows_noBlockedPackages() {
+ doThrow(SecurityException.class)
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .getActiveNotifications();
+
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+
+ verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ }
+
+ @Test
+ public void mediaProjectionOnStart_getCurrentRankingThrows_noBlockedPackages() {
+ doThrow(SecurityException.class)
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .getCurrentRanking();
+
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+
+ verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ }
+
+ @Test
+ public void mediaProjectionOnStart_getCurrentRanking_nullRankingMap_noBlockedPackages() {
+ doReturn(null)
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .getCurrentRanking();
+
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+
+ verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ }
+
+ @Test
+ public void mediaProjectionOnStart_getCurrentRanking_missingRanking_noBlockedPackages() {
+ when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1))).thenReturn(null);
+
+ doReturn(mRankingMap)
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .getCurrentRanking();
+
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+
+ verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
index 57326b2..d08cdc7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -343,10 +343,12 @@
List<ApexManager.ScanResult> scanResults = scanApexInfos(apexInfo);
mApexManager.notifyScanResult(scanResults);
- assertThat(mApexManager.getApkInApexInstallError(activeApex.apexModuleName)).isNull();
+ final String apexPackageName = mApexManager.getActivePackageNameForApexModuleName(
+ activeApex.apexModuleName);
+ assertThat(mApexManager.getApkInApexInstallError(apexPackageName)).isNull();
mApexManager.reportErrorWithApkInApex(activeApex.apexDirectory.getAbsolutePath(),
"Some random error");
- assertThat(mApexManager.getApkInApexInstallError(activeApex.apexModuleName))
+ assertThat(mApexManager.getApkInApexInstallError(apexPackageName))
.isEqualTo("Some random error");
}
@@ -370,9 +372,11 @@
List<ApexManager.ScanResult> scanResults = scanApexInfos(apexInfo);
mApexManager.notifyScanResult(scanResults);
- assertThat(mApexManager.getApksInApex(activeApex.apexModuleName)).isEmpty();
+ final String apexPackageName = mApexManager.getActivePackageNameForApexModuleName(
+ activeApex.apexModuleName);
+ assertThat(mApexManager.getApksInApex(apexPackageName)).isEmpty();
mApexManager.registerApkInApex(fakeApkInApex);
- assertThat(mApexManager.getApksInApex(activeApex.apexModuleName)).isEmpty();
+ assertThat(mApexManager.getApksInApex(apexPackageName)).isEmpty();
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
index 60cedcf..24e7242 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
@@ -108,6 +108,20 @@
}
@Test
+ public void testPackageMonitorCallback_SuspendNoAccessCallbackNotCalled() throws Exception {
+ BiFunction<Integer, Bundle, Bundle> filterExtras = (callingUid, intentExtras) -> null;
+
+ IRemoteCallback callback = createMockPackageMonitorCallback();
+ mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */,
+ Binder.getCallingUid());
+ mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGES_SUSPENDED,
+ FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0}, null /* instantUserIds */,
+ null /* broadcastAllowList */, mHandler, filterExtras);
+
+ verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
+ }
+
+ @Test
public void testPackageMonitorCallback_SuspendCallbackCalled() throws Exception {
Bundle result = new Bundle();
result.putInt(Intent.EXTRA_UID, FAKE_PACKAGE_UID);
diff --git a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
index 9851bc1..97e94e3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
@@ -16,24 +16,24 @@
package com.android.server.trust;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.argThat;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-
import android.Manifest;
import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.IActivityManager;
+import android.app.admin.DevicePolicyManager;
import android.app.trust.ITrustListener;
import android.app.trust.ITrustManager;
import android.content.BroadcastReceiver;
@@ -45,14 +45,23 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
+import android.hardware.biometrics.BiometricManager;
import android.net.Uri;
+import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
-import android.os.test.TestLooper;
+import android.os.UserManager;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
+import android.security.Authorization;
+import android.security.authorization.IKeystoreAuthorization;
import android.service.trust.TrustAgentService;
import android.testing.TestableContext;
import android.view.IWindowManager;
@@ -61,12 +70,11 @@
import androidx.test.core.app.ApplicationProvider;
import com.android.internal.widget.LockPatternUtils;
+import com.android.modules.utils.testing.ExtendedMockitoRule;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
-import com.google.android.collect.Lists;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -74,37 +82,74 @@
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
-import org.mockito.MockitoSession;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Random;
+import java.util.Collection;
+import java.util.List;
public class TrustManagerServiceTest {
@Rule
- public MockitoRule mMockitoRule = MockitoJUnit.rule();
+ public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+ .spyStatic(ActivityManager.class)
+ .spyStatic(Authorization.class)
+ .mockStatic(ServiceManager.class)
+ .mockStatic(WindowManagerGlobal.class)
+ .build();
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Rule
public final MockContext mMockContext = new MockContext(
ApplicationProvider.getApplicationContext());
private static final String URI_SCHEME_PACKAGE = "package";
- private static final int TEST_USER_ID = UserHandle.USER_SYSTEM;
+ private static final int TEST_USER_ID = 50;
+ private static final int PARENT_USER_ID = 60;
+ private static final int PROFILE_USER_ID = 70;
+ private static final long[] PARENT_BIOMETRIC_SIDS = new long[] { 600L, 601L };
+ private static final long[] PROFILE_BIOMETRIC_SIDS = new long[] { 700L, 701L };
- private final TestLooper mLooper = new TestLooper();
private final ArrayList<ResolveInfo> mTrustAgentResolveInfoList = new ArrayList<>();
- private final LockPatternUtils mLockPatternUtils = new LockPatternUtils(mMockContext);
- private final TrustManagerService mService = new TrustManagerService(mMockContext);
+ private final ArrayList<ComponentName> mKnownTrustAgents = new ArrayList<>();
+ private final ArrayList<ComponentName> mEnabledTrustAgents = new ArrayList<>();
- @Mock
- private PackageManager mPackageManagerMock;
+ private @Mock ActivityManager mActivityManager;
+ private @Mock BiometricManager mBiometricManager;
+ private @Mock DevicePolicyManager mDevicePolicyManager;
+ private @Mock IKeystoreAuthorization mKeystoreAuthorization;
+ private @Mock LockPatternUtils mLockPatternUtils;
+ private @Mock PackageManager mPackageManager;
+ private @Mock UserManager mUserManager;
+ private @Mock IWindowManager mWindowManager;
+
+ private HandlerThread mHandlerThread;
+ private TrustManagerService.Injector mInjector;
+ private TrustManagerService mService;
+ private ITrustManager mTrustManager;
@Before
- public void setUp() {
- resetTrustAgentLockSettings();
- LocalServices.addService(SystemServiceManager.class, mock(SystemServiceManager.class));
+ public void setUp() throws Exception {
+ when(mActivityManager.isUserRunning(TEST_USER_ID)).thenReturn(true);
+ doReturn(mock(IActivityManager.class)).when(() -> ActivityManager.getService());
+
+ doReturn(mKeystoreAuthorization).when(() -> Authorization.getService());
+
+ when(mLockPatternUtils.getDevicePolicyManager()).thenReturn(mDevicePolicyManager);
+ when(mLockPatternUtils.isSecure(TEST_USER_ID)).thenReturn(true);
+ when(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).thenReturn(mKnownTrustAgents);
+ when(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).thenReturn(mEnabledTrustAgents);
+ doAnswer(invocation -> {
+ mKnownTrustAgents.clear();
+ mKnownTrustAgents.addAll((Collection<ComponentName>) invocation.getArgument(0));
+ return null;
+ }).when(mLockPatternUtils).setKnownTrustAgents(any(), eq(TEST_USER_ID));
+ doAnswer(invocation -> {
+ mEnabledTrustAgents.clear();
+ mEnabledTrustAgents.addAll((Collection<ComponentName>) invocation.getArgument(0));
+ return null;
+ }).when(mLockPatternUtils).setEnabledTrustAgents(any(), eq(TEST_USER_ID));
ArgumentMatcher<Intent> trustAgentIntentMatcher = new ArgumentMatcher<Intent>() {
@Override
@@ -112,17 +157,43 @@
return TrustAgentService.SERVICE_INTERFACE.equals(argument.getAction());
}
};
- when(mPackageManagerMock.queryIntentServicesAsUser(argThat(trustAgentIntentMatcher),
+ when(mPackageManager.queryIntentServicesAsUser(argThat(trustAgentIntentMatcher),
anyInt(), anyInt())).thenReturn(mTrustAgentResolveInfoList);
- when(mPackageManagerMock.checkPermission(any(), any())).thenReturn(
+ when(mPackageManager.checkPermission(any(), any())).thenReturn(
PackageManager.PERMISSION_GRANTED);
- mMockContext.setMockPackageManager(mPackageManagerMock);
+
+ when(mUserManager.getAliveUsers()).thenReturn(
+ List.of(new UserInfo(TEST_USER_ID, "user", UserInfo.FLAG_FULL)));
+
+ when(mWindowManager.isKeyguardLocked()).thenReturn(true);
+
+ mMockContext.addMockSystemService(ActivityManager.class, mActivityManager);
+ mMockContext.addMockSystemService(BiometricManager.class, mBiometricManager);
+ mMockContext.setMockPackageManager(mPackageManager);
+ mMockContext.addMockSystemService(UserManager.class, mUserManager);
+ doReturn(mWindowManager).when(() -> WindowManagerGlobal.getWindowManagerService());
+ LocalServices.addService(SystemServiceManager.class, mock(SystemServiceManager.class));
+
+ grantPermission(Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE);
+ grantPermission(Manifest.permission.TRUST_LISTENER);
+
+ mHandlerThread = new HandlerThread("handler");
+ mHandlerThread.start();
+ mInjector = new TrustManagerService.Injector(mLockPatternUtils, mHandlerThread.getLooper());
+ mService = new TrustManagerService(mMockContext, mInjector);
+
+ // Get the ITrustManager from the new TrustManagerService.
+ mService.onStart();
+ ArgumentCaptor<IBinder> binderArgumentCaptor = ArgumentCaptor.forClass(IBinder.class);
+ verify(() -> ServiceManager.addService(eq(Context.TRUST_SERVICE),
+ binderArgumentCaptor.capture(), anyBoolean(), anyInt()));
+ mTrustManager = ITrustManager.Stub.asInterface(binderArgumentCaptor.getValue());
}
@After
public void tearDown() {
- resetTrustAgentLockSettings();
LocalServices.removeServiceForTest(SystemServiceManager.class);
+ mHandlerThread.quit();
}
@Test
@@ -142,10 +213,9 @@
bootService();
- assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
- systemTrustAgent1, systemTrustAgent2);
- assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
- systemTrustAgent1, systemTrustAgent2, userTrustAgent1, userTrustAgent2);
+ assertThat(mEnabledTrustAgents).containsExactly(systemTrustAgent1, systemTrustAgent2);
+ assertThat(mKnownTrustAgents).containsExactly(systemTrustAgent1, systemTrustAgent2,
+ userTrustAgent1, userTrustAgent2);
}
@Test
@@ -162,10 +232,8 @@
bootService();
- assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
- defaultTrustAgent);
- assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
- systemTrustAgent, defaultTrustAgent);
+ assertThat(mEnabledTrustAgents).containsExactly(defaultTrustAgent);
+ assertThat(mKnownTrustAgents).containsExactly(systemTrustAgent, defaultTrustAgent);
}
@Test
@@ -174,16 +242,16 @@
"com.android/.SystemTrustAgent");
ComponentName trustAgent2 = ComponentName.unflattenFromString(
"com.android/.AnotherSystemTrustAgent");
- initializeEnabledAgents(trustAgent1);
+ mEnabledTrustAgents.add(trustAgent1);
+ Settings.Secure.putIntForUser(mMockContext.getContentResolver(),
+ Settings.Secure.TRUST_AGENTS_INITIALIZED, 1, TEST_USER_ID);
addTrustAgent(trustAgent1, /* isSystemApp= */ true);
addTrustAgent(trustAgent2, /* isSystemApp= */ true);
bootService();
- assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
- trustAgent1);
- assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
- trustAgent1, trustAgent2);
+ assertThat(mEnabledTrustAgents).containsExactly(trustAgent1);
+ assertThat(mKnownTrustAgents).containsExactly(trustAgent1, trustAgent2);
}
@Test
@@ -192,17 +260,17 @@
"com.android/.SystemTrustAgent");
ComponentName trustAgent2 = ComponentName.unflattenFromString(
"com.android/.AnotherSystemTrustAgent");
- initializeEnabledAgents(trustAgent1);
- initializeKnownAgents(trustAgent1);
+ Settings.Secure.putIntForUser(mMockContext.getContentResolver(),
+ Settings.Secure.TRUST_AGENTS_INITIALIZED, 1, TEST_USER_ID);
+ Settings.Secure.putIntForUser(mMockContext.getContentResolver(),
+ Settings.Secure.KNOWN_TRUST_AGENTS_INITIALIZED, 1, TEST_USER_ID);
addTrustAgent(trustAgent1, /* isSystemApp= */ true);
addTrustAgent(trustAgent2, /* isSystemApp= */ true);
bootService();
- assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
- trustAgent1, trustAgent2);
- assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
- trustAgent1, trustAgent2);
+ assertThat(mEnabledTrustAgents).containsExactly(trustAgent1, trustAgent2);
+ assertThat(mKnownTrustAgents).containsExactly(trustAgent1, trustAgent2);
}
@Test
@@ -214,10 +282,8 @@
mMockContext.sendPackageChangedBroadcast(newAgentComponentName);
- assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
- newAgentComponentName);
- assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
- newAgentComponentName);
+ assertThat(mEnabledTrustAgents).containsExactly(newAgentComponentName);
+ assertThat(mKnownTrustAgents).containsExactly(newAgentComponentName);
}
@Test
@@ -235,10 +301,8 @@
mMockContext.sendPackageChangedBroadcast(newAgentComponentName);
- assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
- defaultTrustAgent);
- assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
- defaultTrustAgent, newAgentComponentName);
+ assertThat(mEnabledTrustAgents).containsExactly(defaultTrustAgent);
+ assertThat(mKnownTrustAgents).containsExactly(defaultTrustAgent, newAgentComponentName);
}
@Test
@@ -250,9 +314,8 @@
mMockContext.sendPackageChangedBroadcast(newAgentComponentName);
- assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).isEmpty();
- assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
- newAgentComponentName);
+ assertThat(mEnabledTrustAgents).isEmpty();
+ assertThat(mKnownTrustAgents).containsExactly(newAgentComponentName);
}
@Test
@@ -265,50 +328,88 @@
addTrustAgent(systemTrustAgent2, /* isSystemApp= */ true);
bootService();
// Simulate user turning off systemTrustAgent2
- mLockPatternUtils.setEnabledTrustAgents(Collections.singletonList(systemTrustAgent1),
- TEST_USER_ID);
+ mLockPatternUtils.setEnabledTrustAgents(List.of(systemTrustAgent1), TEST_USER_ID);
mMockContext.sendPackageChangedBroadcast(systemTrustAgent2);
- assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
- systemTrustAgent1);
+ assertThat(mEnabledTrustAgents).containsExactly(systemTrustAgent1);
}
@Test
public void reportEnabledTrustAgentsChangedInformsListener() throws RemoteException {
- final LockPatternUtils utils = mock(LockPatternUtils.class);
- final TrustManagerService service = new TrustManagerService(mMockContext,
- new TrustManagerService.Injector(utils, mLooper.getLooper()));
final ITrustListener trustListener = mock(ITrustListener.class);
- final IWindowManager windowManager = mock(IWindowManager.class);
- final int userId = new Random().nextInt();
+ mTrustManager.registerTrustListener(trustListener);
+ mService.waitForIdle();
+ mTrustManager.reportEnabledTrustAgentsChanged(TEST_USER_ID);
+ mService.waitForIdle();
+ verify(trustListener).onEnabledTrustAgentsChanged(TEST_USER_ID);
+ }
- mMockContext.getTestablePermissions().setPermission(Manifest.permission.TRUST_LISTENER,
- PERMISSION_GRANTED);
+ // Tests that when the device is locked for a managed profile with a *unified* challenge, the
+ // device locked notification that is sent to Keystore contains the biometric SIDs of the parent
+ // user, not the profile. This matches the authentication that is needed to unlock the device
+ // for the profile again.
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+ public void testLockDeviceForManagedProfileWithUnifiedChallenge_usesParentBiometricSids()
+ throws Exception {
+ setupMocksForProfile(/* unifiedChallenge= */ true);
- when(utils.getKnownTrustAgents(anyInt())).thenReturn(new ArrayList<>());
+ when(mWindowManager.isKeyguardLocked()).thenReturn(false);
+ mTrustManager.reportKeyguardShowingChanged();
+ verify(mKeystoreAuthorization).onDeviceUnlocked(PARENT_USER_ID, null);
+ verify(mKeystoreAuthorization).onDeviceUnlocked(PROFILE_USER_ID, null);
- MockitoSession mockSession = mockitoSession()
- .initMocks(this)
- .mockStatic(ServiceManager.class)
- .mockStatic(WindowManagerGlobal.class)
- .startMocking();
+ when(mWindowManager.isKeyguardLocked()).thenReturn(true);
+ mTrustManager.reportKeyguardShowingChanged();
+ verify(mKeystoreAuthorization)
+ .onDeviceLocked(eq(PARENT_USER_ID), eq(PARENT_BIOMETRIC_SIDS));
+ verify(mKeystoreAuthorization)
+ .onDeviceLocked(eq(PROFILE_USER_ID), eq(PARENT_BIOMETRIC_SIDS));
+ }
- doReturn(windowManager).when(() -> {
- WindowManagerGlobal.getWindowManagerService();
- });
+ // Tests that when the device is locked for a managed profile with a *separate* challenge, the
+ // device locked notification that is sent to Keystore contains the biometric SIDs of the
+ // profile itself. This matches the authentication that is needed to unlock the device for the
+ // profile again.
+ @Test
+ public void testLockDeviceForManagedProfileWithSeparateChallenge_usesProfileBiometricSids()
+ throws Exception {
+ setupMocksForProfile(/* unifiedChallenge= */ false);
- service.onStart();
- ArgumentCaptor<IBinder> binderArgumentCaptor = ArgumentCaptor.forClass(IBinder.class);
- verify(() -> ServiceManager.addService(eq(Context.TRUST_SERVICE),
- binderArgumentCaptor.capture(), anyBoolean(), anyInt()));
- ITrustManager manager = ITrustManager.Stub.asInterface(binderArgumentCaptor.getValue());
- manager.registerTrustListener(trustListener);
- mLooper.dispatchAll();
- manager.reportEnabledTrustAgentsChanged(userId);
- mLooper.dispatchAll();
- verify(trustListener).onEnabledTrustAgentsChanged(eq(userId));
- mockSession.finishMocking();
+ mTrustManager.setDeviceLockedForUser(PROFILE_USER_ID, false);
+ verify(mKeystoreAuthorization).onDeviceUnlocked(PROFILE_USER_ID, null);
+
+ mTrustManager.setDeviceLockedForUser(PROFILE_USER_ID, true);
+ verify(mKeystoreAuthorization)
+ .onDeviceLocked(eq(PROFILE_USER_ID), eq(PROFILE_BIOMETRIC_SIDS));
+ }
+
+ private void setupMocksForProfile(boolean unifiedChallenge) {
+ UserInfo parent = new UserInfo(PARENT_USER_ID, "parent", UserInfo.FLAG_FULL);
+ UserInfo profile = new UserInfo(PROFILE_USER_ID, "profile", UserInfo.FLAG_MANAGED_PROFILE);
+ when(mUserManager.getAliveUsers()).thenReturn(List.of(parent, profile));
+ when(mUserManager.getUserInfo(PARENT_USER_ID)).thenReturn(parent);
+ when(mUserManager.getUserInfo(PROFILE_USER_ID)).thenReturn(profile);
+ when(mUserManager.getProfileParent(PROFILE_USER_ID)).thenReturn(parent);
+ when(mUserManager.getEnabledProfileIds(PARENT_USER_ID))
+ .thenReturn(new int[] { PROFILE_USER_ID });
+
+ when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
+ when(mLockPatternUtils.isProfileWithUnifiedChallenge(PROFILE_USER_ID))
+ .thenReturn(unifiedChallenge);
+ when(mLockPatternUtils.isManagedProfileWithUnifiedChallenge(PROFILE_USER_ID))
+ .thenReturn(unifiedChallenge);
+ when(mLockPatternUtils.isSeparateProfileChallengeEnabled(PROFILE_USER_ID))
+ .thenReturn(!unifiedChallenge);
+
+ when(mBiometricManager.getAuthenticatorIds(PARENT_USER_ID))
+ .thenReturn(PARENT_BIOMETRIC_SIDS);
+ when(mBiometricManager.getAuthenticatorIds(PROFILE_USER_ID))
+ .thenReturn(PROFILE_BIOMETRIC_SIDS);
+
+ bootService();
+ mService.onUserSwitching(null, new SystemService.TargetUser(parent));
}
private void addTrustAgent(ComponentName agentComponentName, boolean isSystemApp) {
@@ -327,27 +428,16 @@
mTrustAgentResolveInfoList.add(resolveInfo);
}
- private void initializeEnabledAgents(ComponentName... enabledAgents) {
- mLockPatternUtils.setEnabledTrustAgents(Lists.newArrayList(enabledAgents), TEST_USER_ID);
- Settings.Secure.putIntForUser(mMockContext.getContentResolver(),
- Settings.Secure.TRUST_AGENTS_INITIALIZED, 1, TEST_USER_ID);
- }
-
- private void initializeKnownAgents(ComponentName... knownAgents) {
- mLockPatternUtils.setKnownTrustAgents(Lists.newArrayList(knownAgents), TEST_USER_ID);
- Settings.Secure.putIntForUser(mMockContext.getContentResolver(),
- Settings.Secure.KNOWN_TRUST_AGENTS_INITIALIZED, 1, TEST_USER_ID);
- }
-
private void bootService() {
mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+ mMockContext.sendUserStartedBroadcast();
}
- private void resetTrustAgentLockSettings() {
- mLockPatternUtils.setEnabledTrustAgents(Collections.emptyList(), TEST_USER_ID);
- mLockPatternUtils.setKnownTrustAgents(Collections.emptyList(), TEST_USER_ID);
+ private void grantPermission(String permission) {
+ mMockContext.getTestablePermissions().setPermission(
+ permission, PackageManager.PERMISSION_GRANTED);
}
/** A mock Context that allows the test process to send protected broadcasts. */
@@ -355,6 +445,8 @@
private final ArrayList<BroadcastReceiver> mPackageChangedBroadcastReceivers =
new ArrayList<>();
+ private final ArrayList<BroadcastReceiver> mUserStartedBroadcastReceivers =
+ new ArrayList<>();
MockContext(Context base) {
super(base);
@@ -369,10 +461,18 @@
if (filter.hasAction(Intent.ACTION_PACKAGE_CHANGED)) {
mPackageChangedBroadcastReceivers.add(receiver);
}
+ if (filter.hasAction(Intent.ACTION_USER_STARTED)) {
+ mUserStartedBroadcastReceivers.add(receiver);
+ }
return super.registerReceiverAsUser(receiver, user, filter, broadcastPermission,
scheduler);
}
+ @Override
+ public void sendBroadcastAsUser(Intent intent, UserHandle user,
+ @Nullable String receiverPermission, @Nullable Bundle options) {
+ }
+
void sendPackageChangedBroadcast(ComponentName changedComponent) {
Intent intent = new Intent(
Intent.ACTION_PACKAGE_CHANGED,
@@ -386,5 +486,13 @@
receiver.onReceive(this, intent);
}
}
+
+ void sendUserStartedBroadcast() {
+ Intent intent = new Intent(Intent.ACTION_USER_STARTED)
+ .putExtra(Intent.EXTRA_USER_HANDLE, TEST_USER_ID);
+ for (BroadcastReceiver receiver : mUserStartedBroadcastReceivers) {
+ receiver.onReceive(this, intent);
+ }
+ }
}
}
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 91d8ceb..a9ff3a1 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
@@ -26,6 +26,7 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.content.Context;
@@ -113,7 +114,8 @@
mTestExecutor.simulateAsyncExecutionOfLastCommand();
// THEN the device vibrates once
- verify(mVibrator, times(1)).vibrate(any(), any(VibrationAttributes.class));
+ verify(mVibrator, times(1)).vibrate(anyInt(), any(), any(), any(),
+ any(VibrationAttributes.class));
}
@Test
@@ -129,7 +131,7 @@
mTestExecutor.simulateAsyncExecutionOfLastCommand();
// THEN the device doesn't vibrate
- verify(mVibrator, never()).vibrate(any(), any(VibrationAttributes.class));
+ verifyZeroInteractions(mVibrator);
}
@Test
@@ -145,14 +147,15 @@
mTestExecutor.simulateAsyncExecutionOfLastCommand();
// THEN the device vibrates once
- verify(mVibrator, times(1)).vibrate(any(), any(VibrationAttributes.class));
+ verify(mVibrator, times(1)).vibrate(anyInt(), any(), any(), any(),
+ any(VibrationAttributes.class));
}
@Test
public void testVibrateDisabled_wirelessCharging() {
createNotifier();
- // GIVEN the charging vibration is disabeld
+ // GIVEN the charging vibration is disabled
enableChargingVibration(false);
// WHEN wireless charging starts
@@ -161,7 +164,7 @@
mTestExecutor.simulateAsyncExecutionOfLastCommand();
// THEN the device doesn't vibrate
- verify(mVibrator, never()).vibrate(any(), any(VibrationAttributes.class));
+ verifyZeroInteractions(mVibrator);
}
@Test
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
index d752ae4..aec896f 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -97,6 +97,7 @@
import androidx.test.core.app.ApplicationProvider;
import com.android.internal.app.IBatteryStats;
+import com.android.internal.foldables.FoldGracePeriodProvider;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -164,6 +165,7 @@
@Mock private AttentionManagerInternal mAttentionManagerInternalMock;
@Mock private DreamManagerInternal mDreamManagerInternalMock;
@Mock private PowerManagerService.NativeWrapper mNativeWrapperMock;
+ @Mock private FoldGracePeriodProvider mFoldGracePeriodProvider;
@Mock private Notifier mNotifierMock;
@Mock private WirelessChargerDetector mWirelessChargerDetectorMock;
@Mock private AmbientDisplayConfiguration mAmbientDisplayConfigurationMock;
@@ -359,6 +361,11 @@
DeviceConfigParameterProvider createDeviceConfigParameterProvider() {
return mDeviceParameterProvider;
}
+
+ @Override
+ FoldGracePeriodProvider createFoldGracePeriodProvider() {
+ return mFoldGracePeriodProvider;
+ }
});
return mService;
}
@@ -520,6 +527,8 @@
@Test
public void testWakefulnessSleep_SoftSleepFlag_NoWakelocks() {
+ when(mFoldGracePeriodProvider.isEnabled()).thenReturn(false);
+
createService();
// Start with AWAKE state
startSystem();
@@ -533,6 +542,23 @@
}
@Test
+ public void testWakefulnessAwakeShowKeyguard_SoftSleepFlag_NoWakelocks() {
+ when(mFoldGracePeriodProvider.isEnabled()).thenReturn(true);
+
+ createService();
+ // Start with AWAKE state
+ startSystem();
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+ // Take a nap and verify we stay awake and the keyguard is requested
+ mService.getBinderServiceInstance().goToSleep(mClock.now(),
+ PowerManager.GO_TO_SLEEP_REASON_APPLICATION,
+ PowerManager.GO_TO_SLEEP_FLAG_SOFT_SLEEP);
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+ verify(mNotifierMock).showDismissibleKeyguard();
+ }
+
+ @Test
public void testWakefulnessSleep_SoftSleepFlag_WithPartialWakelock() {
createService();
// Start with AWAKE state
@@ -548,7 +574,7 @@
null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY,
null /* callback */);
- // Take a nap and verify we stay awake.
+ // Take a nap and verify we go to sleep.
mService.getBinderServiceInstance().goToSleep(mClock.now(),
PowerManager.GO_TO_SLEEP_REASON_APPLICATION,
PowerManager.GO_TO_SLEEP_FLAG_SOFT_SLEEP);
diff --git a/services/tests/servicestests/src/com/android/server/BootReceiverTest.java b/services/tests/servicestests/src/com/android/server/BootReceiverTest.java
new file mode 100644
index 0000000..523c5c0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/BootReceiverTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.test.AndroidTestCase;
+
+import com.android.server.os.TombstoneProtos;
+import com.android.server.os.TombstoneProtos.Tombstone;
+
+public class BootReceiverTest extends AndroidTestCase {
+ private static final String TAG = "BootReceiverTest";
+
+ public void testRemoveMemoryFromTombstone() {
+ Tombstone tombstoneBase = Tombstone.newBuilder()
+ .setBuildFingerprint("build_fingerprint")
+ .setRevision("revision")
+ .setPid(123)
+ .setTid(23)
+ .setUid(34)
+ .setSelinuxLabel("selinux_label")
+ .addCommandLine("cmd1")
+ .addCommandLine("cmd2")
+ .addCommandLine("cmd3")
+ .setProcessUptime(300)
+ .setAbortMessage("abort")
+ .addCauses(TombstoneProtos.Cause.newBuilder()
+ .setHumanReadable("cause1")
+ .setMemoryError(TombstoneProtos.MemoryError.newBuilder()
+ .setTool(TombstoneProtos.MemoryError.Tool.SCUDO)
+ .setType(TombstoneProtos.MemoryError.Type.DOUBLE_FREE)))
+ .addLogBuffers(TombstoneProtos.LogBuffer.newBuilder().setName("name").addLogs(
+ TombstoneProtos.LogMessage.newBuilder()
+ .setTimestamp("123")
+ .setMessage("message")))
+ .addOpenFds(TombstoneProtos.FD.newBuilder().setFd(1).setPath("path"))
+ .build();
+
+ Tombstone tombstoneWithoutMemory = tombstoneBase.toBuilder()
+ .putThreads(1, TombstoneProtos.Thread.newBuilder()
+ .setId(1)
+ .setName("thread1")
+ .addRegisters(TombstoneProtos.Register.newBuilder().setName("r1").setU64(1))
+ .addRegisters(TombstoneProtos.Register.newBuilder().setName("r2").setU64(2))
+ .addBacktraceNote("backtracenote1")
+ .addUnreadableElfFiles("files1")
+ .setTaggedAddrCtrl(1)
+ .setPacEnabledKeys(10)
+ .build())
+ .build();
+
+ Tombstone tombstoneWithMemory = tombstoneBase.toBuilder()
+ .addMemoryMappings(TombstoneProtos.MemoryMapping.newBuilder()
+ .setBeginAddress(1)
+ .setEndAddress(100)
+ .setOffset(10)
+ .setRead(true)
+ .setWrite(true)
+ .setExecute(false)
+ .setMappingName("mapping")
+ .setBuildId("build")
+ .setLoadBias(70))
+ .putThreads(1, TombstoneProtos.Thread.newBuilder()
+ .setId(1)
+ .setName("thread1")
+ .addRegisters(TombstoneProtos.Register.newBuilder().setName("r1").setU64(1))
+ .addRegisters(TombstoneProtos.Register.newBuilder().setName("r2").setU64(2))
+ .addBacktraceNote("backtracenote1")
+ .addUnreadableElfFiles("files1")
+ .addMemoryDump(TombstoneProtos.MemoryDump.newBuilder()
+ .setRegisterName("register1")
+ .setMappingName("mapping")
+ .setBeginAddress(10))
+ .setTaggedAddrCtrl(1)
+ .setPacEnabledKeys(10)
+ .build())
+ .build();
+
+ assertThat(BootReceiver.removeMemoryFromTombstone(tombstoneWithMemory))
+ .isEqualTo(tombstoneWithoutMemory);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 2e2495d..77b1455 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -180,8 +180,7 @@
REPORT_LOCKED_BOOT_COMPLETE_MSG);
@Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(
- SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT);
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void setUp() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 995d1f4..276c832 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -1982,8 +1982,8 @@
return new AssociationInfo(associationId, /* userId= */ 0, /* packageName=*/ null,
/* tag= */ null, MacAddress.BROADCAST_ADDRESS, /* displayName= */ "", deviceProfile,
/* associatedDevice= */ null, /* selfManaged= */ true,
- /* notifyOnDeviceNearby= */ false, /* revoked= */false, /* timeApprovedMs= */0,
- /* lastTimeConnectedMs= */0, /* systemDataSyncFlags= */ -1);
+ /* notifyOnDeviceNearby= */ false, /* revoked= */ false, /* pending= */ false,
+ /* timeApprovedMs= */0, /* lastTimeConnectedMs= */0, /* systemDataSyncFlags= */ -1);
}
/** Helper class to drop permissions temporarily and restore them at the end of a test. */
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
index a2f8c8b..6b85a32 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
@@ -25,10 +25,8 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.ContentResolver;
@@ -1223,35 +1221,6 @@
StartInputFlags.VIEW_HAS_FOCUS | StartInputFlags.IS_TEXT_EDITOR));
}
- @Test
- public void testInputMethodSettings_SwitchCurrentUser() {
- TestContext ownerUserContext = createMockContext(0 /* userId */);
- final InputMethodInfo systemIme = createFakeInputMethodInfo(
- "SystemIme", "fake.latin", true /* isSystem */);
- final InputMethodInfo nonSystemIme = createFakeInputMethodInfo("NonSystemIme",
- "fake.voice0", false /* isSystem */);
- final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
- methodMap.put(systemIme.getId(), systemIme);
-
- // Init InputMethodSettings for the owner user (userId=0), verify calls can get the
- // corresponding user's context, contentResolver and the resources configuration.
- InputMethodUtils.InputMethodSettings settings = new InputMethodUtils.InputMethodSettings(
- methodMap, 0 /* userId */);
- assertEquals(0, settings.getCurrentUserId());
-
- settings.getEnabledInputMethodSubtypeListLocked(nonSystemIme, true);
- verify(ownerUserContext.getResources(), atLeastOnce()).getConfiguration();
-
- // Calling switchCurrentUser to the secondary user (userId=10), verify calls can get the
- // corresponding user's context, contentResolver and the resources configuration.
- settings.switchCurrentUser(10 /* userId */);
- assertEquals(10, settings.getCurrentUserId());
-
- settings.getEnabledInputMethodSubtypeListLocked(nonSystemIme, true);
- verify(TestContext.getSecondaryUserContext().getResources(),
- atLeastOnce()).getConfiguration();
- }
-
private static IntArray createSubtypeHashCodeArrayFromStr(String subtypeHashCodesStr) {
final IntArray subtypes = new IntArray();
final TextUtils.SimpleStringSplitter imeSubtypeSplitter =
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
index 6a088d9..757abde 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
@@ -157,7 +157,7 @@
validateTagCount("uses-library", 1000, tag)
validateTagCount("activity-alias", 4000, tag)
validateTagCount("provider", 8000, tag)
- validateTagCount("activity", 40000, tag)
+ validateTagCount("activity", 30000, tag)
}
@Test
@@ -465,7 +465,8 @@
R.styleable.AndroidManifestData_pathAdvancedPattern,
4000
)
- validateTagAttr(tag, "mimeType", R.styleable.AndroidManifestData_mimeType, 512)
+ validateTagAttr(tag, "mimeType", R.styleable.AndroidManifestData_mimeType, 255)
+ validateTagAttr(tag, "mimeGroup", R.styleable.AndroidManifestData_mimeGroup, 1024)
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index cf8548c..1b77b99 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -19,17 +19,21 @@
import static android.app.Notification.GROUP_ALERT_ALL;
import static android.app.Notification.GROUP_ALERT_CHILDREN;
import static android.app.Notification.GROUP_ALERT_SUMMARY;
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
import static android.media.AudioAttributes.USAGE_NOTIFICATION;
import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
+
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -195,7 +199,8 @@
// TODO (b/291907312): remove feature flag
mSetFlagsRule.enableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER);
// Disable feature flags by default. Tests should enable as needed.
- mSetFlagsRule.disableFlags(Flags.FLAG_POLITE_NOTIFICATIONS, Flags.FLAG_EXPIRE_BITMAPS);
+ mSetFlagsRule.disableFlags(Flags.FLAG_POLITE_NOTIFICATIONS,
+ Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS, Flags.FLAG_VIBRATE_WHILE_UNLOCKED);
mService = spy(new NotificationManagerService(getContext(), mNotificationRecordLogger,
mNotificationInstanceIdSequence));
@@ -364,10 +369,20 @@
}
private NotificationRecord getNotificationRecord(int id,
- boolean insistent, boolean once,
- boolean noisy, boolean buzzy, boolean lights, boolean defaultVibration,
- boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior,
- boolean isLeanback, UserHandle userHandle) {
+ boolean insistent, boolean once,
+ boolean noisy, boolean buzzy, boolean lights, boolean defaultVibration,
+ boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior,
+ boolean isLeanback, UserHandle userHandle) {
+ return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, defaultVibration,
+ defaultSound, defaultLights, groupKey, groupAlertBehavior, isLeanback, userHandle,
+ mPkg);
+ }
+
+ private NotificationRecord getNotificationRecord(int id,
+ boolean insistent, boolean once,
+ boolean noisy, boolean buzzy, boolean lights, boolean defaultVibration,
+ boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior,
+ boolean isLeanback, UserHandle userHandle, String packageName) {
final Builder builder = new Builder(getContext())
.setContentTitle("foo")
@@ -427,8 +442,8 @@
when(packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK))
.thenReturn(isLeanback);
- StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id, mTag, mUid,
- mPid, n, userHandle, null, System.currentTimeMillis());
+ StatusBarNotification sbn = new StatusBarNotification(packageName, packageName, id, mTag,
+ mUid, mPid, n, userHandle, null, System.currentTimeMillis());
NotificationRecord r = new NotificationRecord(context, sbn, mChannel);
mService.addNotification(r);
return r;
@@ -1990,7 +2005,6 @@
public void testBeepVolume_politeNotif() throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
TestableFlagResolver flagResolver = new TestableFlagResolver();
- flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule1");
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
initAttentionHelper(flagResolver);
@@ -2015,13 +2029,11 @@
assertNotEquals(-1, r.getLastAudiblyAlertedMs());
}
- // TODO b/270456865: Only one of the two strategies will be released.
- // The other one need to be removed
@Test
- public void testBeepVolume_politeNotif_Strategy2() throws Exception {
+ public void testBeepVolume_politeNotif_GlobalStrategy() throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
TestableFlagResolver flagResolver = new TestableFlagResolver();
- flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule2");
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
initAttentionHelper(flagResolver);
@@ -2032,14 +2044,58 @@
mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
Mockito.reset(mRingtonePlayer);
- // update should beep at 0% volume
- r.isUpdate = true;
- mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ // Use different package for next notifications
+ NotificationRecord r2 = getNotificationRecord(mId, false /* insistent */, false /* once */,
+ true /* noisy */, false /* buzzy*/, false /* lights */, true, true,
+ false, null, Notification.GROUP_ALERT_ALL, false, mUser, "anotherPkg");
+
+ // update should beep at 50% volume
+ mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+ verifyBeepVolume(0.5f);
+
+ // Use different package for next notifications
+ NotificationRecord r3 = getNotificationRecord(mId, false /* insistent */, false /* once */,
+ true /* noisy */, false /* buzzy*/, false /* lights */, true, true,
+ false, null, Notification.GROUP_ALERT_ALL, false, mUser, "yetAnotherPkg");
+
+ // 2nd update should beep at 0% volume
+ Mockito.reset(mRingtonePlayer);
+ mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
verifyBeepVolume(0.0f);
+ verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+ }
+
+ @Test
+ public void testBeepVolume_politeNotif_GlobalStrategy_ChannelHasUserSound() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+ initAttentionHelper(flagResolver);
+
+ NotificationRecord r = getBeepyNotification();
+
+ // set up internal state
+ mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ Mockito.reset(mRingtonePlayer);
+
+ // Use package with user-set sounds for next notifications
+ mChannel = new NotificationChannel("test2", "test2", IMPORTANCE_DEFAULT);
+ mChannel.lockFields(NotificationChannel.USER_LOCKED_SOUND);
+ NotificationRecord r2 = getNotificationRecord(mId, false /* insistent */, false /* once */,
+ true /* noisy */, false /* buzzy*/, false /* lights */, true, true,
+ false, null, Notification.GROUP_ALERT_ALL, false, mUser, "anotherPkg");
+
+ // update should beep at 100% volume
+ mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+ verifyBeepVolume(1.0f);
+
// 2nd update should beep at 50% volume
Mockito.reset(mRingtonePlayer);
- mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
verifyBeepVolume(0.5f);
verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
@@ -2047,10 +2103,101 @@
}
@Test
+ public void testBeepVolume_politeNotif_applyPerApp() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+ // NOTIFICATION_COOLDOWN_ALL setting is enabled
+ Settings.System.putInt(getContext().getContentResolver(),
+ Settings.System.NOTIFICATION_COOLDOWN_ALL, 1);
+ initAttentionHelper(flagResolver);
+
+ NotificationRecord r = getBeepyNotification();
+
+ // set up internal state
+ mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ Mockito.reset(mRingtonePlayer);
+
+ // Use different channel for next notifications
+ mChannel = new NotificationChannel("test2", "test2", IMPORTANCE_DEFAULT);
+
+ // update should beep at 50% volume
+ NotificationRecord r2 = getBeepyNotification();
+ mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+ verifyBeepVolume(0.5f);
+
+ // 2nd update should beep at 0% volume
+ Mockito.reset(mRingtonePlayer);
+ mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+ verifyBeepVolume(0.0f);
+
+ // Use different package for next notifications
+ NotificationRecord r3 = getNotificationRecord(mId, false /* insistent */, false /* once */,
+ true /* noisy */, false /* buzzy*/, false /* lights */, true, true,
+ false, null, Notification.GROUP_ALERT_ALL, false, mUser, "anotherPkg");
+
+ // Update from new package should beep at 100% volume
+ Mockito.reset(mRingtonePlayer);
+ mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
+ verifyBeepVolume(1.0f);
+
+ verify(mAccessibilityService, times(4)).sendAccessibilityEvent(any(), anyInt());
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+ }
+
+ @Test
+ public void testBeepVolume_politeNotif_applyPerApp_ChannelHasUserSound() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+ // NOTIFICATION_COOLDOWN_ALL setting is enabled
+ Settings.System.putInt(getContext().getContentResolver(),
+ Settings.System.NOTIFICATION_COOLDOWN_ALL, 1);
+ initAttentionHelper(flagResolver);
+
+ NotificationRecord r = getBeepyNotification();
+
+ // set up internal state
+ mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ Mockito.reset(mRingtonePlayer);
+
+ // Use different channel for next notifications
+ mChannel = new NotificationChannel("test2", "test2", IMPORTANCE_DEFAULT);
+ mChannel.lockFields(NotificationChannel.USER_LOCKED_SOUND);
+
+ // update should beep at 100% volume
+ NotificationRecord r2 = getBeepyNotification();
+ mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+ verifyBeepVolume(1.0f);
+
+ // 2nd update should beep at 50% volume
+ Mockito.reset(mRingtonePlayer);
+ mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+ verifyBeepVolume(0.5f);
+
+ // Use different package for next notifications
+ mChannel = new NotificationChannel("test3", "test3", IMPORTANCE_DEFAULT);
+ NotificationRecord r3 = getNotificationRecord(mId, false /* insistent */, false /* once */,
+ true /* noisy */, false /* buzzy*/, false /* lights */, true, true,
+ false, null, Notification.GROUP_ALERT_ALL, false, mUser, "anotherPkg");
+
+ // Update from new package should beep at 100% volume
+ Mockito.reset(mRingtonePlayer);
+ mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
+ verifyBeepVolume(1.0f);
+
+ verify(mAccessibilityService, times(4)).sendAccessibilityEvent(any(), anyInt());
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+ }
+
+ @Test
public void testVibrationIntensity_politeNotif() throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
TestableFlagResolver flagResolver = new TestableFlagResolver();
- flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule1");
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
initAttentionHelper(flagResolver);
@@ -2076,37 +2223,9 @@
}
@Test
- public void testVibrationIntensity_politeNotif_Strategy2() throws Exception {
- mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
- TestableFlagResolver flagResolver = new TestableFlagResolver();
- flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule2");
- flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
- flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
- initAttentionHelper(flagResolver);
-
- NotificationRecord r = getBuzzyBeepyNotification();
-
- // set up internal state
- mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
-
- VibratorHelper vibratorHelper = mAttentionHelper.getVibratorHelper();
- Mockito.reset(vibratorHelper);
-
- // update should buzz at 0% intensity
- r.isUpdate = true;
- mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
-
- verify(vibratorHelper, times(1)).scale(any(), eq(0.0f));
- Mockito.reset(vibratorHelper);
-
- // 2nd update should buzz at 50% intensity
- mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
- verify(vibratorHelper, times(1)).scale(any(), eq(0.5f));
- }
-
- @Test
public void testBuzzOnlyOnScreenUnlock_politeNotif() throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_VIBRATE_WHILE_UNLOCKED);
TestableFlagResolver flagResolver = new TestableFlagResolver();
// When NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED setting is enabled
@@ -2161,7 +2280,6 @@
public void testBeepVolume_politeNotif_workProfile() throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
TestableFlagResolver flagResolver = new TestableFlagResolver();
- flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule1");
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
@@ -2202,7 +2320,6 @@
public void testBeepVolume_politeNotif_workProfile_disabled() throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
TestableFlagResolver flagResolver = new TestableFlagResolver();
- flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule1");
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java
index 5892793..c10c3c2 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java
@@ -170,6 +170,10 @@
Settings.Secure.putIntForUser(getContext().getContentResolver(),
Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, USER_SYSTEM);
mHistoryManager.mSettingsObserver.update(null, USER_SYSTEM);
+ // fake cloned settings to profile
+ Settings.Secure.putIntForUser(getContext().getContentResolver(),
+ Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, mProfileId);
+ mHistoryManager.mSettingsObserver.update(null, mProfileId);
// unlock user, verify that history is disabled for self and profile
mHistoryManager.onUserUnlocked(USER_SYSTEM);
@@ -181,6 +185,37 @@
}
@Test
+ public void testAddProfile_historyEnabledInPrimary() {
+ // create a history
+ mHistoryManager.onUserUnlocked(MIN_SECONDARY_USER_ID);
+ assertThat(mHistoryManager.doesHistoryExistForUser(MIN_SECONDARY_USER_ID)).isTrue();
+
+ // fake Settings#CLONE_TO_MANAGED_PROFILE
+ int newProfileId = 99;
+ Settings.Secure.putIntForUser(getContext().getContentResolver(),
+ Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 1, newProfileId);
+ mUsers = new ArrayList<>();
+ UserInfo userFullSecondary = new UserInfo();
+ userFullSecondary.id = MIN_SECONDARY_USER_ID;
+ mUsers.add(userFullSecondary);
+ UserInfo userProfile = new UserInfo();
+ userProfile.id = newProfileId;
+ mUsers.add(userProfile);
+ when(mUserManager.getUsers()).thenReturn(mUsers);
+
+ mProfiles = new int[] {MIN_SECONDARY_USER_ID, userProfile.id};
+ when(mUserManager.getProfileIds(MIN_SECONDARY_USER_ID, true)).thenReturn(mProfiles);
+ when(mUserManager.getProfileIds(userProfile.id, true))
+ .thenReturn(new int[] {userProfile.id});
+ when(mUserManager.getProfileParent(userProfile.id)).thenReturn(userFullSecondary);
+
+ // add profile
+ mHistoryManager.onUserAdded(newProfileId);
+ mHistoryManager.onUserUnlocked(newProfileId);
+ assertThat(mHistoryManager.doesHistoryExistForUser(newProfileId)).isTrue();
+ }
+
+ @Test
public void testOnUserUnlocked_historyDisabledThenEnabled() {
// create a history
mHistoryManager.onUserUnlocked(USER_SYSTEM);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index f1edd9a..c1f35cc 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -958,22 +958,24 @@
mTestNotificationChannel.setAllowBubbles(channelEnabled);
}
- private void setUpPrefsForHistory(int uid, boolean globalEnabled) throws Exception {
+ private void setUpPrefsForHistory(@UserIdInt int userId, boolean globalEnabled)
+ throws Exception {
initNMS(SystemService.PHASE_ACTIVITY_MANAGER_READY);
// Sets NOTIFICATION_HISTORY_ENABLED setting for calling process uid
Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.NOTIFICATION_HISTORY_ENABLED, globalEnabled ? 1 : 0, uid);
+ Settings.Secure.NOTIFICATION_HISTORY_ENABLED, globalEnabled ? 1 : 0, userId);
// Sets NOTIFICATION_HISTORY_ENABLED setting for uid 0
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.NOTIFICATION_HISTORY_ENABLED, globalEnabled ? 1 : 0);
+ setUsers(new int[] {0, userId});
// Forces an update by calling observe on mSettingsObserver, which picks up the settings
// changes above.
mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START, mMainLooper);
assertEquals(globalEnabled, Settings.Secure.getIntForUser(mContext.getContentResolver(),
- Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0 /* =def */, uid) != 0);
+ Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0 /* =def */, userId) != 0);
}
private StatusBarNotification generateSbn(String pkg, int uid, long postTime, int userId) {
@@ -10443,7 +10445,7 @@
@Test
public void testHandleOnPackageRemoved_ClearsHistory() throws Exception {
// Enables Notification History setting
- setUpPrefsForHistory(mUid, true /* =enabled */);
+ setUpPrefsForHistory(mUserId, true /* =enabled */);
// Posts a notification to the mTestNotificationChannel.
final NotificationRecord notif = generateNotificationRecord(
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
index 6cc1c43..08af09c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
@@ -20,7 +20,11 @@
import static com.google.common.truth.Truth.assertThat;
+import android.app.Flags;
import android.app.NotificationManager.Policy;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenPolicy;
import androidx.test.filters.SmallTest;
@@ -28,6 +32,7 @@
import com.android.server.UiServiceTestCase;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -35,6 +40,9 @@
@RunWith(AndroidJUnit4.class)
public class ZenAdaptersTest extends UiServiceTestCase {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Test
public void notificationPolicyToZenPolicy_allCallers() {
Policy policy = new Policy(Policy.PRIORITY_CATEGORY_CALLS, Policy.PRIORITY_SENDERS_ANY, 0);
@@ -127,4 +135,35 @@
assertThat(zenPolicy.getVisualEffectPeek()).isEqualTo(ZenPolicy.STATE_UNSET);
assertThat(zenPolicy.getVisualEffectStatusBar()).isEqualTo(ZenPolicy.STATE_UNSET);
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void notificationPolicyToZenPolicy_modesApi_priorityChannels() {
+ Policy policy = new Policy(0, 0, 0, 0,
+ Policy.policyState(false, true), 0);
+
+ ZenPolicy zenPolicy = notificationPolicyToZenPolicy(policy);
+ assertThat(zenPolicy.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+
+ Policy notAllowed = new Policy(0, 0, 0, 0,
+ Policy.policyState(false, false), 0);
+ ZenPolicy zenPolicyNotAllowed = notificationPolicyToZenPolicy(notAllowed);
+ assertThat(zenPolicyNotAllowed.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_NONE);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MODES_API)
+ public void notificationPolicyToZenPolicy_noModesApi_priorityChannelsUnset() {
+ Policy policy = new Policy(0, 0, 0, 0,
+ Policy.policyState(false, true), 0);
+
+ ZenPolicy zenPolicy = notificationPolicyToZenPolicy(policy);
+ assertThat(zenPolicy.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_UNSET);
+
+ Policy notAllowed = new Policy(0, 0, 0, 0,
+ Policy.policyState(false, false), 0);
+ ZenPolicy zenPolicyNotAllowed = notificationPolicyToZenPolicy(notAllowed);
+ assertThat(zenPolicyNotAllowed.getAllowedChannels())
+ .isEqualTo(ZenPolicy.CHANNEL_TYPE_UNSET);
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 25c0cd9..f84d8e9 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -3876,6 +3876,7 @@
.allowCalls(PEOPLE_TYPE_CONTACTS)
.allowConversations(CONVERSATION_SENDERS_IMPORTANT)
.hideAllVisualEffects()
+ .allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY)
.build();
assertThat(mZenModeHelper.mConfig.automaticRules.values())
.comparingElementsUsing(IGNORE_TIMESTAMPS)
@@ -3907,6 +3908,7 @@
.allowCalls(PEOPLE_TYPE_STARRED)
.allowConversations(CONVERSATION_SENDERS_IMPORTANT)
.hideAllVisualEffects()
+ .allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY)
.build();
assertThat(mZenModeHelper.mConfig.automaticRules.values())
.comparingElementsUsing(IGNORE_TIMESTAMPS)
diff --git a/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java b/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java
new file mode 100644
index 0000000..c3da903
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java
@@ -0,0 +1,354 @@
+/*
+ * 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.policy;
+
+import static android.os.PowerManager.WAKE_REASON_CAMERA_LAUNCH;
+import static android.os.PowerManager.WAKE_REASON_LID;
+import static android.os.PowerManager.WAKE_REASON_GESTURE;
+import static android.os.PowerManager.WAKE_REASON_POWER_BUTTON;
+import static android.os.PowerManager.WAKE_REASON_WAKE_KEY;
+import static android.os.PowerManager.WAKE_REASON_WAKE_MOTION;
+import static android.view.InputDevice.SOURCE_ROTARY_ENCODER;
+import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
+import static android.view.KeyEvent.KEYCODE_HOME;
+import static android.view.KeyEvent.KEYCODE_POWER;
+import static android.view.KeyEvent.KEYCODE_STEM_PRIMARY;
+
+import static com.android.internal.R.bool.config_allowTheaterModeWakeFromKey;
+import static com.android.internal.R.bool.config_allowTheaterModeWakeFromPowerKey;
+import static com.android.internal.R.bool.config_allowTheaterModeWakeFromMotion;
+import static com.android.internal.R.bool.config_allowTheaterModeWakeFromCameraLens;
+import static com.android.internal.R.bool.config_allowTheaterModeWakeFromLidSwitch;
+import static com.android.internal.R.bool.config_allowTheaterModeWakeFromGesture;
+import static com.android.server.policy.Flags.FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.Resources;
+import android.os.PowerManager;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.os.Clock;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
+import com.android.server.LocalServices;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.function.BooleanSupplier;
+/**
+ * Test class for {@link WindowWakeUpPolicy}.
+ *
+ * <p>Build/Install/Run: atest WmTests:WindowWakeUpPolicyTests
+ */
+public final class WindowWakeUpPolicyTests {
+ @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Mock PowerManager mPowerManager;
+ @Mock Clock mClock;
+ @Mock WindowWakeUpPolicyInternal.InputWakeUpDelegate mInputWakeUpDelegate;
+
+ private Context mContextSpy;
+ private Resources mResourcesSpy;
+
+ private WindowWakeUpPolicy mPolicy;
+
+ @Before
+ public void setUp() {
+ mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
+ mResourcesSpy = spy(mContextSpy.getResources());
+ when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
+ when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
+ LocalServices.removeServiceForTest(WindowWakeUpPolicyInternal.class);
+ }
+
+ @Test
+ public void testSupportsInputWakeDelegatse_publishesLocalService() {
+ mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
+
+ mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+
+ assertThat(LocalServices.getService(WindowWakeUpPolicyInternal.class)).isNotNull();
+ }
+
+ @Test
+ public void testDoesNotSupportInputWakeDelegatse_doesNotPublishLocalService() {
+ mSetFlagsRule.disableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
+
+ mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+
+ assertThat(LocalServices.getService(WindowWakeUpPolicyInternal.class)).isNull();
+ }
+
+ @Test
+ public void testMotionWakeUpDelegation_wakePowerManagerIfDelegateDoesNotHandleWake() {
+ setTheaterModeEnabled(false);
+ mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
+ mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+ LocalServices.getService(WindowWakeUpPolicyInternal.class)
+ .setInputWakeUpDelegate(mInputWakeUpDelegate);
+
+ setDelegatedMotionWakeUpResult(true);
+
+ // Verify the policy wake up call succeeds because of the call on the delegate, and not
+ // because of a PowerManager wake up.
+ assertThat(mPolicy.wakeUpFromMotion(200, SOURCE_TOUCHSCREEN, true)).isTrue();
+ verify(mInputWakeUpDelegate).wakeUpFromMotion(200, SOURCE_TOUCHSCREEN, true);
+ verifyNoPowerManagerWakeUp();
+
+ setDelegatedMotionWakeUpResult(false);
+
+ // Verify the policy wake up call succeeds because of the PowerManager wake up, since the
+ // delegate would not handle the wake up request.
+ assertThat(mPolicy.wakeUpFromMotion(300, SOURCE_ROTARY_ENCODER, false)).isTrue();
+ verify(mInputWakeUpDelegate).wakeUpFromMotion(300, SOURCE_ROTARY_ENCODER, false);
+ verify(mPowerManager).wakeUp(300, WAKE_REASON_WAKE_MOTION, "android.policy:MOTION");
+ }
+
+ @Test
+ public void testKeyWakeUpDelegation_wakePowerManagerIfDelegateDoesNotHandleWake() {
+ setTheaterModeEnabled(false);
+ mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
+ mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+ LocalServices.getService(WindowWakeUpPolicyInternal.class)
+ .setInputWakeUpDelegate(mInputWakeUpDelegate);
+
+ setDelegatedKeyWakeUpResult(true);
+
+ // Verify the policy wake up call succeeds because of the call on the delegate, and not
+ // because of a PowerManager wake up.
+ assertThat(mPolicy.wakeUpFromKey(200, KEYCODE_POWER, true)).isTrue();
+ verify(mInputWakeUpDelegate).wakeUpFromKey(200, KEYCODE_POWER, true);
+ verifyNoPowerManagerWakeUp();
+
+ setDelegatedKeyWakeUpResult(false);
+
+ // Verify the policy wake up call succeeds because of the PowerManager wake up, since the
+ // delegate would not handle the wake up request.
+ assertThat(mPolicy.wakeUpFromKey(300, KEYCODE_STEM_PRIMARY, false)).isTrue();
+ verify(mInputWakeUpDelegate).wakeUpFromKey(300, KEYCODE_STEM_PRIMARY, false);
+ verify(mPowerManager).wakeUp(300, WAKE_REASON_WAKE_KEY, "android.policy:KEY");
+ }
+
+ @Test
+ public void testDelegatedKeyWakeIsSubjectToPolicyChecks() {
+ mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
+ setDelegatedKeyWakeUpResult(true);
+ setTheaterModeEnabled(true);
+ setBooleanRes(config_allowTheaterModeWakeFromKey, false);
+ setBooleanRes(config_allowTheaterModeWakeFromPowerKey, false);
+ mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+ LocalServices.getService(WindowWakeUpPolicyInternal.class)
+ .setInputWakeUpDelegate(mInputWakeUpDelegate);
+
+ // Check that the wake up does not happen because the theater mode policy check fails.
+ assertThat(mPolicy.wakeUpFromKey(200, KEYCODE_POWER, true)).isFalse();
+ verify(mInputWakeUpDelegate, never()).wakeUpFromKey(anyLong(), anyInt(), anyBoolean());
+ }
+
+ @Test
+ public void testDelegatedMotionWakeIsSubjectToPolicyChecks() {
+ mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
+ setDelegatedMotionWakeUpResult(true);
+ setTheaterModeEnabled(true);
+ setBooleanRes(config_allowTheaterModeWakeFromMotion, false);
+ mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+ LocalServices.getService(WindowWakeUpPolicyInternal.class)
+ .setInputWakeUpDelegate(mInputWakeUpDelegate);
+
+ // Check that the wake up does not happen because the theater mode policy check fails.
+ assertThat(mPolicy.wakeUpFromMotion(200, SOURCE_TOUCHSCREEN, true)).isFalse();
+ verify(mInputWakeUpDelegate, never()).wakeUpFromMotion(anyLong(), anyInt(), anyBoolean());
+ }
+
+ @Test
+ public void testWakeUpFromMotion() {
+ runPowerManagerUpChecks(
+ () -> mPolicy.wakeUpFromMotion(mClock.uptimeMillis(), SOURCE_TOUCHSCREEN, true),
+ config_allowTheaterModeWakeFromMotion,
+ WAKE_REASON_WAKE_MOTION,
+ "android.policy:MOTION");
+ }
+
+ @Test
+ public void testWakeUpFromKey_nonPowerKey() {
+ runPowerManagerUpChecks(
+ () -> mPolicy.wakeUpFromKey(mClock.uptimeMillis(), KEYCODE_HOME, true),
+ config_allowTheaterModeWakeFromKey,
+ WAKE_REASON_WAKE_KEY,
+ "android.policy:KEY");
+ }
+
+ @Test
+ public void testWakeUpFromKey_powerKey() {
+ // Disable the resource affecting all wake keys because it affects power key as well.
+ // That way, power key wake during theater mode will solely be controlled by
+ // `config_allowTheaterModeWakeFromPowerKey` in the checks.
+ setBooleanRes(config_allowTheaterModeWakeFromKey, false);
+
+ // Test with power key
+ runPowerManagerUpChecks(
+ () -> mPolicy.wakeUpFromKey(mClock.uptimeMillis(), KEYCODE_POWER, true),
+ config_allowTheaterModeWakeFromPowerKey,
+ WAKE_REASON_POWER_BUTTON,
+ "android.policy:POWER");
+
+ // Test that power key wake ups happen during theater mode as long as wake-keys are allowed
+ // even if the power-key specific theater mode config is disabled.
+ setBooleanRes(config_allowTheaterModeWakeFromPowerKey, false);
+ runPowerManagerUpChecks(
+ () -> mPolicy.wakeUpFromKey(mClock.uptimeMillis(), KEYCODE_POWER, false),
+ config_allowTheaterModeWakeFromKey,
+ WAKE_REASON_POWER_BUTTON,
+ "android.policy:POWER");
+ }
+
+ @Test
+ public void testWakeUpFromLid() {
+ runPowerManagerUpChecks(
+ () -> mPolicy.wakeUpFromLid(),
+ config_allowTheaterModeWakeFromLidSwitch,
+ WAKE_REASON_LID,
+ "android.policy:LID");
+ }
+
+ @Test
+ public void testWakeUpFromWakeGesture() {
+ runPowerManagerUpChecks(
+ () -> mPolicy.wakeUpFromWakeGesture(),
+ config_allowTheaterModeWakeFromGesture,
+ WAKE_REASON_GESTURE,
+ "android.policy:GESTURE");
+ }
+
+ @Test
+ public void testwakeUpFromCameraCover() {
+ runPowerManagerUpChecks(
+ () -> mPolicy.wakeUpFromCameraCover(mClock.uptimeMillis()),
+ config_allowTheaterModeWakeFromCameraLens,
+ WAKE_REASON_CAMERA_LAUNCH,
+ "android.policy:CAMERA_COVER");
+ }
+
+ @Test
+ public void testWakeUpFromPowerKeyCameraGesture() {
+ // Disable the resource affecting all wake keys because it affects power key as well.
+ // That way, power key wake during theater mode will solely be controlled by
+ // `config_allowTheaterModeWakeFromPowerKey` in the checks.
+ setBooleanRes(config_allowTheaterModeWakeFromKey, false);
+
+ runPowerManagerUpChecks(
+ () -> mPolicy.wakeUpFromPowerKeyCameraGesture(),
+ config_allowTheaterModeWakeFromPowerKey,
+ WAKE_REASON_CAMERA_LAUNCH,
+ "android.policy:CAMERA_GESTURE_PREVENT_LOCK");
+ }
+
+ private void runPowerManagerUpChecks(
+ BooleanSupplier wakeUpCall,
+ int theatherModeWakeResId,
+ int expectedWakeReason,
+ String expectedWakeDetails) {
+ // Test under theater mode enabled.
+ setTheaterModeEnabled(true);
+
+ Mockito.reset(mPowerManager);
+ setBooleanRes(theatherModeWakeResId, true);
+ mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+ setUptimeMillis(200);
+ assertWithMessage("Wake should happen in theater mode when config allows it.")
+ .that(wakeUpCall.getAsBoolean()).isTrue();
+ verify(mPowerManager).wakeUp(200L, expectedWakeReason, expectedWakeDetails);
+
+ Mockito.reset(mPowerManager);
+ setBooleanRes(theatherModeWakeResId, false);
+ mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+ setUptimeMillis(250);
+ assertWithMessage("Wake should not happen in theater mode when config disallows it.")
+ .that(wakeUpCall.getAsBoolean()).isFalse();
+ verifyNoPowerManagerWakeUp();
+
+ // Cases when theater mode is disabled.
+ setTheaterModeEnabled(false);
+
+ Mockito.reset(mPowerManager);
+ setBooleanRes(theatherModeWakeResId, true);
+ mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+ setUptimeMillis(300);
+ assertWithMessage("Wake should happen when not in theater mode.")
+ .that(wakeUpCall.getAsBoolean()).isTrue();
+ verify(mPowerManager).wakeUp(300L, expectedWakeReason, expectedWakeDetails);
+
+ Mockito.reset(mPowerManager);
+ setBooleanRes(theatherModeWakeResId, false);
+ mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+ setUptimeMillis(350);
+ assertWithMessage("Wake should happen when not in theater mode.")
+ .that(wakeUpCall.getAsBoolean()).isTrue();
+ verify(mPowerManager).wakeUp(350L, expectedWakeReason, expectedWakeDetails);
+ }
+
+ private void verifyNoPowerManagerWakeUp() {
+ verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
+ }
+
+ private void setBooleanRes(int resId, boolean val) {
+ when(mResourcesSpy.getBoolean(resId)).thenReturn(val);
+ }
+
+ private void setUptimeMillis(long uptimeMillis) {
+ when(mClock.uptimeMillis()).thenReturn(uptimeMillis);
+ }
+
+ private void setTheaterModeEnabled(boolean enabled) {
+ Settings.Global.putInt(
+ mContextSpy.getContentResolver(), Settings.Global.THEATER_MODE_ON, enabled ? 1 : 0);
+ }
+
+ private void setDelegatedMotionWakeUpResult(boolean result) {
+ when(mInputWakeUpDelegate.wakeUpFromMotion(anyLong(), anyInt(), anyBoolean()))
+ .thenReturn(result);
+ }
+
+ private void setDelegatedKeyWakeUpResult(boolean result) {
+ when(mInputWakeUpDelegate.wakeUpFromKey(anyLong(), anyInt(), anyBoolean()))
+ .thenReturn(result);
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index f049b33..f5282cb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -143,7 +143,6 @@
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
-import android.util.MergedConfiguration;
import android.util.MutableBoolean;
import android.view.DisplayInfo;
import android.view.IRemoteAnimationFinishedCallback;
@@ -395,8 +394,7 @@
activity.setState(RESUMED, "Testing");
task.onRequestedOverrideConfigurationChanged(task.getConfiguration());
- activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
- activity.getConfiguration()));
+ activity.setLastReportedConfiguration(new Configuration(), activity.getConfiguration());
activity.info.configChanges &= ~CONFIG_ORIENTATION;
final Configuration newConfig = new Configuration(task.getConfiguration());
@@ -420,8 +418,7 @@
activity.setState(RESUMED, "Testing");
task.onRequestedOverrideConfigurationChanged(task.getConfiguration());
- activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
- activity.getConfiguration()));
+ activity.setLastReportedConfiguration(new Configuration(), activity.getConfiguration());
activity.info.configChanges &= ~CONFIG_ORIENTATION;
final Configuration newConfig = new Configuration(task.getConfiguration());
@@ -447,8 +444,7 @@
activity.setState(RESUMED, "Testing");
task.onRequestedOverrideConfigurationChanged(task.getConfiguration());
- activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
- activity.getConfiguration()));
+ activity.setLastReportedConfiguration(new Configuration(), activity.getConfiguration());
activity.info.configChanges &= ~CONFIG_ORIENTATION;
final Configuration newConfig = new Configuration(task.getConfiguration());
@@ -468,8 +464,7 @@
activity.setState(RESUMED, "Testing");
task.onRequestedOverrideConfigurationChanged(task.getConfiguration());
- activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
- activity.getConfiguration()));
+ activity.setLastReportedConfiguration(new Configuration(), activity.getConfiguration());
activity.info.configChanges &= ~ActivityInfo.CONFIG_FONT_SCALE;
final Configuration newConfig = new Configuration(task.getConfiguration());
@@ -571,8 +566,7 @@
.build();
activity.setState(RESUMED, "Testing");
- activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
- activity.getConfiguration()));
+ activity.setLastReportedConfiguration(new Configuration(), activity.getConfiguration());
clearInvocations(mClientLifecycleManager);
@@ -799,8 +793,7 @@
doReturn(false).when(stack).isTranslucent(any());
assertTrue(task.shouldBeVisible(null /* starting */));
- activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
- activity.getConfiguration()));
+ activity.setLastReportedConfiguration(new Configuration(), activity.getConfiguration());
final Configuration newConfig = new Configuration(activity.getConfiguration());
final int shortSide = newConfig.screenWidthDp == newConfig.screenHeightDp
@@ -846,7 +839,7 @@
}
@Test
- public void testTakeOptions() {
+ public void testTakeSceneTransitionInfo() {
final ActivityRecord activity = createActivityWithTask();
ActivityOptions opts = ActivityOptions.makeRemoteAnimation(
new RemoteAnimationAdapter(new Stub() {
@@ -864,7 +857,9 @@
}
}, 0, 0));
activity.updateOptionsLocked(opts);
- assertNotNull(activity.takeOptions());
+ // Ensure the SceneTransitionInfo is null (since the ActivityOptions is for remote
+ // animation and AR#takeSceneTransitionInfo also clear the AR#mPendingOptions
+ assertNull(activity.takeSceneTransitionInfo());
assertNull(activity.getOptions());
final AppTransition appTransition = activity.mDisplayContent.mAppTransition;
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index ac18f80..d83824a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -464,9 +464,10 @@
// Simulate ActivityOptions#makeSceneTransitionAnimation
final Bundle myBundle = new Bundle();
myBundle.putInt(ActivityOptions.KEY_ANIM_TYPE, ANIM_SCENE_TRANSITION);
- myBundle.putParcelable("android:activity.transitionCompleteListener",
- mock(android.os.ResultReceiver.class));
final ActivityOptions options = new ActivityOptions(myBundle);
+ final ActivityOptions.SceneTransitionInfo info = new ActivityOptions.SceneTransitionInfo();
+ info.setResultReceiver(mock(android.os.ResultReceiver.class));
+ options.setSceneTransitionInfo(info);
final ActivityRecord testActivity = new ActivityBuilder(mAtm)
.setCreateTask(true)
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
index dfa595c..99d354a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
@@ -55,6 +55,11 @@
@RunWith(WindowTestRunner.class)
public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
+ // The fields to override the current DisplayInfo.
+ private String mUniqueId;
+ private int mColorMode;
+ private int mLogicalDensityDpi;
+
@Override
protected void onBeforeSystemServicesCreated() {
// Set other flags to their default values
@@ -73,7 +78,7 @@
public void testUpdate_deferrableFieldChangedTransitionStarted_deferrableFieldUpdated() {
performInitialDisplayUpdate();
- givenDisplayInfo(/* uniqueId= */ "old");
+ mUniqueId = "old";
Runnable onUpdated = mock(Runnable.class);
mDisplayContent.requestDisplayUpdate(onUpdated);
@@ -82,11 +87,21 @@
verify(onUpdated).run();
clearInvocations(mDisplayContent.mTransitionController, onUpdated);
- givenDisplayInfo(/* uniqueId= */ "new");
+ mUniqueId = "new";
mDisplayContent.requestDisplayUpdate(onUpdated);
captureStartTransitionCollection().getValue().onCollectStarted(/* deferred= */ true);
verify(onUpdated).run();
+ verify(mDisplayContent.mTransitionController).requestStartTransition(
+ any(), any(), any(), any());
assertThat(mDisplayContent.getDisplayInfo().uniqueId).isEqualTo("new");
+ clearInvocations(mDisplayContent.mTransitionController, onUpdated);
+
+ mLogicalDensityDpi += 100;
+ mDisplayContent.requestDisplayUpdate(onUpdated);
+ captureStartTransitionCollection().getValue().onCollectStarted(/* deferred= */ true);
+ verify(onUpdated).run();
+ verify(mDisplayContent.mTransitionController).requestStartTransition(
+ any(), any(), any(), any());
}
@Test
@@ -94,7 +109,8 @@
performInitialDisplayUpdate();
// Update only color mode (non-deferrable field) and keep the same unique id
- givenDisplayInfo(/* uniqueId= */ "initial_unique_id", /* colorMode= */ 123);
+ mUniqueId = "initial_unique_id";
+ mColorMode = 123;
Runnable onUpdated = mock(Runnable.class);
mDisplayContent.requestDisplayUpdate(onUpdated);
@@ -107,7 +123,8 @@
performInitialDisplayUpdate();
// Update only color mode (non-deferrable field) and keep the same unique id
- givenDisplayInfo(/* uniqueId= */ "initial_unique_id", /* colorMode= */ 123);
+ mUniqueId = "initial_unique_id";
+ mColorMode = 123;
mDisplayContent.requestDisplayUpdate(mock(Runnable.class));
assertThat(mDisplayContent.getDisplayInfo().colorMode).isEqualTo(123);
@@ -116,7 +133,7 @@
// Update unique id (deferrable field), keep the same color mode,
// this update should be deferred
- givenDisplayInfo(/* uniqueId= */ "new_unique_id", /* colorMode= */ 123);
+ mUniqueId = "new_unique_id";
mDisplayContent.requestDisplayUpdate(mock(Runnable.class));
assertThat(mDisplayContent.getDisplayInfo().colorMode).isEqualTo(123);
@@ -126,7 +143,7 @@
// Update color mode again and keep the same unique id, color mode update
// should not be deferred, unique id update is still deferred as transition
// has not started collecting yet
- givenDisplayInfo(/* uniqueId= */ "new_unique_id", /* colorMode= */ 456);
+ mColorMode = 456;
Runnable onUpdated = mock(Runnable.class);
mDisplayContent.requestDisplayUpdate(onUpdated);
@@ -146,14 +163,14 @@
@Test
public void testUpdate_deferrableFieldUpdatedTransitionPending_fieldNotUpdated() {
performInitialDisplayUpdate();
- givenDisplayInfo(/* uniqueId= */ "old");
+ mUniqueId = "old";
Runnable onUpdated = mock(Runnable.class);
mDisplayContent.requestDisplayUpdate(onUpdated);
captureStartTransitionCollection().getValue().onCollectStarted(/* deferred= */ true);
verify(onUpdated).run();
clearInvocations(mDisplayContent.mTransitionController, onUpdated);
- givenDisplayInfo(/* uniqueId= */ "new");
+ mUniqueId = "new";
mDisplayContent.requestDisplayUpdate(onUpdated);
captureStartTransitionCollection(); // do not continue by not starting the collection
@@ -164,7 +181,7 @@
@Test
public void testTwoDisplayUpdates_transitionStarted_displayUpdated() {
performInitialDisplayUpdate();
- givenDisplayInfo(/* uniqueId= */ "old");
+ mUniqueId = "old";
Runnable onUpdated = mock(Runnable.class);
mDisplayContent.requestDisplayUpdate(onUpdated);
captureStartTransitionCollection().getValue()
@@ -173,10 +190,10 @@
clearInvocations(mDisplayContent.mTransitionController, onUpdated);
// Perform two display updates while WM is 'busy'
- givenDisplayInfo(/* uniqueId= */ "new1");
+ mUniqueId = "new1";
Runnable onUpdated1 = mock(Runnable.class);
mDisplayContent.requestDisplayUpdate(onUpdated1);
- givenDisplayInfo(/* uniqueId= */ "new2");
+ mUniqueId = "new2";
Runnable onUpdated2 = mock(Runnable.class);
mDisplayContent.requestDisplayUpdate(onUpdated2);
@@ -215,22 +232,19 @@
return callbackCaptor;
}
- private void givenDisplayInfo(String uniqueId) {
- givenDisplayInfo(uniqueId, /* colorMode= */ 0);
- }
+ private void performInitialDisplayUpdate() {
+ mUniqueId = "initial_unique_id";
+ mColorMode = 0;
+ mLogicalDensityDpi = 400;
- private void givenDisplayInfo(String uniqueId, int colorMode) {
spyOn(mDisplayContent.mDisplay);
doAnswer(invocation -> {
DisplayInfo info = invocation.getArgument(0);
- info.uniqueId = uniqueId;
- info.colorMode = colorMode;
+ info.uniqueId = mUniqueId;
+ info.colorMode = mColorMode;
+ info.logicalDensityDpi = mLogicalDensityDpi;
return null;
}).when(mDisplayContent.mDisplay).getDisplayInfo(any());
- }
-
- private void performInitialDisplayUpdate() {
- givenDisplayInfo(/* uniqueId= */ "initial_unique_id", /* colorMode= */ 0);
Runnable onUpdated = mock(Runnable.class);
mDisplayContent.requestDisplayUpdate(onUpdated);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 28fecd6..6013063 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -76,7 +76,6 @@
import android.os.PowerManager;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
-import android.util.MergedConfiguration;
import android.util.Pair;
import androidx.test.filters.MediumTest;
@@ -545,8 +544,7 @@
assertNotEquals(activity.getConfiguration().orientation, rotatedConfig.orientation);
// Assume the activity was shown in different orientation. For example, the top activity is
// landscape and the portrait lockscreen is shown.
- activity.setLastReportedConfiguration(
- new MergedConfiguration(mAtm.getGlobalConfiguration(), rotatedConfig));
+ activity.setLastReportedConfiguration(mAtm.getGlobalConfiguration(), rotatedConfig);
activity.setState(STOPPED, "sleep");
display.setIsSleeping(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java b/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java
new file mode 100644
index 0000000..71dbc57
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.wm;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.wm.SensitiveContentPackages.PackageInfo;
+
+import org.junit.After;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Build/Install/Run:
+ * atest WmTests:SensitiveContentPackagesTest
+ */
+@SmallTest
+@Presubmit
+public class SensitiveContentPackagesTest {
+ private static final String APP_PKG_1 = "com.android.server.wm.one";
+ private static final String APP_PKG_2 = "com.android.server.wm.two";
+ private static final String APP_PKG_3 = "com.android.server.wm.three";
+
+ private static final int APP_UID_1 = 5;
+ private static final int APP_UID_2 = 6;
+ private static final int APP_UID_3 = 7;
+
+
+ private final SensitiveContentPackages mSensitiveContentPackages =
+ new SensitiveContentPackages();
+
+ @After
+ public void tearDown() {
+ mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ }
+
+ @Test
+ public void setShouldBlockScreenCaptureForApp() {
+ Set<PackageInfo> blockedApps =
+ Set.of(new PackageInfo(APP_PKG_1, APP_UID_1),
+ new PackageInfo(APP_PKG_1, APP_UID_2),
+ new PackageInfo(APP_PKG_2, APP_UID_1),
+ new PackageInfo(APP_PKG_2, APP_UID_2));
+
+ mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(blockedApps);
+
+ assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1));
+ assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_3));
+
+ assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1));
+ assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_3));
+
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_1));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_2));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_3));
+ }
+
+ @Test
+ public void setShouldBlockScreenCaptureForApp_empty() {
+ Set<PackageInfo> blockedApps =
+ Set.of(new PackageInfo(APP_PKG_1, APP_UID_1),
+ new PackageInfo(APP_PKG_1, APP_UID_2),
+ new PackageInfo(APP_PKG_2, APP_UID_1),
+ new PackageInfo(APP_PKG_2, APP_UID_2));
+
+ mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(blockedApps);
+ mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_3));
+
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_3));
+
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_1));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_2));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_3));
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
index 0f1e4d1..6c5f975 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
@@ -135,6 +135,14 @@
assertFalse(r.isSyncFinished(r.getSyncGroup()));
r.finishRelaunching();
assertTrue(r.isSyncFinished(r.getSyncGroup()));
+ assertEquals(SYNC_STATE_READY, r.mSyncState);
+
+ // If the container has finished the sync, isSyncFinished should not change the sync state.
+ final BLASTSyncEngine.SyncGroup syncGroup = r.getSyncGroup();
+ r.finishSync(mTransaction, syncGroup, false /* cancel */);
+ assertEquals(SYNC_STATE_NONE, r.mSyncState);
+ assertTrue(r.isSyncFinished(syncGroup));
+ assertEquals(SYNC_STATE_NONE, r.mSyncState);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 45e1e95..b360800 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -43,6 +43,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.policy.WindowManagerPolicy.USER_ROTATION_FREE;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
+import static com.android.server.wm.TaskFragment.EMBEDDED_DIM_AREA_PARENT_TASK;
import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
import static com.google.common.truth.Truth.assertThat;
@@ -1620,6 +1621,29 @@
}
@Test
+ public void testBoostDimmingTaskFragmentOnTask() {
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final Task task = createTask(mDisplayContent);
+ final TaskFragment primary = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final TaskFragment secondary = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+
+ primary.mVisibleRequested = true;
+ secondary.mVisibleRequested = true;
+ primary.setAdjacentTaskFragment(secondary);
+ secondary.setAdjacentTaskFragment(primary);
+ primary.setEmbeddedDimArea(EMBEDDED_DIM_AREA_PARENT_TASK);
+ doReturn(true).when(primary).shouldBoostDimmer();
+ task.assignChildLayers(t);
+
+ // The layers are initially assigned via the hierarchy, but the primary will be boosted and
+ // assigned again to above of the secondary.
+ verify(primary).assignLayer(t, 0);
+ verify(secondary).assignLayer(t, 1);
+ verify(primary).assignLayer(t, 2);
+ }
+
+ @Test
public void testMoveOrCreateDecorSurface() {
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 52e2d8a..7551b165 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -330,6 +330,10 @@
}
@Override
+ public void showDismissibleKeyguard() {
+ }
+
+ @Override
public void setPipVisibilityLw(boolean visible) {
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 8bf4833..a1cc8d5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -37,6 +37,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
@@ -79,12 +80,14 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
+import android.util.ArraySet;
import android.util.MergedConfiguration;
import android.view.ContentRecordingSession;
import android.view.IWindow;
import android.view.InputChannel;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
+import android.view.Surface;
import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowInsets;
@@ -100,16 +103,19 @@
import com.android.compatibility.common.util.AdoptShellPermissionsRule;
import com.android.internal.os.IResultReceiver;
import com.android.server.LocalServices;
+import com.android.server.wm.SensitiveContentPackages.PackageInfo;
import com.android.server.wm.WindowManagerService.WindowContainerInfo;
import com.google.common.truth.Expect;
+import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
+import java.util.Collections;
/**
* Build/Install/Run:
@@ -131,6 +137,11 @@
@Rule
public Expect mExpect = Expect.create();
+ @After
+ public void tearDown() {
+ mWm.mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ }
+
@Test
public void testIsRequestedOrientationMapped() {
mWm.setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled*/ true,
@@ -813,6 +824,42 @@
}
@Test
+ public void setShouldBlockScreenCaptureForApp() {
+ String testPackage = "test";
+ int ownerId1 = 20;
+ int ownerId2 = 21;
+ PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId1);
+ ArraySet<PackageInfo> blockedPackages = new ArraySet();
+ blockedPackages.add(blockedPackage);
+
+ WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+ wmInternal.setShouldBlockScreenCaptureForApp(blockedPackages);
+
+ assertTrue(mWm.mSensitiveContentPackages
+ .shouldBlockScreenCaptureForApp(testPackage, ownerId1));
+ assertFalse(mWm.mSensitiveContentPackages
+ .shouldBlockScreenCaptureForApp(testPackage, ownerId2));
+ verify(mWm).refreshScreenCaptureDisabled();
+ }
+
+ @Test
+ public void setShouldBlockScreenCaptureForApp_emptySet_clearsCache() {
+ String testPackage = "test";
+ int ownerId1 = 20;
+ PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId1);
+ ArraySet<PackageInfo> blockedPackages = new ArraySet();
+ blockedPackages.add(blockedPackage);
+
+ WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+ wmInternal.setShouldBlockScreenCaptureForApp(blockedPackages);
+ wmInternal.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+
+ assertFalse(mWm.mSensitiveContentPackages
+ .shouldBlockScreenCaptureForApp(testPackage, ownerId1));
+ verify(mWm, times(2)).refreshScreenCaptureDisabled();
+ }
+
+ @Test
public void testisLetterboxBackgroundMultiColored() {
assertThat(setupLetterboxConfigurationWithBackgroundType(
LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING)).isTrue();
@@ -952,6 +999,57 @@
}
@Test
+ public void testDrawMagnifiedViewport() {
+ final int displayId = mDisplayContent.mDisplayId;
+ // Use real surface, so ViewportWindow's BlastBufferQueue can be created.
+ final ArrayList<SurfaceControl> surfaceControls = new ArrayList<>();
+ mWm.mSurfaceControlFactory = s -> new SurfaceControl.Builder() {
+ @Override
+ public SurfaceControl build() {
+ final SurfaceControl sc = super.build();
+ surfaceControls.add(sc);
+ return sc;
+ }
+ };
+ mWm.mAccessibilityController.setMagnificationCallbacks(displayId,
+ mock(WindowManagerInternal.MagnificationCallbacks.class));
+ final boolean[] lockCanvasInWmLock = { false };
+ final Surface surface = mWm.mAccessibilityController.forceShowMagnifierSurface(displayId);
+ spyOn(surface);
+ doAnswer(invocationOnMock -> {
+ lockCanvasInWmLock[0] |= Thread.holdsLock(mWm.mGlobalLock);
+ invocationOnMock.callRealMethod();
+ return null;
+ }).when(surface).lockCanvas(any());
+ mWm.mAccessibilityController.drawMagnifiedRegionBorderIfNeeded(displayId, mTransaction);
+ waitUntilHandlersIdle();
+ try {
+ verify(surface).lockCanvas(any());
+
+ clearInvocations(surface);
+ // Invalidate and redraw.
+ mWm.mAccessibilityController.onDisplaySizeChanged(mDisplayContent);
+ mWm.mAccessibilityController.drawMagnifiedRegionBorderIfNeeded(displayId, mTransaction);
+ // Turn off magnification to release surface.
+ mWm.mAccessibilityController.setMagnificationCallbacks(displayId, null);
+ if (!com.android.window.flags.Flags.drawMagnifierBorderOutsideWmlock()) {
+ verify(surface).release();
+ assertTrue(lockCanvasInWmLock[0]);
+ return;
+ }
+ waitUntilHandlersIdle();
+ // lockCanvas must not be called after releasing.
+ verify(surface, never()).lockCanvas(any());
+ verify(surface).release();
+ assertFalse(lockCanvasInWmLock[0]);
+ } finally {
+ for (int i = surfaceControls.size() - 1; i >= 0; --i) {
+ surfaceControls.get(i).release();
+ }
+ }
+ }
+
+ @Test
public void testRequestKeyboardShortcuts_noWindow() {
doNothing().when(mWm.mContext).enforceCallingOrSelfPermission(anyString(), anyString());
doReturn(null).when(mWm).getFocusedWindowLocked();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index f24baba..fb4edfa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -55,6 +55,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.notification.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION;
import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.WindowContainer.SYNC_STATE_WAITING_FOR_DRAW;
@@ -90,6 +91,7 @@
import android.os.InputConfig;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
import android.util.ArraySet;
import android.util.MergedConfiguration;
import android.view.Gravity;
@@ -109,7 +111,9 @@
import androidx.test.filters.SmallTest;
import com.android.server.testutils.StubTransaction;
+import com.android.server.wm.SensitiveContentPackages.PackageInfo;
+import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -130,6 +134,11 @@
@RunWith(WindowTestRunner.class)
public class WindowStateTests extends WindowTestsBase {
+ @After
+ public void tearDown() {
+ mWm.mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ }
+
@Test
public void testIsParentWindowHidden() {
final WindowState parentWindow = createWindow(null, TYPE_APPLICATION, "parentWindow");
@@ -1373,6 +1382,28 @@
assertThat(listener.mIsVisibleForImeTargetOverlay).isFalse();
}
+ @Test
+ @RequiresFlagsEnabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION)
+ public void testIsSecureLocked_sensitiveContentProtectionManagerEnabled() {
+ String testPackage = "test";
+ int ownerId1 = 20;
+ int ownerId2 = 21;
+ final WindowState window1 = createWindow(null, TYPE_APPLICATION, "window1", ownerId1);
+ final WindowState window2 = createWindow(null, TYPE_APPLICATION, "window2", ownerId2);
+
+ // Setting packagename for targeted feature
+ window1.mAttrs.packageName = testPackage;
+ window2.mAttrs.packageName = testPackage;
+
+ PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId1);
+ ArraySet<PackageInfo> blockedPackages = new ArraySet();
+ blockedPackages.add(blockedPackage);
+ mWm.mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(blockedPackages);
+
+ assertTrue(window1.isSecureLocked());
+ assertFalse(window2.isSecureLocked());
+ }
+
private static class TestImeTargetChangeListener implements ImeTargetChangeListener {
private IBinder mImeTargetToken;
private boolean mIsRemoved;
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index d05eb5c..57b13e9 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -422,8 +422,8 @@
"android.telecom.extra.CALL_CREATED_TIME_MILLIS";
/**
- * The extra for call log uri that was used to mark missed calls as read when dialer gets the
- * notification on reboot.
+ * Extra URI that is used by a dialer to query the {@link android.provider.CallLog} content
+ * provider and associate a missed call notification with a call log entry.
*/
@FlaggedApi(Flags.FLAG_ADD_CALL_URI_FOR_MISSED_CALLS)
public static final String EXTRA_CALL_LOG_URI =
diff --git a/telephony/java/android/telephony/SecurityAlgorithmUpdate.java b/telephony/java/android/telephony/SecurityAlgorithmUpdate.java
index 61d7ead..c902016 100644
--- a/telephony/java/android/telephony/SecurityAlgorithmUpdate.java
+++ b/telephony/java/android/telephony/SecurityAlgorithmUpdate.java
@@ -169,6 +169,7 @@
public static final int SECURITY_ALGORITHM_NEA1 = 56;
public static final int SECURITY_ALGORITHM_NEA2 = 57;
public static final int SECURITY_ALGORITHM_NEA3 = 58;
+ public static final int SECURITY_ALGORITHM_IMS_NULL = 67;
public static final int SECURITY_ALGORITHM_SIP_NULL = 68;
public static final int SECURITY_ALGORITHM_AES_GCM = 69;
public static final int SECURITY_ALGORITHM_AES_GMAC = 70;
@@ -176,9 +177,8 @@
public static final int SECURITY_ALGORITHM_DES_EDE3_CBC = 72;
public static final int SECURITY_ALGORITHM_AES_EDE3_CBC = 73;
public static final int SECURITY_ALGORITHM_HMAC_SHA1_96 = 74;
- public static final int SECURITY_ALGORITHM_HMAC_SHA1_96_NULL = 75;
- public static final int SECURITY_ALGORITHM_HMAC_MD5_96 = 76;
- public static final int SECURITY_ALGORITHM_HMAC_MD5_96_NULL = 77;
+ public static final int SECURITY_ALGORITHM_HMAC_MD5_96 = 75;
+ public static final int SECURITY_ALGORITHM_SRTP_NULL = 86;
public static final int SECURITY_ALGORITHM_SRTP_AES_COUNTER = 87;
public static final int SECURITY_ALGORITHM_SRTP_AES_F8 = 88;
public static final int SECURITY_ALGORITHM_SRTP_HMAC_SHA1 = 89;
@@ -199,15 +199,15 @@
SECURITY_ALGORITHM_UEA2, SECURITY_ALGORITHM_EEA0, SECURITY_ALGORITHM_EEA1,
SECURITY_ALGORITHM_EEA2, SECURITY_ALGORITHM_EEA3, SECURITY_ALGORITHM_NEA0,
SECURITY_ALGORITHM_NEA1, SECURITY_ALGORITHM_NEA2, SECURITY_ALGORITHM_NEA3,
- SECURITY_ALGORITHM_SIP_NULL, SECURITY_ALGORITHM_AES_GCM,
+ SECURITY_ALGORITHM_IMS_NULL, SECURITY_ALGORITHM_SIP_NULL, SECURITY_ALGORITHM_AES_GCM,
SECURITY_ALGORITHM_AES_GMAC, SECURITY_ALGORITHM_AES_CBC,
SECURITY_ALGORITHM_DES_EDE3_CBC, SECURITY_ALGORITHM_AES_EDE3_CBC,
- SECURITY_ALGORITHM_HMAC_SHA1_96, SECURITY_ALGORITHM_HMAC_SHA1_96_NULL,
- SECURITY_ALGORITHM_HMAC_MD5_96, SECURITY_ALGORITHM_HMAC_MD5_96_NULL,
- SECURITY_ALGORITHM_SRTP_AES_COUNTER, SECURITY_ALGORITHM_SRTP_AES_F8,
- SECURITY_ALGORITHM_SRTP_HMAC_SHA1, SECURITY_ALGORITHM_ENCR_AES_GCM_16,
- SECURITY_ALGORITHM_ENCR_AES_CBC, SECURITY_ALGORITHM_AUTH_HMAC_SHA2_256_128,
- SECURITY_ALGORITHM_UNKNOWN, SECURITY_ALGORITHM_OTHER, SECURITY_ALGORITHM_ORYX})
+ SECURITY_ALGORITHM_HMAC_SHA1_96, SECURITY_ALGORITHM_HMAC_MD5_96,
+ SECURITY_ALGORITHM_SRTP_NULL, SECURITY_ALGORITHM_SRTP_AES_COUNTER,
+ SECURITY_ALGORITHM_SRTP_AES_F8, SECURITY_ALGORITHM_SRTP_HMAC_SHA1,
+ SECURITY_ALGORITHM_ENCR_AES_GCM_16, SECURITY_ALGORITHM_ENCR_AES_CBC,
+ SECURITY_ALGORITHM_AUTH_HMAC_SHA2_256_128, SECURITY_ALGORITHM_UNKNOWN,
+ SECURITY_ALGORITHM_OTHER, SECURITY_ALGORITHM_ORYX})
public @interface SecurityAlgorithm {
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 9e292be..1b47dfe 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -510,7 +510,7 @@
/** @hide */
@UnsupportedAppUsage
public TelephonyManager(Context context) {
- this(context, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
+ this(context, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
}
/** @hide */
@@ -2140,10 +2140,14 @@
* the IMEI/SV for GSM phones. Return null if the software version is
* not available.
* <p>
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY}.
*/
@RequiresPermission(anyOf = {
android.Manifest.permission.READ_PHONE_STATE,
android.Manifest.permission.READ_BASIC_PHONE_STATE})
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY)
@Nullable
public String getDeviceSoftwareVersion() {
return getDeviceSoftwareVersion(getSlotIndex());
@@ -2158,10 +2162,13 @@
*
* @param slotIndex of which deviceID is returned
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY}.
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY)
@Nullable
public String getDeviceSoftwareVersion(int slotIndex) {
ITelephony telephony = getITelephony();
@@ -2288,6 +2295,9 @@
*
* See {@link #getImei(int)} for details on the required permissions and behavior
* when the caller does not hold sufficient permissions.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_GSM}.
*/
@SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@@ -2330,6 +2340,9 @@
* </ul>
*
* @param slotIndex of which IMEI is returned
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_GSM}.
*/
@SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@@ -2350,6 +2363,9 @@
/**
* Returns the Type Allocation Code from the IMEI. Return null if Type Allocation Code is not
* available.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_GSM}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_GSM)
@Nullable
@@ -2362,6 +2378,9 @@
* available.
*
* @param slotIndex of which Type Allocation Code is returned
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_GSM}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_GSM)
@Nullable
@@ -2407,6 +2426,9 @@
* the READ_PHONE_STATE permission, or if the calling app is targeting API level 29 or
* higher, then a SecurityException is thrown.</li>
* </ul>
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CDMA}.
*/
@SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@@ -2446,6 +2468,9 @@
* </ul>
*
* @param slotIndex of which MEID is returned
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CDMA}.
*/
@SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@@ -2472,6 +2497,9 @@
/**
* Returns the Manufacturer Code from the MEID. Return null if Manufacturer Code is not
* available.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CDMA}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_CDMA)
@Nullable
@@ -2484,6 +2512,9 @@
* available.
*
* @param slotIndex of which Type Allocation Code is returned
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CDMA}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_CDMA)
@Nullable
@@ -2528,6 +2559,9 @@
* the READ_PHONE_STATE permission, or if the calling app is targeting API level 29 or
* higher, then a SecurityException is thrown.</li>
* </ul>
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@@ -2563,10 +2597,14 @@
*<p>
* @return Current location of the device or null if not available.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
+ *
* @deprecated use {@link #getAllCellInfo} instead, which returns a superset of this API.
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
public CellLocation getCellLocation() {
try {
ITelephony telephony = getITelephony();
@@ -2596,12 +2634,15 @@
*
* @return List of NeighboringCellInfo or null if info unavailable.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
* @removed
* @deprecated Use {@link #getAllCellInfo} which returns a superset of the information
* from NeighboringCellInfo, including LTE cell information.
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
public List<NeighboringCellInfo> getNeighboringCellInfo() {
try {
ITelephony telephony = getITelephony();
@@ -2648,9 +2689,12 @@
* @see #PHONE_TYPE_CDMA
* @see #PHONE_TYPE_SIP
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY}.
* {@hide}
*/
@SystemApi
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY)
public int getCurrentPhoneType() {
return getCurrentPhoneType(getSubId());
}
@@ -2663,9 +2707,13 @@
* @see #PHONE_TYPE_CDMA
*
* @param subId for which phone type is returned
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY}.
* @hide
*/
@SystemApi
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY)
public int getCurrentPhoneType(int subId) {
int phoneId;
if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
@@ -2712,7 +2760,11 @@
* @see #PHONE_TYPE_GSM
* @see #PHONE_TYPE_CDMA
* @see #PHONE_TYPE_SIP
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY}.
*/
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY)
public int getPhoneType() {
if (!isVoiceCapable()) {
return PHONE_TYPE_NONE;
@@ -2912,6 +2964,9 @@
* @see CarrierConfigManager#getConfigForSubId(int)
* @see #createForSubscriptionId(int)
* @see #createForPhoneAccountHandle(PhoneAccountHandle)
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@WorkerThread
@@ -2958,6 +3013,9 @@
* <p>
* @return the lowercase 2 character ISO-3166-1 alpha-2 country code, or empty string if not
* available.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
public String getNetworkCountryIso() {
@@ -2980,6 +3038,8 @@
* available.
*
* @throws IllegalArgumentException when the slotIndex is invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
*
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
@@ -3108,9 +3168,13 @@
*
* @deprecated use {@link #getDataNetworkType()}
* @return the NETWORK_TYPE_xxxx for current data connection.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
public @NetworkType int getNetworkType() {
return getNetworkType(getSubId(SubscriptionManager.getActiveDataSubscriptionId()));
}
@@ -3199,12 +3263,15 @@
* @see #NETWORK_TYPE_EHRPD
* @see #NETWORK_TYPE_HSPAP
* @see #NETWORK_TYPE_NR
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(anyOf = {
android.Manifest.permission.READ_PHONE_STATE,
android.Manifest.permission.READ_BASIC_PHONE_STATE})
- @RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
public @NetworkType int getDataNetworkType() {
return getDataNetworkType(getSubId(SubscriptionManager.getActiveDataSubscriptionId()));
}
@@ -3245,6 +3312,9 @@
* or {@link android.Manifest.permission#READ_BASIC_PHONE_STATE
* READ_BASIC_PHONE_STATE} or that the calling app has carrier privileges
* (see {@link #hasCarrierPrivileges}).
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(anyOf = {
@@ -3597,6 +3667,9 @@
* of whether an active SIM profile is present or not so this API would always return true.
*
* @return true if a ICC card is present.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
public boolean hasIccCard() {
@@ -3640,6 +3713,9 @@
* @see #SIM_STATE_PERM_DISABLED
* @see #SIM_STATE_CARD_IO_ERROR
* @see #SIM_STATE_CARD_RESTRICTED
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
public @SimState int getSimState() {
@@ -3681,6 +3757,8 @@
* @see #SIM_STATE_CARD_RESTRICTED
* @see #SIM_STATE_PRESENT
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@SystemApi
@@ -3701,11 +3779,14 @@
* @see #SIM_STATE_CARD_RESTRICTED
* @see #SIM_STATE_PRESENT
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
* @deprecated instead use {@link #getSimCardState(int, int)}
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@Deprecated
public @SimState int getSimCardState(int physicalSlotIndex) {
int activePort = getFirstActivePortIndex(physicalSlotIndex);
@@ -3727,6 +3808,8 @@
* @see #SIM_STATE_CARD_RESTRICTED
* @see #SIM_STATE_PRESENT
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@SystemApi
@@ -3785,6 +3868,8 @@
* @see #SIM_STATE_PERM_DISABLED
* @see #SIM_STATE_LOADED
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@SystemApi
@@ -3808,11 +3893,14 @@
* @see #SIM_STATE_PERM_DISABLED
* @see #SIM_STATE_LOADED
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
* @deprecated instead use {@link #getSimApplicationState(int, int)}
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@Deprecated
public @SimState int getSimApplicationState(int physicalSlotIndex) {
int activePort = getFirstActivePortIndex(physicalSlotIndex);
@@ -3836,6 +3924,8 @@
* @see #SIM_STATE_PERM_DISABLED
* @see #SIM_STATE_LOADED
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@SystemApi
@@ -3876,6 +3966,9 @@
*
* @param appType the uicc app type like {@link APPTYPE_CSIM}
* @return true if the specified type of application in UICC CARD or false if no uicc or error.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@SystemApi
@@ -3908,6 +4001,9 @@
* @see #SIM_STATE_PERM_DISABLED
* @see #SIM_STATE_CARD_IO_ERROR
* @see #SIM_STATE_CARD_RESTRICTED
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
public @SimState int getSimState(int slotIndex) {
@@ -4105,6 +4201,9 @@
* the READ_PHONE_STATE permission, or if the calling app is targeting API level 29 or
* higher, then a SecurityException is thrown.</li>
* </ul>
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@@ -4172,6 +4271,9 @@
*
* @return {@code true} if 3GPP and 3GPP2 radio technologies can be supported at the same time
* {@code false} if not supported or unknown
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
* @hide
*/
@SystemApi
@@ -4219,6 +4321,9 @@
* through a factory reset.
*
* @return card ID of the default eUICC card, if loaded.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_EUICC)
public int getCardIdForDefaultEuicc() {
@@ -4252,6 +4357,9 @@
* @return a list of UiccCardInfo objects, representing information on the currently inserted
* UICCs and eUICCs. Each UiccCardInfo in the list will have private information filtered out if
* the caller does not have adequate permissions for that card.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@@ -4276,6 +4384,8 @@
*
* @return UiccSlotInfo array.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@SystemApi
@@ -4319,6 +4429,9 @@
* @param physicalSlots The content of the array represents the physical slot index. The array
* size should be same as {@link #getUiccSlotsInfo()}.
* @return boolean Return true if the switch succeeds, false if the switch fails.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
* @deprecated {@link #setSimSlotMapping(Collection, Executor, Consumer)}
*/
@@ -4328,6 +4441,7 @@
@SystemApi
@Deprecated
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
public boolean switchSlots(int[] physicalSlots) {
try {
ITelephony telephony = getITelephony();
@@ -4420,6 +4534,8 @@
* @throws IllegalArgumentException if the caller passes in an invalid collection of
* UiccSlotMapping like duplicate data, etc
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@SystemApi
@@ -4452,11 +4568,14 @@
* @return a map indicates the mapping from logical slots to physical slots. The size of the map
* should be {@link #getPhoneCount()} if success, otherwise return an empty map.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
* @deprecated use {@link #getSimSlotMapping()} instead.
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@NonNull
@Deprecated
public Map<Integer, Integer> getLogicalToPhysicalSlotMapping() {
@@ -4484,6 +4603,9 @@
*
* @return a collection of {@link UiccSlotMapping} which indicates the mapping from logical
* slots to ports and physical slots.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@SystemApi
@@ -4541,6 +4663,9 @@
* the READ_PHONE_STATE permission, or if the calling app is targeting API level 29 or
* higher, then a SecurityException is thrown.</li>
* </ul>
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@@ -4593,6 +4718,8 @@
* found, and the carrier does not require a key.
* @throws IllegalArgumentException when an invalid key is found or when key is required but
* not found.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@@ -4638,6 +4765,9 @@
* Requires Permission: MODIFY_PHONE_STATE.
*
* @see #getCarrierInfoForImsiEncryption
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -4840,6 +4970,9 @@
* from disk, as well as on which {@code callback} will be called.
* @param callback A callback called when the upload operation terminates, either in success
* or in error.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
public void uploadCallComposerPicture(@NonNull Path pictureToUpload,
@@ -4947,6 +5080,9 @@
* read, as well as on which the callback will be called.
* @param callback A callback called when the upload operation terminates, either in success
* or in error.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
public void uploadCallComposerPicture(@NonNull InputStream pictureToUpload,
@@ -5081,6 +5217,9 @@
*
* <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
* or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
@@ -5139,6 +5278,8 @@
* {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
* for apps targeting SDK API level 29 and below.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @deprecated use {@link SubscriptionManager#getPhoneNumber(int)} instead.
*/
@Deprecated
@@ -5148,6 +5289,7 @@
android.Manifest.permission.READ_SMS,
android.Manifest.permission.READ_PHONE_NUMBERS
})
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
public String getLine1Number() {
return getLine1Number(getSubId());
}
@@ -5214,9 +5356,13 @@
* @param alphaTag alpha-tagging of the dailing nubmer
* @param number The dialing number
* @return true if the operation was executed correctly.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @deprecated use {@link SubscriptionManager#setCarrierPhoneNumber(int, String)} instead.
*/
@Deprecated
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
public boolean setLine1NumberForDisplay(String alphaTag, String number) {
return setLine1NumberForDisplay(getSubId(), alphaTag, number);
}
@@ -5336,6 +5482,8 @@
* {@link SubscriptionManager#createSubscriptionGroup(List)} for the definition of a group,
* otherwise return an empty array if there is a failure.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@SystemApi
@@ -5421,6 +5569,9 @@
*
* <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
* or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
@@ -5459,6 +5610,9 @@
*
* @param alphaTag The alpha tag to display.
* @param number The voicemail number.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
public boolean setVoiceMailNumber(String alphaTag, String number) {
@@ -5533,6 +5687,8 @@
* @see #KEY_VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL
* @see #KEY_VOICEMAIL_SCRAMBLED_PIN_STRING
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
* @hide
*/
@SystemApi
@@ -5563,6 +5719,9 @@
* @see #createForSubscriptionId(int)
* @see #createForPhoneAccountHandle(PhoneAccountHandle)
* @see VisualVoicemailService
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
*/
@Nullable
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@@ -5593,6 +5752,9 @@
*
* @see TelecomManager#getDefaultDialerPackage()
* @see CarrierConfigManager#KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
public void setVisualVoicemailSmsFilterSettings(VisualVoicemailSmsFilterSettings settings) {
@@ -5623,6 +5785,9 @@
*
* @see SmsManager#sendDataMessage(String, String, short, byte[], PendingIntent, PendingIntent)
* @see SmsManager#sendTextMessage(String, String, String, PendingIntent, PendingIntent)
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
public void sendVisualVoicemailSms(String number, int port, String text,
@@ -5808,6 +5973,9 @@
* @see #SIM_ACTIVATION_STATE_ACTIVATING
* @see #SIM_ACTIVATION_STATE_ACTIVATED
* @see #SIM_ACTIVATION_STATE_DEACTIVATED
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}
* @hide
*/
@SystemApi
@@ -5856,6 +6024,9 @@
* @see #SIM_ACTIVATION_STATE_ACTIVATED
* @see #SIM_ACTIVATION_STATE_DEACTIVATED
* @see #SIM_ACTIVATION_STATE_RESTRICTED
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_DATA}.
* @hide
*/
@SystemApi
@@ -5904,6 +6075,9 @@
* @see #SIM_ACTIVATION_STATE_ACTIVATING
* @see #SIM_ACTIVATION_STATE_ACTIVATED
* @see #SIM_ACTIVATION_STATE_DEACTIVATED
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
* @hide
*/
@SystemApi
@@ -5954,6 +6128,9 @@
* @see #SIM_ACTIVATION_STATE_ACTIVATED
* @see #SIM_ACTIVATION_STATE_DEACTIVATED
* @see #SIM_ACTIVATION_STATE_RESTRICTED
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_DATA}.
* @hide
*/
@SystemApi
@@ -6032,6 +6209,9 @@
*
* <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
* or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
@@ -6072,7 +6252,10 @@
*
* @throws SecurityException if the caller does not have carrier privileges or is not the
* current default dialer
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
*/
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
public void sendDialerSpecialCode(String inputCode) {
try {
final ITelephony telephony = getITelephony();
@@ -6143,6 +6326,9 @@
*
* <p>Requires Permission:
* {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@Nullable
@@ -6168,6 +6354,9 @@
* Returns the IMS public user identities (IMPU) that were loaded from the ISIM.
* @return an array of IMPU strings, with one IMPU per string, or null if
* not present or not loaded
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
* @deprecated use {@link #getImsPublicUserIdentities()}
*/
@@ -6199,6 +6388,8 @@
* EF_IMPU is not available.
* @throws IllegalStateException in case the ISIM hasn’t been loaded
* @throws SecurityException if the caller does not have the required permission/privilege
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@NonNull
@@ -6254,6 +6445,10 @@
* targeting API level 31+.
*
* @return the current call state.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELECOM}.
+ *
* @deprecated Use {@link #getCallStateForSubscription} to retrieve the call state for a
* specific telephony subscription (which allows carrier privileged apps),
* {@link TelephonyCallback.CallStateListener} for real-time call state updates, or
@@ -6261,6 +6456,7 @@
* device.
*/
@RequiresPermission(value = android.Manifest.permission.READ_PHONE_STATE, conditional = true)
+ @RequiresFeature(PackageManager.FEATURE_TELECOM)
@Deprecated
public @CallState int getCallState() {
if (mContext != null) {
@@ -6281,6 +6477,9 @@
* @see TelephonyManager#createForSubscriptionId(int)
* @see TelephonyManager#createForPhoneAccountHandle(PhoneAccountHandle)
* @return The call state of the subscription associated with this TelephonyManager instance.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
*/
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
@@ -6341,6 +6540,9 @@
* @see #DATA_ACTIVITY_OUT
* @see #DATA_ACTIVITY_INOUT
* @see #DATA_ACTIVITY_DORMANT
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_DATA}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
public int getDataActivity() {
@@ -6414,6 +6616,9 @@
* @see #DATA_SUSPENDED
* @see #DATA_DISCONNECTING
* @see #DATA_HANDOVER_IN_PROGRESS
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_DATA}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
public int getDataState() {
@@ -6599,10 +6804,14 @@
* Returns the CDMA ERI icon display number. The number is assigned by
* 3GPP2 C.R1001-H v1.0 Table 8.1-1. Additionally carriers define their own ERI display numbers.
* Defined values are {@link #ERI_ON}, {@link #ERI_OFF}, and {@link #ERI_FLASH}.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CDMA}.
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CDMA)
public @EriIconIndex int getCdmaEnhancedRoamingIndicatorDisplayNumber() {
return getCdmaEriIconIndex(getSubId());
}
@@ -6810,6 +7019,9 @@
*
* @return List of {@link android.telephony.CellInfo}; null if cell
* information is unavailable.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
@@ -6905,6 +7117,9 @@
*
* @param executor the executor on which callback will be invoked.
* @param callback a callback to receive CellInfo.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
@@ -6966,6 +7181,9 @@
* @param workSource the requestor to whom the power consumption for this should be attributed.
* @param executor the executor on which callback will be invoked.
* @param callback a callback to receive CellInfo.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
* @hide
*/
@SystemApi
@@ -7052,6 +7270,9 @@
/**
* Returns the MMS user agent.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
public String getMmsUserAgent() {
@@ -7068,6 +7289,9 @@
/**
* Returns the MMS user agent profile URL.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
public String getMmsUAProfUrl() {
@@ -7111,8 +7335,12 @@
*
* @param AID Application id. See ETSI 102.221 and 101.220.
* @return an IccOpenLogicalChannelResponse object.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @deprecated Replaced by {@link #iccOpenLogicalChannel(String, int)}
*/
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@Deprecated
public IccOpenLogicalChannelResponse iccOpenLogicalChannel(String AID) {
return iccOpenLogicalChannel(getSubId(), AID, -1);
@@ -7145,6 +7373,8 @@
* @param aid Application id. See ETSI 102.221 and 101.220.
* @param p2 P2 parameter (described in ISO 7816-4).
* @return an IccOpenLogicalChannelResponse object.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
* @deprecated This API is not compatible on eUICC supporting Multiple Enabled Profile(MEP),
* instead use {@link #iccOpenLogicalChannelByPort(int, int, String, int)}
@@ -7200,9 +7430,13 @@
* @param aid Application id. See ETSI 102.221 and 101.220.
* @param p2 P2 parameter (described in ISO 7816-4).
* @return an IccOpenLogicalChannelResponse object.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@SystemApi
@NonNull
public IccOpenLogicalChannelResponse iccOpenLogicalChannelByPort(int slotIndex,
@@ -7255,6 +7489,9 @@
* @param AID Application id. See ETSI 102.221 and 101.220.
* @param p2 P2 parameter (described in ISO 7816-4).
* @return an IccOpenLogicalChannelResponse object.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
public IccOpenLogicalChannelResponse iccOpenLogicalChannel(String AID, int p2) {
@@ -7321,6 +7558,9 @@
* @param channel is the channel id to be closed as returned by a successful
* iccOpenLogicalChannel.
* @return true if the channel was closed successfully.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
* @deprecated This API is not compatible on eUICC supporting Multiple Enabled Profile(MEP),
* instead use {@link #iccCloseLogicalChannelByPort(int, int, int)}
@@ -7365,9 +7605,12 @@
* @throws IllegalStateException if the Telephony process is not currently available or modem
* currently can't process this command.
* @throws IllegalArgumentException if invalid arguments are passed.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@SystemApi
public void iccCloseLogicalChannelByPort(int slotIndex, int portIndex, int channel) {
try {
@@ -7403,6 +7646,8 @@
* iccOpenLogicalChannel.
* @return true if the channel was closed successfully.
* @throws IllegalArgumentException if input parameters are wrong. e.g., invalid channel
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
public boolean iccCloseLogicalChannel(int channel) {
@@ -7469,6 +7714,8 @@
* @param data Data to be sent with the APDU.
* @return The APDU response from the ICC card with the status appended at the end, or null if
* there is an issue connecting to the Telephony service.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
* @deprecated This API is not compatible on eUICC supporting Multiple Enabled Profile(MEP),
* instead use
@@ -7516,9 +7763,13 @@
* @param data Data to be sent with the APDU.
* @return The APDU response from the ICC card with the status appended at the end, or null if
* there is an issue connecting to the Telephony service.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@SystemApi
@NonNull
public String iccTransmitApduLogicalChannelByPort(int slotIndex, int portIndex, int channel,
@@ -7563,6 +7814,9 @@
* @param data Data to be sent with the APDU.
* @return The APDU response from the ICC card with the status appended at
* the end.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
public String iccTransmitApduLogicalChannel(int channel, int cla,
@@ -7628,6 +7882,9 @@
* @param data Data to be sent with the APDU.
* @return The APDU response from the ICC card with the status appended at
* the end.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
* @deprecated This API is not compatible on eUICC supporting Multiple Enabled Profile(MEP),
* instead use
@@ -7673,9 +7930,13 @@
* @param data Data to be sent with the APDU.
* @return The APDU response from the ICC card with the status appended at
* the end.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@SystemApi
@NonNull
public String iccTransmitApduBasicChannelByPort(int slotIndex, int portIndex, int cla,
@@ -7712,6 +7973,9 @@
* @param data Data to be sent with the APDU.
* @return The APDU response from the ICC card with the status appended at
* the end.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
public String iccTransmitApduBasicChannel(int cla,
@@ -7768,6 +8032,9 @@
* @param p3 P3 value of the APDU command.
* @param filePath
* @return The APDU response.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
public byte[] iccExchangeSimIO(int fileID, int command, int p1, int p2, int p3,
@@ -7817,6 +8084,9 @@
* @return The APDU response from the ICC card in hexadecimal format
* with the last 4 bytes being the status word. If the command fails,
* returns an empty string.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
public String sendEnvelopeWithStatus(String content) {
@@ -7978,6 +8248,8 @@
*
* @return {@code true} on success; {@code false} on any failure.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
* @hide
*/
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@@ -8008,6 +8280,8 @@
*
* @deprecated Using {@link #rebootModem()} instead.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
* @hide
*/
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@@ -8035,6 +8309,8 @@
* app has carrier privileges (see {@link #hasCarrierPrivileges}).
* @throws IllegalStateException if the Telephony process is not currently available.
* @throws RuntimeException
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
*/
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
@@ -8166,6 +8442,9 @@
* {@link #getMaxNumberVerificationTimeoutMillis()}, whichever is lesser.
* @param executor The {@link Executor} that callbacks should be executed on.
* @param callback The callback to use for delivering results.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
*/
@SystemApi
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@@ -8378,6 +8657,9 @@
* See 3GPP TS 31.103 (Section 4.2.7) for the definition and more information on this table.
*
* @return IMS Service Table or null if not present or not loaded
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@Nullable
@@ -8496,6 +8778,9 @@
* Key freshness failure
* Authentication error, no memory space available
* Authentication error, no memory space available in EFMUK
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
// TODO(b/73660190): This should probably require MODIFY_PHONE_STATE, not
// READ_PRIVILEGED_PHONE_STATE. It certainly shouldn't reference the permission in Javadoc since
@@ -8552,6 +8837,9 @@
* or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
* @return an array of forbidden PLMNs or null if not available
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
@@ -8602,6 +8890,9 @@
* @return number of PLMNs that were successfully written to the SIM FPLMN list.
* This may be less than the number of PLMNs passed in where the SIM file does not have enough
* room for all of the values passed in. Return -1 in the event of an unexpected failure
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -8637,6 +8928,8 @@
* @param appType of type int of either {@link #APPTYPE_USIM} or {@link #APPTYPE_ISIM}.
* @return HexString represents sim service table else null.
* @throws SecurityException if the caller does not have the required permission/privileges
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@@ -8673,6 +8966,9 @@
* state.
*
* @param slotIndex the sim slot to reset the IMS stack on.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide */
@SystemApi
@WorkerThread
@@ -9084,12 +9380,15 @@
*
* @return The bitmask of preferred network types.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
* @hide
* @deprecated Use {@link #getAllowedNetworkTypesBitmask} instead.
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@SystemApi
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
public @NetworkTypeBitMask long getPreferredNetworkTypeBitmask() {
return getAllowedNetworkTypesBitmask();
}
@@ -9108,6 +9407,8 @@
*
* @return The bitmask of allowed network types.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
* @hide
*/
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@@ -9133,10 +9434,14 @@
* or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
* @return the allowed network type bitmask
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
* @hide
* @deprecated Use {@link #getAllowedNetworkTypesForReason} instead.
*/
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
@SystemApi
@Deprecated
public @NetworkTypeBitMask long getAllowedNetworkTypes() {
@@ -9161,6 +9466,9 @@
* <p>Requires Permission:
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
* app has carrier privileges (see {@link #hasCarrierPrivileges}).
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -9247,6 +9555,9 @@
* tasks one at a time in serial order.
* @param callback Returns network scan results or errors.
* @return A NetworkScan obj which contains a callback which can be used to stop the scan.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(allOf = {
@@ -9295,11 +9606,15 @@
* tasks one at a time in serial order.
* @param callback Returns network scan results or errors.
* @return A NetworkScan obj which contains a callback which can be used to stop the scan.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(allOf = {
android.Manifest.permission.MODIFY_PHONE_STATE
})
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
public @Nullable NetworkScan requestNetworkScan(
@IncludeLocationData int includeLocationData,
@NonNull NetworkScanRequest request,
@@ -9317,6 +9632,9 @@
}
/**
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
+ *
* @deprecated
* Use {@link
* #requestNetworkScan(NetworkScanRequest, Executor, TelephonyScanManager.NetworkScanCallback)}
@@ -9327,6 +9645,7 @@
android.Manifest.permission.MODIFY_PHONE_STATE,
Manifest.permission.ACCESS_FINE_LOCATION
})
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
public NetworkScan requestNetworkScan(
NetworkScanRequest request, TelephonyScanManager.NetworkScanCallback callback) {
return requestNetworkScan(request, AsyncTask.SERIAL_EXECUTOR, callback);
@@ -9347,6 +9666,9 @@
* attaching to the selected PLMN until reboot; otherwise, attach to the chosen PLMN and resume
* normal network selection next time.
* @return {@code true} on success; {@code false} on any failure.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -9378,6 +9700,9 @@
* {@link AccessNetworkConstants.AccessNetworkType#UNKNOWN}, modem will select
* the next best RAN for network registration.
* @return {@code true} on success; {@code false} on any failure.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
*/
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
@@ -9430,6 +9755,9 @@
* or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
* @return the network selection mode.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
*/
@SuppressAutoDoc // No support for carrier privileges (b/72967236).
@RequiresPermission(anyOf = {
@@ -9459,6 +9787,9 @@
* (see {@link #hasCarrierPrivileges})
*
* @return manually selected network info on success or empty string on failure
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
*/
@SuppressAutoDoc // No support carrier privileges (b/72967236).
@RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
@@ -9488,6 +9819,8 @@
*
* @return {@code true} if this device is in emergency SMS mode, {@code false} otherwise.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
* @hide
*/
@SystemApi
@@ -9557,11 +9890,15 @@
*
* @param networkTypeBitmask The bitmask of preferred network types.
* @return true on success; false on any failure.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
* @hide
* @deprecated Use {@link #setAllowedNetworkTypesForReason} instead.
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
@SystemApi
public boolean setPreferredNetworkTypeBitmask(@NetworkTypeBitMask long networkTypeBitmask) {
try {
@@ -9603,6 +9940,10 @@
*
* @param allowedNetworkTypes The bitmask of allowed network types.
* @return true on success; false on any failure.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
+ *
* @hide
* @deprecated Use {@link #setAllowedNetworkTypesForReason} instead with reason
* {@link #ALLOWED_NETWORK_TYPES_REASON_CARRIER}.
@@ -9690,11 +10031,16 @@
* @throws IllegalStateException if the Telephony process is not currently available.
* @throws IllegalArgumentException if invalid AllowedNetworkTypesReason is passed.
* @throws SecurityException if the caller does not have the required privileges or if the
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
* caller tries to use one of the following security-based reasons without
* {@link android.Manifest.permission#MODIFY_PHONE_STATE} permissions.
* <ol>
* <li>{@code TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G}</li>
* </ol>
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
*/
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@RequiresFeature(
@@ -9734,6 +10080,8 @@
* @throws IllegalStateException if the Telephony process is not currently available.
* @throws IllegalArgumentException if invalid AllowedNetworkTypesReason is passed.
* @throws SecurityException if the caller does not have the required permission/privileges
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
*/
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@RequiresFeature(
@@ -9808,6 +10156,9 @@
* <p>Requires that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
* @return true on success; false on any failure.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
public boolean setPreferredNetworkTypeToGlobal() {
@@ -9833,6 +10184,9 @@
* Requires Permission: MODIFY_PHONE_STATE.
*
* @return {@code true} if DUN APN is required for tethering.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_DATA}.
* @hide
*/
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -9886,6 +10240,9 @@
* is a superset of the checks done in SubscriptionManager#canManageSubscription.
*
* @return true if the app has carrier privileges.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
public boolean hasCarrierPrivileges() {
@@ -9933,6 +10290,9 @@
*
* @param brand The brand name to display/set.
* @return true if the operation was executed correctly.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
public boolean setOperatorBrandOverride(String brand) {
@@ -10034,7 +10394,11 @@
* Expose the rest of ITelephony to @SystemApi
*/
- /** @hide */
+ /**
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CDMA}.
+ * @hide
+ */
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_CDMA)
@@ -10042,7 +10406,11 @@
return getCdmaMdn(getSubId());
}
- /** @hide */
+ /**
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CDMA}.
+ * @hide
+ */
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_CDMA)
@@ -10059,7 +10427,11 @@
}
}
- /** @hide */
+ /**
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CDMA}.
+ * @hide
+ */
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_CDMA)
@@ -10067,7 +10439,11 @@
return getCdmaMin(getSubId());
}
- /** @hide */
+ /**
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CDMA}.
+ * @hide
+ */
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_CDMA)
@@ -10084,7 +10460,11 @@
}
}
- /** @hide */
+ /**
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
+ * @hide
+ */
@SystemApi
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@@ -10101,7 +10481,11 @@
return CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
}
- /** @hide */
+ /**
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
+ * @hide
+ */
@SystemApi
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@@ -10118,14 +10502,22 @@
return CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
}
- /** @hide */
+ /**
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
+ * @hide
+ */
@SystemApi
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
public List<String> getCarrierPackageNamesForIntent(Intent intent) {
return getCarrierPackageNamesForIntentAndPhone(intent, getPhoneId());
}
- /** @hide */
+ /**
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
+ * @hide
+ */
@SystemApi
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@@ -10152,10 +10544,13 @@
* @return The system-selected package that provides the {@link CarrierService} implementation
* for the current subscription, or {@code null} if none is resolved
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
public @Nullable String getCarrierServicePackageName() {
return getCarrierServicePackageNameForLogicalSlot(getPhoneId());
}
@@ -10169,10 +10564,13 @@
* @return The system-selected package that provides the {@link CarrierService} implementation
* for the slot, or {@code null} if none is resolved
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
public @Nullable String getCarrierServicePackageNameForLogicalSlot(int logicalSlotIndex) {
try {
ITelephony telephony = getITelephony();
@@ -10206,6 +10604,8 @@
/**
* Get the names of packages with carrier privileges for all the active subscriptions.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@SystemApi
@@ -10256,6 +10656,8 @@
*
* @throws IllegalArgumentException if requested state is invalid.
* @throws SecurityException if the caller does not have the permission.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
*/
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
@@ -10285,6 +10687,9 @@
*
* @return the user-set status for enriched calling with call composer, either of
* {@link #CALL_COMPOSER_STATUS_ON} or {@link #CALL_COMPOSER_STATUS_OFF}.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
*/
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
@@ -10301,7 +10706,11 @@
return CALL_COMPOSER_STATUS_OFF;
}
- /** @hide */
+ /**
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
+ * @hide
+ */
@SystemApi
@SuppressLint("RequiresPermission")
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
@@ -10316,6 +10725,9 @@
}
/**
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
+ *
* @deprecated Use {@link android.telecom.TelecomManager#placeCall(Uri address,
* Bundle extras)} instead.
* @hide
@@ -10323,6 +10735,7 @@
@Deprecated
@SystemApi
@RequiresPermission(android.Manifest.permission.CALL_PHONE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
public void call(String callingPackage, String number) {
try {
ITelephony telephony = getITelephony();
@@ -10369,6 +10782,8 @@
}
/**
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELECOM}.
* @deprecated Use {@link android.telecom.TelecomManager#isInCall} instead
* @hide
*/
@@ -10378,12 +10793,15 @@
android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
android.Manifest.permission.READ_PHONE_STATE
})
+ @RequiresFeature(PackageManager.FEATURE_TELECOM)
public boolean isOffhook() {
TelecomManager tm = (TelecomManager) mContext.getSystemService(TELECOM_SERVICE);
return tm.isInCall();
}
/**
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELECOM}.
* @deprecated Use {@link android.telecom.TelecomManager#isRinging} instead
* @hide
*/
@@ -10393,12 +10811,15 @@
android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
android.Manifest.permission.READ_PHONE_STATE
})
+ @RequiresFeature(PackageManager.FEATURE_TELECOM)
public boolean isRinging() {
TelecomManager tm = (TelecomManager) mContext.getSystemService(TELECOM_SERVICE);
return tm.isRinging();
}
/**
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELECOM}.
* @deprecated Use {@link android.telecom.TelecomManager#isInCall} instead
* @hide
*/
@@ -10408,12 +10829,15 @@
android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
android.Manifest.permission.READ_PHONE_STATE
})
+ @RequiresFeature(PackageManager.FEATURE_TELECOM)
public boolean isIdle() {
TelecomManager tm = (TelecomManager) mContext.getSystemService(TELECOM_SERVICE);
return !tm.isInCall();
}
/**
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
* @deprecated Use {@link android.telephony.TelephonyManager#getServiceState} instead
* @hide
*/
@@ -10423,6 +10847,7 @@
android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
android.Manifest.permission.READ_PHONE_STATE
})
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
public boolean isRadioOn() {
try {
ITelephony telephony = getITelephony();
@@ -10434,7 +10859,11 @@
return false;
}
- /** @hide */
+ /**
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
+ * @hide
+ */
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@@ -10449,7 +10878,11 @@
return false;
}
- /** @hide */
+ /**
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
+ * @hide
+ */
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@@ -10465,11 +10898,15 @@
}
/**
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
+ *
* @deprecated use {@link #supplyIccLockPin(String)} instead.
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@Deprecated
public int[] supplyPinReportResult(String pin) {
try {
@@ -10483,11 +10920,15 @@
}
/**
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
+ *
* @deprecated use {@link #supplyIccLockPuk(String, String)} instead.
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@Deprecated
public int[] supplyPukReportResult(String puk, String pin) {
try {
@@ -10513,6 +10954,8 @@
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
* app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@SystemApi
@@ -10549,6 +10992,8 @@
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
* app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@SystemApi
@@ -10622,6 +11067,9 @@
* @param callback called by the framework to inform the caller of the result of executing the
* USSD request (see {@link UssdResponseCallback}).
* @param handler the {@link Handler} to run the request on.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
*/
@RequiresPermission(android.Manifest.permission.CALL_PHONE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
@@ -10666,6 +11114,9 @@
* voice and data simultaneously. This can change based on location or network condition.
*
* @return {@code true} if simultaneous voice and data supported, and {@code false} otherwise.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_DATA}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
public boolean isConcurrentVoiceAndDataSupported() {
@@ -10679,9 +11130,13 @@
return false;
}
- /** @hide */
+ /**
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
+ * @hide */
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
public boolean handlePinMmi(String dialString) {
try {
ITelephony telephony = getITelephony();
@@ -10693,9 +11148,14 @@
return false;
}
- /** @hide */
+ /**
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
+ * @hide
+ */
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
public boolean handlePinMmiForSubscriber(int subId, String dialString) {
try {
ITelephony telephony = getITelephony();
@@ -10707,7 +11167,11 @@
return false;
}
- /** @hide */
+ /**
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
+ * @hide
+ */
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
@@ -10725,6 +11189,8 @@
* @deprecated - use the APIs {@link requestRadioPowerOffForReason} and
* {@link clearRadioPowerOffForReason}.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
* @hide
*/
@Deprecated
@@ -10752,6 +11218,8 @@
* @deprecated - use the APIs {@link requestRadioPowerOffForReason} and
* {@link clearRadioPowerOffForReason}.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
* @hide
*/
@Deprecated
@@ -10799,6 +11267,8 @@
* @throws SecurityException if the caller does not have MODIFY_PHONE_STATE permission.
* @throws IllegalStateException if the Telephony service is not currently available.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
* @hide
*/
@SystemApi
@@ -10828,6 +11298,8 @@
* @throws SecurityException if the caller does not have MODIFY_PHONE_STATE permission.
* @throws IllegalStateException if the Telephony service is not currently available.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
* @hide
*/
@SystemApi
@@ -10857,6 +11329,8 @@
* @throws SecurityException if the caller does not have READ_PRIVILEGED_PHONE_STATE permission.
* @throws IllegalStateException if the Telephony service is not currently available.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
* @hide
*/
@SystemApi
@@ -10886,6 +11360,8 @@
* <p>To know when the radio has completed powering off, use
* {@link PhoneStateListener#LISTEN_SERVICE_STATE LISTEN_SERVICE_STATE}.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
* @hide
*/
@SystemApi
@@ -10907,6 +11383,9 @@
* Check if any radio is on over all the slot indexes.
*
* @return {@code true} if any radio is on over any slot index.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
* @hide
*/
@SystemApi
@@ -10953,6 +11432,8 @@
* {@link android.Manifest.permission#READ_PHONE_STATE} or that the calling
* app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
* @hide
*/
@SystemApi
@@ -10982,7 +11463,11 @@
Log.e(TAG, "Do not call TelephonyManager#updateServiceLocation()");
}
- /** @hide */
+ /**
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_DATA}.
+ * @hide
+ */
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
@@ -10997,7 +11482,11 @@
return false;
}
- /** @hide */
+ /**
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_DATA}.
+ * @hide
+ */
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
@@ -11012,7 +11501,11 @@
return false;
}
- /** @hide */
+ /**
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_DATA}.
+ * @hide
+ */
@SystemApi
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
public boolean isDataConnectivityPossible() {
@@ -11027,7 +11520,11 @@
return false;
}
- /** @hide */
+ /**
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
+ * @hide
+ */
@SystemApi
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
public boolean needsOtaServiceProvisioning() {
@@ -11078,23 +11575,29 @@
* app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
* @param enable Whether to enable mobile data.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_DATA}.
* @deprecated use setDataEnabledForReason with reason DATA_ENABLED_REASON_USER instead.
*
*/
@Deprecated
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
public void setDataEnabled(boolean enable) {
setDataEnabled(getSubId(SubscriptionManager.getDefaultDataSubscriptionId()), enable);
}
/**
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_DATA}.
* @hide
* @deprecated use {@link #setDataEnabledForReason(int, boolean)} instead.
*/
@SystemApi
@Deprecated
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
public void setDataEnabled(int subId, boolean enable) {
try {
setDataEnabledForReason(subId, DATA_ENABLED_REASON_USER, enable);
@@ -11105,10 +11608,14 @@
/**
* @deprecated use {@link #isDataEnabled()} instead.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_DATA}.
* @hide
*/
@SystemApi
@Deprecated
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
public boolean getDataEnabled() {
return isDataEnabled();
}
@@ -11132,6 +11639,9 @@
* {@link ConnectivityManager#getRestrictBackgroundStatus}.
*
* @return true if mobile data is enabled.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_DATA}.
*/
@RequiresPermission(anyOf = {android.Manifest.permission.ACCESS_NETWORK_STATE,
android.Manifest.permission.MODIFY_PHONE_STATE,
@@ -11162,6 +11672,9 @@
* has carrier privileges (see {@link #hasCarrierPrivileges}).
*
* @return {@code true} if the data roaming is enabled on the subscription, otherwise return
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_DATA}.
* {@code false}.
*/
@RequiresPermission(anyOf = {android.Manifest.permission.ACCESS_NETWORK_STATE,
@@ -11201,6 +11714,8 @@
* {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
* or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CDMA}.
* @hide
*/
@SystemApi
@@ -11243,6 +11758,8 @@
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
* app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CDMA}.
* @hide
*/
@SystemApi
@@ -11311,6 +11828,8 @@
* {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
* or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CDMA}.
* @hide
*/
@SystemApi
@@ -11349,6 +11868,8 @@
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
* app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CDMA}.
* @hide
*/
@SystemApi
@@ -11384,6 +11905,8 @@
*
* @param isEnabled {@code true} to enable mobile data roaming, otherwise disable it.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_DATA}.
* @hide
*/
@SystemApi
@@ -11402,11 +11925,15 @@
}
/**
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_DATA}.
+ *
* @deprecated use {@link #isDataEnabled()} instead.
* @hide
*/
@Deprecated
@SystemApi
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
public boolean getDataEnabled(int subId) {
try {
return isDataEnabledForReason(subId, DATA_ENABLED_REASON_USER);
@@ -11417,6 +11944,8 @@
}
/**
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @deprecated Use {@link android.telephony.ims.ImsMmTelManager#setVtSettingEnabled(boolean)}
* instead.
* @hide
@@ -11424,6 +11953,7 @@
@Deprecated
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS)
public void enableVideoCalling(boolean enable) {
try {
ITelephony telephony = getITelephony();
@@ -11435,6 +11965,8 @@
}
/**
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @deprecated Use {@link ImsMmTelManager#isVtSettingEnabled()} instead to check if the user
* has enabled the Video Calling setting, {@link ImsMmTelManager#isAvailable(int, int)} to
* determine if video calling is available, or {@link ImsMmTelManager#isCapable(int, int)} to
@@ -11447,6 +11979,7 @@
android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
android.Manifest.permission.READ_PHONE_STATE
})
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS)
public boolean isVideoCallingEnabled() {
try {
ITelephony telephony = getITelephony();
@@ -11462,6 +11995,9 @@
* Whether the device supports configuring the DTMF tone length.
*
* @return {@code true} if the DTMF tone length can be changed, and {@code false} otherwise.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
public boolean canChangeDtmfToneLength() {
@@ -11483,7 +12019,11 @@
* Whether the device is a world phone.
*
* @return {@code true} if the device is a world phone, and {@code false} otherwise.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY}.
*/
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY)
public boolean isWorldPhone() {
try {
ITelephony telephony = getITelephony();
@@ -11504,8 +12044,11 @@
*
* @return {@code true} if the device supports TTY mode, and {@code false} otherwise.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELECOM}.
*/
@Deprecated
+ @RequiresFeature(PackageManager.FEATURE_TELECOM)
public boolean isTtyModeSupported() {
try {
TelecomManager telecomManager = null;
@@ -11526,6 +12069,9 @@
* support for the feature and device firmware support.
*
* @return {@code true} if the device and carrier both support RTT, {@code false} otherwise.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS)
public boolean isRttSupported() {
@@ -11546,6 +12092,9 @@
*
* @return {@code true} if the device supports hearing aid compatibility, and {@code false}
* otherwise.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
public boolean isHearingAidCompatibilitySupported() {
@@ -11808,11 +12357,14 @@
* <p>Requires Permission:
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* {@hide}
**/
@SystemApi
@Deprecated
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
public void setSimPowerState(int state) {
setSimPowerStateForSlot(getSlotIndex(), state);
}
@@ -11834,11 +12386,14 @@
* <p>Requires Permission:
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* {@hide}
**/
@SystemApi
@Deprecated
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
public void setSimPowerStateForSlot(int slotIndex, int state) {
try {
ITelephony telephony = getITelephony();
@@ -11871,6 +12426,8 @@
* <p>Requires Permission:
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* {@hide}
**/
@SystemApi
@@ -11901,6 +12458,8 @@
* <p>Requires Permission:
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* {@hide}
**/
@SystemApi
@@ -12055,6 +12614,9 @@
* application currently configured for this user.
* @return component name of the app and class to direct Respond Via Message intent to, or
* {@code null} if the functionality is not supported.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
* @hide
*/
@SystemApi
@@ -12077,6 +12639,9 @@
* user associated with this subscription.
* @return component name of the app and class to direct Respond Via Message intent to, or
* {@code null} if the functionality is not supported.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
* @hide
*/
@SystemApi
@@ -12213,9 +12778,13 @@
* @return The {@link PhoneAccountHandle} associated with the TelphonyManager, or {@code null}
* if there is no associated {@link PhoneAccountHandle}; this can happen if the subscription is
* data-only or an opportunistic subscription.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
public @Nullable PhoneAccountHandle getPhoneAccountHandle() {
return getPhoneAccountHandleForSubscriptionId(getSubId());
}
@@ -12277,10 +12846,14 @@
* Resets Telephony and IMS settings back to factory defaults only for the subscription
* associated with this instance.
* @see #createForSubscriptionId(int)
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY}.
* @hide
*/
@SystemApi
@RequiresPermission(Manifest.permission.CONNECTIVITY_INTERNAL)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY)
public void resetSettings() {
try {
Log.d(TAG, "resetSettings: subId=" + getSubId());
@@ -12302,6 +12875,8 @@
*
* @see Locale#toLanguageTag()
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@SystemApi
@@ -12394,10 +12969,14 @@
* @param callback A callback object to which the result will be delivered. If there was an
* error processing the request, {@link OutcomeReceiver#onError} will be called
* with more details about the error.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY}.
* @hide
*/
@SystemApi
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY)
public void requestModemActivityInfo(@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<ModemActivityInfo, ModemActivityInfoException> callback) {
Objects.requireNonNull(executor);
@@ -12484,6 +13063,9 @@
* and {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}.
* May return {@code null} when the subscription is inactive or when there was an error
* communicating with the phone process.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(allOf = {
@@ -12516,12 +13098,16 @@
* location related information.
* May return {@code null} when the subscription is inactive or when there was an error
* communicating with the phone process.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(allOf = {
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.ACCESS_COARSE_LOCATION
})
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
public @Nullable ServiceState getServiceState(@IncludeLocationData int includeLocationData) {
return getServiceStateForSubscriber(getSubId(),
includeLocationData != INCLUDE_LOCATION_DATA_FINE,
@@ -12580,6 +13166,9 @@
* voicemail ringtone.
* @return The URI for the ringtone to play when receiving a voicemail from a specific
* PhoneAccount. May be {@code null} if no ringtone is set.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
public @Nullable Uri getVoicemailRingtoneUri(PhoneAccountHandle accountHandle) {
@@ -12606,10 +13195,13 @@
* @param uri The URI for the ringtone to play when receiving a voicemail from a specific
* PhoneAccount.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
* @deprecated Use {@link android.provider.Settings#ACTION_CHANNEL_NOTIFICATION_SETTINGS}
* instead.
*/
@Deprecated
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
public void setVoicemailRingtoneUri(PhoneAccountHandle phoneAccountHandle, Uri uri) {
try {
ITelephony service = getITelephony();
@@ -12627,6 +13219,9 @@
* @param accountHandle The handle for the {@link PhoneAccount} for which to retrieve the
* voicemail vibration setting.
* @return {@code true} if the vibration is set for this PhoneAccount, {@code false} otherwise.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
public boolean isVoicemailVibrationEnabled(PhoneAccountHandle accountHandle) {
@@ -12653,10 +13248,13 @@
* @param enabled Whether to enable or disable vibration for voicemail notifications from a
* specific PhoneAccount.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
* @deprecated Use {@link android.provider.Settings#ACTION_CHANNEL_NOTIFICATION_SETTINGS}
* instead.
*/
@Deprecated
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
public void setVoicemailVibrationEnabled(PhoneAccountHandle phoneAccountHandle,
boolean enabled) {
try {
@@ -12682,6 +13280,9 @@
*
* @return Carrier id of the current subscription. Return {@link #UNKNOWN_CARRIER_ID} if the
* subscription is unavailable or the carrier cannot be identified.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
public int getSimCarrierId() {
@@ -12707,6 +13308,9 @@
*
* @return Carrier name of the current subscription. Return {@code null} if the subscription is
* unavailable or the carrier cannot be identified.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
public @Nullable CharSequence getSimCarrierIdName() {
@@ -12745,6 +13349,9 @@
* @return Returns fine-grained carrier id of the current subscription.
* Return {@link #UNKNOWN_CARRIER_ID} if the subscription is unavailable or the carrier cannot
* be identified.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
public int getSimSpecificCarrierId() {
@@ -12771,6 +13378,9 @@
*
* @return user-facing name of the subscription specific carrier id. Return {@code null} if the
* subscription is unavailable or the carrier cannot be identified.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
public @Nullable CharSequence getSimSpecificCarrierIdName() {
@@ -12799,6 +13409,9 @@
*
* @return matching carrier id from sim MCCMNC. Return {@link #UNKNOWN_CARRIER_ID} if the
* subscription is unavailable or the carrier cannot be identified.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
public int getCarrierIdFromSimMccMnc() {
@@ -12874,6 +13487,9 @@
*
* @param appType the uicc app type.
* @return Application ID for specified app type or {@code null} if no uicc or error.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@Nullable
@@ -12939,6 +13555,9 @@
* Requires that the calling app has READ_PRIVILEGED_PHONE_STATE permission
*
* @return PRLVersion or null if error.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CDMA}.
* @hide
*/
@SystemApi
@@ -13004,6 +13623,9 @@
*
* @return The number of carriers set successfully. Should be length of
* carrierList on success; -1 if carrierList null or on error.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CARRIERLOCK}.
* @hide
*/
@SystemApi
@@ -13130,6 +13752,9 @@
* @return {@link #SET_CARRIER_RESTRICTION_SUCCESS} in case of success.
* {@link #SET_CARRIER_RESTRICTION_NOT_SUPPORTED} if the modem does not support the
* configuration. {@link #SET_CARRIER_RESTRICTION_ERROR} in all other error cases.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CARRIERLOCK}.
* @hide
*/
@SystemApi
@@ -13163,11 +13788,15 @@
*
* @return List of {@link android.telephony.CarrierIdentifier}; empty list
* means all carriers are allowed.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CARRIERLOCK}.
* @hide
*/
@Deprecated
@SystemApi
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CARRIERLOCK)
public List<CarrierIdentifier> getAllowedCarriers(int slotIndex) {
if (SubscriptionManager.isValidPhoneId(slotIndex)) {
CarrierRestrictionRules carrierRestrictionRule = getCarrierRestrictionRules();
@@ -13189,6 +13818,9 @@
* @return {@link CarrierRestrictionRules} which contains the allowed carrier list and the
* excluded carrier list with the priority between the two lists. Returns {@code null}
* in case of error.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CARRIERLOCK}.
* @hide
*/
@SystemApi
@@ -13257,6 +13889,8 @@
* status result fetched from the radio
* @throws SecurityException if the caller does not have the required permission/privileges or
* if the caller is not pre-registered.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
@@ -13323,11 +13957,15 @@
* @see #resetAllCarrierActions()
* @deprecated use {@link #setDataEnabledForReason(int, boolean) with
* reason {@link #DATA_ENABLED_REASON_CARRIER}} instead.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_DATA}.
* @hide
*/
@Deprecated
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
public void setCarrierDataEnabled(boolean enabled) {
try {
setDataEnabledForReason(DATA_ENABLED_REASON_CARRIER, enabled);
@@ -13351,6 +13989,8 @@
* @deprecated - use the APIs {@link requestRadioPowerOffForReason} and
* {@link clearRadioPowerOffForReason}.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
* @hide
*/
@Deprecated
@@ -13470,6 +14110,9 @@
*
* @param report control start/stop reporting network status.
* @see #resetAllCarrierActions()
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
* @hide
*/
@SystemApi
@@ -13496,6 +14139,8 @@
*
* <p>Requires Permission:
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@SystemApi
@@ -13618,6 +14263,8 @@
* has {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} irrespective of
* the reason.
* @throws IllegalStateException if the Telephony process is not currently available.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_DATA}.
*/
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
@@ -13661,6 +14308,8 @@
* {@link android.Manifest.permission#MODIFY_PHONE_STATE}
* {@link android.Manifest.permission#READ_BASIC_PHONE_STATE}
* @throws IllegalStateException if the Telephony process is not currently available.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_DATA}.
*/
@RequiresPermission(anyOf = {android.Manifest.permission.ACCESS_NETWORK_STATE,
android.Manifest.permission.READ_PHONE_STATE,
@@ -13717,6 +14366,9 @@
* given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}
*
* @return true if phone is in emergency callback mode.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
* @hide
*/
@SystemApi
@@ -13756,6 +14408,9 @@
* given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}.
*
* @return {@code true} if manual network selection is allowed, otherwise return {@code false}.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
*/
@SuppressAutoDoc // No support carrier privileges (b/72967236).
@RequiresPermission(anyOf = {android.Manifest.permission.READ_PRECISE_PHONE_STATE,
@@ -13779,6 +14434,9 @@
* Get the most recent SignalStrength information reported by the modem. Due
* to power saving this information may not always be current.
* @return the most recent cached signal strength info from the modem
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
*/
@Nullable
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
@@ -13805,6 +14463,9 @@
* <LI>And possibly others.</LI>
* </UL>
* @return {@code true} if the overall data connection is allowed; {@code false} if not.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_DATA}.
*/
@RequiresPermission(anyOf = {android.Manifest.permission.ACCESS_NETWORK_STATE,
android.Manifest.permission.READ_PHONE_STATE,
@@ -13975,6 +14636,9 @@
*
* @param enable enable(True) or disable(False)
* @return returns true if successfully set.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
* @hide
*/
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -14004,6 +14668,9 @@
* <p>
* Requires Permission:
* {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
* @hide
*/
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@@ -14205,6 +14872,8 @@
* that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
* @throws SecurityException if the caller does not have the required permission
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
*/
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
@@ -14240,6 +14909,8 @@
* <p> Requires permission:
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
* @hide
*/
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -14267,6 +14938,8 @@
* <p> Requires permission:
* {@link android.Manifest.permission#READ_ACTIVE_EMERGENCY_SESSION}
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
* @hide
*/
@RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION)
@@ -14293,6 +14966,8 @@
* <p> Requires permission:
* {@link android.Manifest.permission#READ_ACTIVE_EMERGENCY_SESSION}
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
* @hide
*/
@RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION)
@@ -14353,6 +15028,9 @@
* subscription, the key is {@link SubscriptionManager#getDefaultSubscriptionId}) and the value
* as the list of {@link EmergencyNumber}; empty Map if this information is not available;
* or throw a SecurityException if the caller does not have the permission.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
*/
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
@NonNull
@@ -14409,6 +15087,8 @@
* as the list of {@link EmergencyNumber}; empty Map if this information is not available;
* or throw a SecurityException if the caller does not have the permission.
* @throws IllegalStateException if the Telephony process is not currently available.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
*/
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
@NonNull
@@ -14477,6 +15157,8 @@
* @return {@code true} if the given number is an emergency number based on current locale,
* SIM card(s), Android database, modem, network or defaults; {@code false} otherwise.
* @throws IllegalStateException if the Telephony process is not currently available.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
public boolean isEmergencyNumber(@NonNull String number) {
@@ -14514,6 +15196,8 @@
* network; {@code false} if it is not; or throw an SecurityException if the caller does not
* have the required permission/privileges
* @throws IllegalStateException if the Telephony process is not currently available.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
*
* @deprecated Please use {@link TelephonyManager#isEmergencyNumber(String)} instead.
* @hide
@@ -14543,6 +15227,8 @@
* <p>Requires Permission:
* {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
* @hide
*/
@SystemApi
@@ -14694,6 +15380,9 @@
* @param callback Callback will be triggered once it succeeds or failed.
* See the {@code SET_OPPORTUNISTIC_SUB_*} constants
* for more details. Pass null if don't care about the result.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_DATA}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
public void setPreferredOpportunisticDataSubscription(int subId, boolean needValidation,
@@ -14754,6 +15443,8 @@
* {@link SubscriptionManager#DEFAULT_SUBSCRIPTION_ID} if there are no preferred
* subscription id
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_DATA}.
*/
@RequiresPermission(anyOf = {
android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
@@ -14793,6 +15484,8 @@
* @param executor The executor of where the callback will execute.
* @param callback Callback will be triggered once it succeeds or failed.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
@@ -14853,10 +15546,13 @@
* @param enable whether to enable or disable the modem stack.
* @return whether the operation is successful.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY}.
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY)
public boolean enableModemForSlot(int slotIndex, boolean enable) {
boolean ret = false;
try {
@@ -14879,10 +15575,14 @@
* {@link #hasCarrierPrivileges()}).
*
* @param slotIndex which slot it's checking.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY}.
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(anyOf = {android.Manifest.permission.READ_PHONE_STATE,
android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE})
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY)
public boolean isModemEnabledForSlot(int slotIndex) {
try {
ITelephony telephony = getITelephony();
@@ -14945,6 +15645,8 @@
* @param isMultiSimCarrierRestricted true if usage of multiple SIMs is restricted, false
* otherwise.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CARRIERLOCK}.
* @hide
*/
@SystemApi
@@ -15000,6 +15702,9 @@
* {@link #MULTISIM_NOT_SUPPORTED_BY_HARDWARE} if the device does not support multiple SIMs.
* {@link #MULTISIM_NOT_SUPPORTED_BY_CARRIER} in the device supports multiple SIMs, but the
* functionality is restricted by the carrier.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
@@ -15031,6 +15736,8 @@
*
* @param numOfSims number of live SIMs we want to switch to
* @throws android.os.RemoteException
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -15058,6 +15765,9 @@
*
* @return {@code true} if reboot will be triggered after making changes to modem
* configurations, otherwise return {@code false}.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@RequiresPermission(Manifest.permission.READ_PHONE_STATE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@@ -15220,6 +15930,8 @@
* {@link #CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED}, or
* {@link #CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES}
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@SystemApi
@@ -15334,6 +16046,8 @@
* @param apnType Value indicating the apn type. Apn types are defined in {@link ApnSetting}.
* @return whether data is enabled for a apn type.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_DATA}.
* @hide
*/
@SystemApi
@@ -15356,6 +16070,8 @@
* Whether an APN type is metered or not. It will be evaluated with the subId associated
* with the TelephonyManager instance.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_DATA}.
* @hide
*/
@SystemApi
@@ -15385,6 +16101,9 @@
* @param executor The executor to execute the callback on
* @param callback The callback that gets invoked when the radio responds to the request. Called
* with {@code true} if the request succeeded, {@code false} otherwise.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
* @hide
*/
@SystemApi
@@ -15403,6 +16122,9 @@
* Same as {@link #setSystemSelectionChannels(List, Executor, Consumer<Boolean>)}, but to be
* used when the caller does not need feedback on the results of the operation.
* @param specifiers which bands to scan.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
* @hide
*/
@SystemApi
@@ -15450,6 +16172,8 @@
* @return a list of {@link RadioAccessSpecifier}, or an empty list if no bands are specified.
* @throws IllegalStateException if the Telephony process is not currently available.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
* @hide
*/
@SystemApi
@@ -15478,6 +16202,8 @@
* @return {@code true} if input mccmnc and mvno matches with data from sim operator.
* {@code false} otherwise.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* {@hide}
*/
@SystemApi
@@ -15568,6 +16294,8 @@
* {@link CallForwardingInfo#REASON_UNCONDITIONAL}, {@link CallForwardingInfo#REASON_BUSY},
* {@link CallForwardingInfo#REASON_NO_REPLY}, {@link CallForwardingInfo#REASON_NOT_REACHABLE},
* {@link CallForwardingInfo#REASON_ALL}, or {@link CallForwardingInfo#REASON_ALL_CONDITIONAL}
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
*
* @hide
*/
@@ -15645,6 +16373,8 @@
* <li>{@link CallForwardingInfo#getTimeoutSeconds()} returns a non-positive value when
* enabling call forwarding</li>
* </ul>
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
* @hide
*/
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -15769,6 +16499,9 @@
* <li>{@link #CALL_WAITING_STATUS_NOT_SUPPORTED}}</li>
* <li>{@link #CALL_WAITING_STATUS_FDN_CHECK_FAILURE}}</li>
* </ul>
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
* @hide
*/
@SystemApi
@@ -15819,6 +16552,9 @@
* {@link #CALL_WAITING_STATUS_NOT_SUPPORTED} or
* {@link #CALL_WAITING_STATUS_UNKNOWN_ERROR} or
* {@link #CALL_WAITING_STATUS_FDN_CHECK_FAILURE} if it failed.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
* @hide
*/
@SystemApi
@@ -15919,6 +16655,9 @@
*
* @param policy The data policy to enable.
* @param enabled Whether to enable or disable the policy.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_DATA}.
* @hide
*/
@SystemApi
@@ -15940,6 +16679,9 @@
*
* @param policy The data policy that you want the status for.
* @return {@code true} if enabled, {@code false} otherwise.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_DATA}.
* @hide
*/
@SystemApi
@@ -15975,6 +16717,8 @@
* {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
* or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@WorkerThread
@@ -16009,6 +16753,8 @@
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
* app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@SystemApi
@@ -16051,6 +16797,8 @@
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
* app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@SystemApi
@@ -16199,6 +16947,8 @@
* </ol>
* @return operation result.
* @throws IllegalStateException if the Telephony process is not currently available.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
* @hide
*/
@SystemApi
@@ -16233,6 +16983,8 @@
* connectivity is active. It means the device is allowed to connect to both primary and
* secondary cell.
* @throws IllegalStateException if the Telephony process is not currently available.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
* @hide
*/
@SystemApi
@@ -16470,6 +17222,8 @@
*
* @throws IllegalStateException if the Telephony process is not currently available.
* @throws SecurityException if the caller doesn't have the permission.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*
*/
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
@@ -16586,6 +17340,9 @@
*
* @param capability the name of the capability to check for
* @return the availability of the capability
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
public boolean isRadioInterfaceCapabilitySupported(
@@ -16705,6 +17462,8 @@
* @throws IllegalArgumentException if the thermalMitigationRequest had invalid parameters or
* if the device's modem does not support data throttling.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
* @hide
*/
@SystemApi
@@ -17037,6 +17796,9 @@
* contain the GBA Ks_NAF/Ks_ext_NAF when available. If the NAF keys are
* available and valid at the time of call and bootstrapping is not requested,
* then the callback shall be invoked with the available keys.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@SystemApi
@@ -17135,6 +17897,8 @@
* @param request the SignalStrengthUpdateRequest to be set into the System
*
* @throws IllegalStateException if a new request is set with same subId from the same caller
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -17165,6 +17929,9 @@
* @see #setSignalStrengthUpdateRequest(SignalStrengthUpdateRequest)
*
* @param request the SignalStrengthUpdateRequest to be cleared from the System
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -17188,10 +17955,14 @@
* @return the PhoneCapability which describes the data connection capability of modem.
* It's used to evaluate possible phone config change, for example from single
* SIM device to multi-SIM device.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
public @NonNull PhoneCapability getPhoneCapability() {
try {
ITelephony telephony = getITelephony();
@@ -17256,11 +18027,15 @@
* at least one SIM card for which the user needs to manually enter the PIN
* code after the reboot. {@link #PREPARE_UNATTENDED_REBOOT_ERROR} in case
* of error.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.REBOOT)
@PrepareUnattendedRebootResult
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
public int prepareForUnattendedReboot() {
try {
ITelephony service = getITelephony();
@@ -17363,6 +18138,9 @@
*
* @param executor the executor on which callback will be invoked.
* @param callback a callback to receive the current slicing configuration.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
*/
@RequiresFeature(
enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported",
@@ -17444,8 +18222,11 @@
* @param capability The premium capability to check.
* @return Whether the given premium capability is available to purchase.
* @throws SecurityException if the caller does not hold permission READ_BASIC_PHONE_STATE.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_DATA}.
*/
@RequiresPermission(android.Manifest.permission.READ_BASIC_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
public boolean isPremiumCapabilityAvailableForPurchase(@PremiumCapability int capability) {
try {
ITelephony telephony = getITelephony();
@@ -17685,10 +18466,13 @@
* @param callback The result of the purchase request.
* @throws SecurityException if the caller does not hold permissions
* READ_BASIC_PHONE_STATE or INTERNET.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_DATA}.
* @see #isPremiumCapabilityAvailableForPurchase(int) to check whether the capability is valid.
*/
@RequiresPermission(allOf = {android.Manifest.permission.READ_BASIC_PHONE_STATE,
android.Manifest.permission.INTERNET})
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
public void purchasePremiumCapability(@PremiumCapability int capability,
@NonNull @CallbackExecutor Executor executor,
@NonNull @PurchasePremiumCapabilityResult Consumer<Integer> callback) {
@@ -18142,10 +18926,14 @@
* Get current cell broadcast message identifier ranges.
*
* @throws SecurityException if the caller does not have the required permission
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
+ *
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_CELL_BROADCASTS)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
@NonNull
public List<CellBroadcastIdRange> getCellBroadcastIdRanges() {
try {
@@ -18299,10 +19087,13 @@
* the result when the operation completes.
* @throws SecurityException if the caller does not have the required permission
* @throws IllegalArgumentException when the ranges are invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_CELL_BROADCASTS)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
public void setCellBroadcastIdRanges(@NonNull List<CellBroadcastIdRange> ranges,
@NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<Integer> callback) {
@@ -18378,7 +19169,8 @@
* </ul>
*
* @return Primary IMEI of type string
- * @throws UnsupportedOperationException if the radio doesn't support this feature.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_GSM}.
* @throws SecurityException if the caller does not have the required permission/privileges
*/
@NonNull
diff --git a/telephony/java/android/telephony/euicc/EuiccCardManager.java b/telephony/java/android/telephony/euicc/EuiccCardManager.java
index e981e1f..69594f2 100644
--- a/telephony/java/android/telephony/euicc/EuiccCardManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccCardManager.java
@@ -183,6 +183,9 @@
* @param cardId The Id of the eUICC.
* @param executor The executor through which the callback should be invoked.
* @param callback The callback to get the result code and all the profiles.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void requestAllProfiles(String cardId, @CallbackExecutor Executor executor,
ResultCallback<EuiccProfileInfo[]> callback) {
@@ -212,6 +215,9 @@
* @param iccid The iccid of the profile.
* @param executor The executor through which the callback should be invoked.
* @param callback The callback to get the result code and profile.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void requestProfile(String cardId, String iccid, @CallbackExecutor Executor executor,
ResultCallback<EuiccProfileInfo> callback) {
@@ -244,6 +250,9 @@
* ICCID is known, an APDU will be sent through to read the enabled profile.
* @param executor The executor through which the callback should be invoked.
* @param callback The callback to get the result code and the profile.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void requestEnabledProfileForPort(@NonNull String cardId, int portIndex,
@NonNull @CallbackExecutor Executor executor,
@@ -276,6 +285,9 @@
* @param refresh Whether sending the REFRESH command to modem.
* @param executor The executor through which the callback should be invoked.
* @param callback The callback to get the result code.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void disableProfile(String cardId, String iccid, boolean refresh,
@CallbackExecutor Executor executor, ResultCallback<Void> callback) {
@@ -307,6 +319,9 @@
* @param refresh Whether sending the REFRESH command to modem.
* @param executor The executor through which the callback should be invoked.
* @param callback The callback to get the result code and the EuiccProfileInfo enabled.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
* @deprecated instead use {@link #switchToProfile(String, String, int, boolean, Executor,
* ResultCallback)}
*/
@@ -344,6 +359,9 @@
* @param refresh Whether sending the REFRESH command to modem.
* @param executor The executor through which the callback should be invoked.
* @param callback The callback to get the result code and the EuiccProfileInfo enabled.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void switchToProfile(@Nullable String cardId, @Nullable String iccid, int portIndex,
boolean refresh, @NonNull @CallbackExecutor Executor executor,
@@ -375,6 +393,9 @@
* @param nickname The nickname of the profile.
* @param executor The executor through which the callback should be invoked.
* @param callback The callback to get the result code.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void setNickname(String cardId, String iccid, String nickname,
@CallbackExecutor Executor executor, ResultCallback<Void> callback) {
@@ -404,6 +425,9 @@
* @param iccid The iccid of the profile.
* @param executor The executor through which the callback should be invoked.
* @param callback The callback to get the result code.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void deleteProfile(String cardId, String iccid, @CallbackExecutor Executor executor,
ResultCallback<Void> callback) {
@@ -434,6 +458,9 @@
* EuiccCard for details.
* @param executor The executor through which the callback should be invoked.
* @param callback The callback to get the result code.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void resetMemory(String cardId, @ResetOption int options,
@CallbackExecutor Executor executor, ResultCallback<Void> callback) {
@@ -462,6 +489,9 @@
* @param cardId The Id of the eUICC.
* @param executor The executor through which the callback should be invoked.
* @param callback The callback to get the result code and the default SM-DP+ address.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void requestDefaultSmdpAddress(String cardId, @CallbackExecutor Executor executor,
ResultCallback<String> callback) {
@@ -490,6 +520,9 @@
* @param cardId The Id of the eUICC.
* @param executor The executor through which the callback should be invoked.
* @param callback The callback to get the result code and the SM-DS address.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void requestSmdsAddress(String cardId, @CallbackExecutor Executor executor,
ResultCallback<String> callback) {
@@ -519,6 +552,9 @@
* @param defaultSmdpAddress The default SM-DP+ address to set.
* @param executor The executor through which the callback should be invoked.
* @param callback The callback to get the result code.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void setDefaultSmdpAddress(String cardId, String defaultSmdpAddress,
@CallbackExecutor Executor executor, ResultCallback<Void> callback) {
@@ -548,6 +584,9 @@
* @param cardId The Id of the eUICC.
* @param executor The executor through which the callback should be invoked.
* @param callback the callback to get the result code and the rule authorisation table.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void requestRulesAuthTable(String cardId, @CallbackExecutor Executor executor,
ResultCallback<EuiccRulesAuthTable> callback) {
@@ -576,6 +615,9 @@
* @param cardId The Id of the eUICC.
* @param executor The executor through which the callback should be invoked.
* @param callback the callback to get the result code and the challenge.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void requestEuiccChallenge(String cardId, @CallbackExecutor Executor executor,
ResultCallback<byte[]> callback) {
@@ -604,6 +646,9 @@
* @param cardId The Id of the eUICC.
* @param executor The executor through which the callback should be invoked.
* @param callback the callback to get the result code and the info1.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void requestEuiccInfo1(String cardId, @CallbackExecutor Executor executor,
ResultCallback<byte[]> callback) {
@@ -632,6 +677,9 @@
* @param cardId The Id of the eUICC.
* @param executor The executor through which the callback should be invoked.
* @param callback the callback to get the result code and the info2.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void requestEuiccInfo2(String cardId, @CallbackExecutor Executor executor,
ResultCallback<byte[]> callback) {
@@ -671,6 +719,9 @@
* @param executor The executor through which the callback should be invoked.
* @param callback the callback to get the result code and a byte array which represents a
* {@code AuthenticateServerResponse} defined in GSMA RSP v2.0+.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void authenticateServer(String cardId, String matchingId, byte[] serverSigned1,
byte[] serverSignature1, byte[] euiccCiPkIdToBeUsed, byte[] serverCertificate,
@@ -716,6 +767,9 @@
* @param executor The executor through which the callback should be invoked.
* @param callback the callback to get the result code and a byte array which represents a
* {@code PrepareDownloadResponse} defined in GSMA RSP v2.0+
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void prepareDownload(String cardId, @Nullable byte[] hashCc, byte[] smdpSigned2,
byte[] smdpSignature2, byte[] smdpCertificate, @CallbackExecutor Executor executor,
@@ -753,6 +807,9 @@
* @param executor The executor through which the callback should be invoked.
* @param callback the callback to get the result code and a byte array which represents a
* {@code LoadBoundProfilePackageResponse} defined in GSMA RSP v2.0+.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void loadBoundProfilePackage(String cardId, byte[] boundProfilePackage,
@CallbackExecutor Executor executor, ResultCallback<byte[]> callback) {
@@ -787,6 +844,9 @@
* @param executor The executor through which the callback should be invoked.
* @param callback the callback to get the result code and an byte[] which represents a
* {@code CancelSessionResponse} defined in GSMA RSP v2.0+.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void cancelSession(String cardId, byte[] transactionId, @CancelReason int reason,
@CallbackExecutor Executor executor, ResultCallback<byte[]> callback) {
@@ -820,6 +880,9 @@
* @param events bits of the event types ({@link EuiccNotification.Event}) to list.
* @param executor The executor through which the callback should be invoked.
* @param callback the callback to get the result code and the list of notifications.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void listNotifications(String cardId, @EuiccNotification.Event int events,
@CallbackExecutor Executor executor, ResultCallback<EuiccNotification[]> callback) {
@@ -850,6 +913,9 @@
* @param events bits of the event types ({@link EuiccNotification.Event}) to list.
* @param executor The executor through which the callback should be invoked.
* @param callback the callback to get the result code and the list of notifications.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void retrieveNotificationList(String cardId, @EuiccNotification.Event int events,
@CallbackExecutor Executor executor, ResultCallback<EuiccNotification[]> callback) {
@@ -880,6 +946,9 @@
* @param seqNumber the sequence number of the notification.
* @param executor The executor through which the callback should be invoked.
* @param callback the callback to get the result code and the notification.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void retrieveNotification(String cardId, int seqNumber,
@CallbackExecutor Executor executor, ResultCallback<EuiccNotification> callback) {
@@ -910,6 +979,9 @@
* @param seqNumber the sequence number of the notification.
* @param executor The executor through which the callback should be invoked.
* @param callback the callback to get the result code.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void removeNotificationFromList(String cardId, int seqNumber,
@CallbackExecutor Executor executor, ResultCallback<Void> callback) {
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 86fbb04..09d2108 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -927,6 +927,9 @@
* subscription APIs.
*
* @return true if embedded subscriptions are currently enabled.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public boolean isEnabled() {
// In the future, this may reach out to IEuiccController (if non-null) to check any dynamic
@@ -942,6 +945,9 @@
* access to the EID of another eUICC.
*
* @return the EID. May be null if the eUICC is not ready.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
@Nullable
public String getEid() {
@@ -963,6 +969,8 @@
* @return the status of eUICC OTA. If the eUICC is not ready,
* {@link OtaStatus#EUICC_OTA_STATUS_UNAVAILABLE} will be returned.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
* @hide
*/
@SystemApi
@@ -1014,6 +1022,9 @@
* @param subscription the subscription to download.
* @param switchAfterDownload if true, the profile will be activated upon successful download.
* @param callbackIntent a PendingIntent to launch when the operation completes.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
@RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
public void downloadSubscription(DownloadableSubscription subscription,
@@ -1075,6 +1086,9 @@
* @param resolutionExtras Resolution-specific extras depending on the result of the resolution.
* For example, this may indicate whether the user has consented or may include the input
* they provided.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
* @hide
*/
@SystemApi
@@ -1111,6 +1125,9 @@
*
* @param subscription the subscription which needs metadata filled in
* @param callbackIntent a PendingIntent to launch when the operation completes.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
* @hide
*/
@SystemApi
@@ -1142,6 +1159,9 @@
* internal system use only.
*
* @param callbackIntent a PendingIntent to launch when the operation completes.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
* @hide
*/
@SystemApi
@@ -1163,6 +1183,9 @@
* Returns information about the eUICC chip/device.
*
* @return the {@link EuiccInfo}. May be null if the eUICC is not ready.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
@Nullable
public EuiccInfo getEuiccInfo() {
@@ -1188,6 +1211,9 @@
*
* @param subscriptionId the ID of the subscription to delete.
* @param callbackIntent a PendingIntent to launch when the operation completes.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
@RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
public void deleteSubscription(int subscriptionId, PendingIntent callbackIntent) {
@@ -1251,6 +1277,9 @@
* {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission, or the
* calling app must be authorized to manage the active subscription on the target eUICC.
* @param callbackIntent a PendingIntent to launch when the operation completes.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
@RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
public void switchToSubscription(int subscriptionId, PendingIntent callbackIntent) {
@@ -1312,6 +1341,9 @@
* {@link SubscriptionInfo#getPortIndex()}.
* @param portIndex the index of the port to target for the enabled subscription
* @param callbackIntent a PendingIntent to launch when the operation completes.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
@RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
public void switchToSubscription(int subscriptionId, int portIndex,
@@ -1349,6 +1381,9 @@
* @param subscriptionId the ID of the subscription to update.
* @param nickname the new nickname to apply.
* @param callbackIntent a PendingIntent to launch when the operation completes.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
@RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
public void updateSubscriptionNickname(
@@ -1376,6 +1411,8 @@
* @deprecated From R, callers should specify a flag for specific set of subscriptions to erase
* and use {@link #eraseSubscriptions(int, PendingIntent)} instead
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
* @hide
*/
@SystemApi
@@ -1402,6 +1439,8 @@
* @param options flag indicating specific set of subscriptions to erase
* @param callbackIntent a PendingIntent to launch when the operation completes.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
* @hide
*/
@SystemApi
@@ -1459,6 +1498,9 @@
* determine whether a country is supported please check {@link #isSupportedCountry}.
*
* @param supportedCountries is a list of strings contains country ISO codes in uppercase.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
* @hide
*/
@SystemApi
@@ -1487,6 +1529,9 @@
* determine whether a country is supported please check {@link #isSupportedCountry}.
*
* @param unsupportedCountries is a list of strings contains country ISO codes in uppercase.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
* @hide
*/
@SystemApi
@@ -1512,6 +1557,9 @@
* {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission.
*
* @return list of strings contains country ISO codes in uppercase.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
* @hide
*/
@SystemApi
@@ -1535,6 +1583,9 @@
* {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission.
*
* @return list of strings contains country ISO codes in uppercase.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
* @hide
*/
@SystemApi
@@ -1566,6 +1617,9 @@
* @param countryIso should be the ISO-3166 country code is provided in uppercase 2 character
* format.
* @return whether the given country supports eUICC or not.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
* @hide
*/
@SystemApi
@@ -1630,6 +1684,9 @@
*
* @param portIndex is an enumeration of the ports available on the UICC.
* @return {@code true} if port is available
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public boolean isSimPortAvailable(int portIndex) {
try {
diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java
index 71bb329..551057f 100644
--- a/telephony/java/android/telephony/ims/ImsMmTelManager.java
+++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java
@@ -779,6 +779,8 @@
* @see android.telephony.CarrierConfigManager#KEY_CARRIER_VOLTE_AVAILABLE_BOOL
* @throws IllegalArgumentException if the subscription associated with this operation is not
* active (SIM is not inserted, ESIM inactive) or invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @return true if the user's setting for advanced calling is enabled, false otherwise.
*/
@SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
@@ -827,6 +829,8 @@
* @see #isAdvancedCallingSettingEnabled()
* @throws IllegalArgumentException if the subscription associated with this operation is not
* active (SIM is not inserted, ESIM inactive) or invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*/
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@@ -865,6 +869,8 @@
* @param capability The IMS MmTel capability to query.
* @return {@code true} if the MmTel IMS capability is capable for this subscription, false
* otherwise.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*/
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@@ -893,6 +899,8 @@
* @param capability The IMS MmTel capability to query.
* @return {@code true} if the MmTel IMS capability is available for this subscription, false
* otherwise.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*/
@SystemApi
@@ -986,6 +994,8 @@
*
* @throws IllegalArgumentException if the subscription associated with this operation is not
* active (SIM is not inserted, ESIM inactive) or invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @return true if the user’s “Video Calling” setting is currently enabled.
*/
@RequiresPermission(anyOf = {
@@ -1017,6 +1027,8 @@
*
* @throws IllegalArgumentException if the subscription associated with this operation is not
* active (SIM is not inserted, ESIM inactive) or invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @see #isVtSettingEnabled()
* @hide
*/
@@ -1060,6 +1072,8 @@
*
* @throws IllegalArgumentException if the subscription associated with this operation is not
* active (SIM is not inserted, ESIM inactive) or invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
*/
@SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
@RequiresPermission(anyOf = {
@@ -1090,6 +1104,8 @@
*
* @throws IllegalArgumentException if the subscription associated with this operation is not
* active (SIM is not inserted, ESIM inactive) or invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @param isEnabled true if the user's setting for Voice over WiFi is enabled, false otherwise=
* @see #isVoWiFiSettingEnabled()
* @hide
@@ -1148,6 +1164,8 @@
*
* @throws ImsException if the IMS service associated with this subscription is not available or
* the IMS service is not available.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @return true if the user's setting for Voice over Cross SIM is enabled and false if it is not
*/
@SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
@@ -1192,6 +1210,8 @@
* </ul>
* @throws ImsException if the IMS service associated with this subscription is not available or
* the IMS service is not available.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @param isEnabled true if the user's setting for Voice over Cross SIM is enabled,
* false otherwise
* @see #isCrossSimCallingEnabled()
@@ -1233,6 +1253,8 @@
*
* @throws IllegalArgumentException if the subscription associated with this operation is not
* active (SIM is not inserted, ESIM inactive) or invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @return true if the user's setting for Voice over WiFi while roaming is enabled, false
* if disabled.
*/
@@ -1267,6 +1289,8 @@
* false otherwise.
* @throws IllegalArgumentException if the subscription associated with this operation is not
* active (SIM is not inserted, ESIM inactive) or invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @see #isVoWiFiRoamingSettingEnabled()
* @hide
*/
@@ -1304,6 +1328,8 @@
* - {@link #WIFI_MODE_WIFI_PREFERRED}
* @throws IllegalArgumentException if the subscription associated with this operation is not
* active (SIM is not inserted, ESIM inactive) or invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @see #setVoWiFiSettingEnabled(boolean)
* @hide
*/
@@ -1347,6 +1373,8 @@
*
* @throws IllegalArgumentException if the subscription associated with this operation is not
* active (SIM is not inserted, ESIM inactive) or invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @return The Voice over WiFi Mode preference set by the user, which can be one of the
* following:
* - {@link #WIFI_MODE_WIFI_ONLY}
@@ -1386,6 +1414,8 @@
* - {@link #WIFI_MODE_WIFI_PREFERRED}
* @throws IllegalArgumentException if the subscription associated with this operation is not
* active (SIM is not inserted, ESIM inactive) or invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @see #getVoWiFiModeSetting()
* @hide
*/
@@ -1422,6 +1452,8 @@
* - {@link #WIFI_MODE_WIFI_PREFERRED}
* @throws IllegalArgumentException if the subscription associated with this operation is not
* active (SIM is not inserted, ESIM inactive) or invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @see #setVoWiFiRoamingSettingEnabled(boolean)
* @hide
*/
@@ -1458,6 +1490,8 @@
* - {@link #WIFI_MODE_WIFI_PREFERRED}
* @throws IllegalArgumentException if the subscription associated with this operation is not
* active (SIM is not inserted, ESIM inactive) or invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @see #getVoWiFiRoamingModeSetting()
* @hide
*/
@@ -1492,6 +1526,8 @@
* settings.
* @throws IllegalArgumentException if the subscription associated with this operation is not
* active (SIM is not inserted, ESIM inactive) or invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @param isEnabled if true RTT should be enabled during calls made on this subscription.
* @hide
*/
@@ -1535,6 +1571,8 @@
*
* @throws IllegalArgumentException if the subscription associated with this operation is not
* active (SIM is not inserted, ESIM inactive) or invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @see android.telephony.CarrierConfigManager#KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL
*/
@SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
diff --git a/telephony/java/android/telephony/ims/ImsRcsManager.java b/telephony/java/android/telephony/ims/ImsRcsManager.java
index 2b49bcd..62d4263 100644
--- a/telephony/java/android/telephony/ims/ImsRcsManager.java
+++ b/telephony/java/android/telephony/ims/ImsRcsManager.java
@@ -250,6 +250,8 @@
* the {@code ImsService} associated with the subscription is not available. This can happen if
* the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
* reason.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
*/
@RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
public void registerImsRegistrationCallback(
@@ -294,6 +296,8 @@
* @param c The {@link RegistrationManager.RegistrationCallback} to be removed.
* @see android.telephony.SubscriptionManager.OnSubscriptionsChangedListener
* @see #registerImsRegistrationCallback(Executor, RegistrationCallback)
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
*/
@RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
public void unregisterImsRegistrationCallback(
@@ -329,6 +333,8 @@
* following: {@link RegistrationManager#REGISTRATION_STATE_NOT_REGISTERED},
* {@link RegistrationManager#REGISTRATION_STATE_REGISTERING}, or
* {@link RegistrationManager#REGISTRATION_STATE_REGISTERED}.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
*/
@RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
public void getRegistrationState(@NonNull @CallbackExecutor Executor executor,
@@ -378,6 +384,8 @@
* {@see AccessNetworkConstants#TRANSPORT_TYPE_WWAN},
* {@see AccessNetworkConstants#TRANSPORT_TYPE_WLAN}, or
* {@see AccessNetworkConstants#TRANSPORT_TYPE_INVALID}.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
*/
@RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
public void getRegistrationTransportType(@NonNull @CallbackExecutor Executor executor,
@@ -435,6 +443,8 @@
* {@link ImsRcsManager} is valid, but the ImsService associated with the subscription is not
* available. This can happen if the ImsService has crashed, for example, or if the subscription
* becomes inactive. See {@link ImsException#getCode()} for more information on the error codes.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*/
@SystemApi
@@ -479,6 +489,8 @@
* @see #addOnAvailabilityChangedListener(Executor, OnAvailabilityChangedListener)
* @throws ImsException if the IMS service is not available when calling this method.
* See {@link ImsException#getCode()} for more information on the error codes.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*/
@SystemApi
@@ -525,6 +537,8 @@
* @see android.telephony.CarrierConfigManager.Ims#KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL
* @throws ImsException if the IMS service is not available when calling this method.
* See {@link ImsException#getCode()} for more information on the error codes.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*/
@SystemApi
@@ -563,6 +577,8 @@
* @see #isCapable(int, int)
* @throws ImsException if the IMS service is not available when calling this method.
* See {@link ImsException#getCode()} for more information on the error codes.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*/
@SystemApi
diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java
index 1c5d1e9..62b8420 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -1300,8 +1300,10 @@
* @param executor The executor that the callback methods will be called on.
* @param callback The callback instance being registered.
* @throws ImsException if the subscription associated with this callback is
- * valid, but the {@link ImsService the service crashed, for example. See
+ * valid, but the service crashed, for example. See
* {@link ImsException#getCode()} for a more detailed reason.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
*/
@RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
public void registerFeatureProvisioningChangedCallback(
@@ -1327,6 +1329,8 @@
*
* @param callback The existing {@link FeatureProvisioningCallback} to be removed.
* @see #registerFeatureProvisioningChangedCallback(Executor, FeatureProvisioningCallback)
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
*/
public void unregisterFeatureProvisioningChangedCallback(
@NonNull FeatureProvisioningCallback callback) {
@@ -1347,6 +1351,8 @@
* @return an integer value for the provided key, or
* {@link ImsConfigImplBase#CONFIG_RESULT_UNKNOWN} if the key doesn't exist.
* @throws IllegalArgumentException if the key provided was invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*/
@SystemApi
@@ -1369,6 +1375,8 @@
* @return a String value for the provided key, {@code null} if the key doesn't exist, or
* {@link StringResultError} if there was an error getting the value for the provided key.
* @throws IllegalArgumentException if the key provided was invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*/
@SystemApi
@@ -1392,6 +1400,8 @@
* @param key An integer that represents the provisioning key, which is defined by the OEM.
* @param value a integer value for the provided key.
* @return the result of setting the configuration value.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*
* Note: For compatibility purposes, the integer values [0 - 99] used in
@@ -1420,6 +1430,8 @@
* should be appropriately namespaced to avoid collision.
* @param value a String value for the provided key.
* @return the result of setting the configuration value.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*/
@SystemApi
@@ -1451,6 +1463,9 @@
*
* @see CarrierConfigManager.Ims#KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE
* @param isProvisioned true if the device is provisioned for UT over IMS, false otherwise.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
*/
@WorkerThread
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@@ -1485,6 +1500,9 @@
* @return true if the device is provisioned for the capability or does not require
* provisioning, false if the capability does require provisioning and has not been
* provisioned yet.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
*/
@WorkerThread
@RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
@@ -1509,6 +1527,9 @@
* @return true if the device is provisioned for the capability or does not require
* provisioning, false if the capability does require provisioning and has not been
* provisioned yet.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
+ *
* @deprecated Use {@link #getRcsProvisioningStatusForCapability(int, int)} instead,
* as this only retrieves provisioning information for
* {@link ImsRegistrationImplBase#REGISTRATION_TECH_LTE}
@@ -1546,6 +1567,9 @@
* @return true if the device is provisioned for the capability or does not require
* provisioning, false if the capability does require provisioning and has not been
* provisioned yet.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
*/
@WorkerThread
@RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
@@ -1577,6 +1601,9 @@
* @see CarrierConfigManager#KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL
* @param isProvisioned true if the device is provisioned for the RCS capability specified,
* false otherwise.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
+ *
* @deprecated Use {@link #setRcsProvisioningStatusForCapability(int, int, boolean)} instead,
* as this method only sets provisioning information for
* {@link ImsRegistrationImplBase#REGISTRATION_TECH_LTE}
@@ -1615,6 +1642,9 @@
* @see CarrierConfigManager.Ims#KEY_RCS_REQUIRES_PROVISIONING_BUNDLE
* @param isProvisioned true if the device is provisioned for the RCS capability specified,
* false otherwise.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
*/
@WorkerThread
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@@ -1644,6 +1674,9 @@
* @return true if provisioning is required for the MMTEL capability and IMS
* registration technology specified, false if it is not required or if the device does not
* support IMS.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
*/
@RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
public boolean isProvisioningRequiredForCapability(
@@ -1672,6 +1705,9 @@
* @return true if provisioning is required for the RCS capability and IMS
* registration technology specified, false if it is not required or if the device does not
* support IMS.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
*/
@RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
public boolean isRcsProvisioningRequiredForCapability(
@@ -1700,10 +1736,14 @@
* @param config The XML file to be read. ASCII/UTF8 encoded text if not compressed.
* @param isCompressed The XML file is compressed in gzip format and must be decompressed
* before being read.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION}.
* @hide
*/
@SystemApi
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION)
public void notifyRcsAutoConfigurationReceived(@NonNull byte[] config, boolean isCompressed) {
if (config == null) {
throw new IllegalArgumentException("Must include a non-null config XML file.");
@@ -1714,7 +1754,6 @@
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
-
}
/**
@@ -1787,10 +1826,14 @@
* When the IMS/RCS service receives the RCS client configuration, it will detect
* the change in the configuration, and trigger the auto-configuration as needed.
* @param rcc RCS client configuration {@link RcsClientConfiguration}
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION}.
* @hide
*/
@SystemApi
@RequiresPermission(Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION)
public void setRcsClientConfiguration(
@NonNull RcsClientConfiguration rcc) throws ImsException {
try {
@@ -1826,6 +1869,7 @@
@RequiresPermission(anyOf = {
Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION})
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION)
public boolean isRcsVolteSingleRegistrationCapable() throws ImsException {
try {
return getITelephony().isRcsVolteSingleRegistrationCapable(mSubId);
@@ -1870,12 +1914,15 @@
* params (See {@link #setRcsClientConfiguration}) and re register the
* callback.
* See {@link ImsException#getCode()} for a more detailed reason.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION}.
* @hide
*/
@SystemApi
@RequiresPermission(anyOf = {
Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION})
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION)
public void registerRcsProvisioningCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull RcsProvisioningCallback callback) throws ImsException {
@@ -1908,12 +1955,15 @@
* @see #registerRcsProvisioningCallback(Executor, RcsProvisioningCallback)
* @throws IllegalArgumentException if the subscription associated with
* this callback is invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION}.
* @hide
*/
@SystemApi
@RequiresPermission(anyOf = {
Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION})
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION)
public void unregisterRcsProvisioningCallback(
@NonNull RcsProvisioningCallback callback) {
try {
@@ -1935,10 +1985,14 @@
* {@link RcsProvisioningCallback#onConfigurationReset}, then
* {@link RcsProvisioningCallback#onConfigurationChanged} when the new
* RCS configuration is received and notified by {@link #notifyRcsAutoConfigurationReceived}
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION}.
* @hide
*/
@SystemApi
@RequiresPermission(Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION)
public void triggerRcsReconfiguration() {
try {
getITelephony().triggerRcsReconfiguration(mSubId);
diff --git a/telephony/java/android/telephony/ims/RcsUceAdapter.java b/telephony/java/android/telephony/ims/RcsUceAdapter.java
index 3bb9be0..8925a9e 100644
--- a/telephony/java/android/telephony/ims/RcsUceAdapter.java
+++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java
@@ -21,9 +21,11 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
@@ -49,6 +51,7 @@
*
* @see ImsRcsManager#getUceAdapter() for information on creating an instance of this class.
*/
+@RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS)
public class RcsUceAdapter {
private static final String TAG = "RcsUceAdapter";
@@ -585,6 +588,8 @@
* {@link RcsUceAdapter} is valid, but the ImsService associated with the subscription is not
* available. This can happen if the ImsService has crashed, for example, or if the subscription
* becomes inactive. See {@link ImsException#getCode()} for more information on the error codes.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*/
@SystemApi
@@ -682,6 +687,8 @@
* {@link RcsUceAdapter} is valid, but the ImsService associated with the subscription is not
* available. This can happen if the ImsService has crashed, for example, or if the subscription
* becomes inactive. See {@link ImsException#getCode()} for more information on the error codes.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*/
@SystemApi
@@ -759,6 +766,8 @@
* {@link RcsUceAdapter} is valid, but the ImsService associated with the subscription is not
* available. This can happen if the ImsService has crashed, for example, or if the subscription
* becomes inactive. See {@link ImsException#getCode()} for more information on the error codes.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*/
@SystemApi
@@ -800,6 +809,8 @@
* the {@link ImsService} associated with the subscription is not available. This can happen if
* the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
* reason.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*/
@SystemApi
@@ -845,6 +856,8 @@
* the {@link ImsService} associated with the subscription is not available. This can happen if
* the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
* reason.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*/
@SystemApi
@@ -901,6 +914,8 @@
* {@link RcsUceAdapter} is valid, but the ImsService associated with the subscription is not
* available. This can happen if the ImsService has crashed, for example, or if the subscription
* becomes inactive. See {@link ImsException#getCode()} for more information on the error codes.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
*/
@RequiresPermission(Manifest.permission.READ_PHONE_STATE)
public boolean isUceSettingEnabled() throws ImsException {
@@ -954,6 +969,8 @@
* {@link RcsUceAdapter} is valid, but the ImsService associated with the subscription is not
* available. This can happen if the ImsService has crashed, for example, or if the subscription
* becomes inactive. See {@link ImsException#getCode()} for more information on the error codes.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*/
@SystemApi
diff --git a/telephony/java/android/telephony/ims/SipDelegateManager.java b/telephony/java/android/telephony/ims/SipDelegateManager.java
index 25ebdd0..abf2105 100644
--- a/telephony/java/android/telephony/ims/SipDelegateManager.java
+++ b/telephony/java/android/telephony/ims/SipDelegateManager.java
@@ -525,6 +525,8 @@
* @param callback The callback instance being registered.
* @throws ImsException in the case that the callback can not be registered.
* See {@link ImsException#getCode} for more information on when this is called.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION}.
*/
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public void registerSipDialogStateCallback(@NonNull Executor executor,
@@ -557,6 +559,9 @@
* {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE}
*
* @param callback The callback instance to be unregistered.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION}.
*/
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public void unregisterSipDialogStateCallback(@NonNull SipDialogStateCallback callback)
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 84777c9..9b5ee0c 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3055,6 +3055,29 @@
boolean setEmergencyCallToSatelliteHandoverType(int handoverType, int delaySeconds);
/**
+ * This API should be used by only CTS tests to forcefully set the country codes.
+ *
+ * @param reset {@code true} mean the overridden country codes should not be used, {@code false}
+ * otherwise.
+ * @return {@code true} if the country code is set successfully, {@code false} otherwise.
+ */
+ boolean setCountryCodes(in boolean reset, in List<String> currentNetworkCountryCodes,
+ in Map cachedNetworkCountryCodes, in String locationCountryCode,
+ in long locationCountryCodeTimestampNanos);
+
+ /**
+ * This API should be used by only CTS tests to override the overlay configs of satellite
+ * access controller.
+ *
+ * @param reset {@code true} mean the overridden configs should not be used, {@code false}
+ * otherwise.
+ * @return {@code true} if the overlay configs are set successfully, {@code false} otherwise.
+ */
+ boolean setSatelliteAccessControlOverlayConfigs(in boolean reset, in boolean isAllowed,
+ in String s2CellFile, in long locationFreshDurationNanos,
+ in List<String> satelliteCountryCodes);
+
+ /**
* Test method to confirm the file contents are not altered.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
diff --git a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index 9c33576..bbd4567 100644
--- a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -36,12 +36,12 @@
import android.view.InputDevice
import android.view.inputmethod.InputMethodInfo
import android.view.inputmethod.InputMethodSubtype
-import androidx.test.core.R
import androidx.test.core.app.ApplicationProvider
import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.internal.os.KeyboardConfiguredProto
import com.android.internal.util.FrameworkStatsLog
import com.android.modules.utils.testing.ExtendedMockitoRule
+import com.android.test.input.R
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
diff --git a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
index ba9e4a8..f82d9ca 100644
--- a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
+++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
@@ -130,14 +130,13 @@
private static final Pattern PATTERN_SYSTEM_FONT_FILES =
Pattern.compile("^/(system|product)/fonts/");
- private String mKeyId;
private FontManager mFontManager;
private UiDevice mUiDevice;
@Before
public void setUp() throws Exception {
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
- mKeyId = insertCert(CERT_PATH);
+ insertCert(CERT_PATH);
mFontManager = context.getSystemService(FontManager.class);
expectCommandToSucceed("cmd font clear");
mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
@@ -147,9 +146,6 @@
public void tearDown() throws Exception {
// Ignore errors because this may fail if updatable system font is not enabled.
runShellCommand("cmd font clear", null);
- if (mKeyId != null) {
- expectCommandToSucceed("mini-keyctl unlink " + mKeyId + " .fs-verity");
- }
}
@Test
@@ -369,20 +365,11 @@
assertThat(isFileOpenedBy(fontPath, EMOJI_RENDERING_TEST_APP_ID)).isFalse();
}
- private static String insertCert(String certPath) throws Exception {
- Pair<String, String> result;
- try (InputStream is = new FileInputStream(certPath)) {
- result = runShellCommand("mini-keyctl padd asymmetric fsv_test .fs-verity", is);
- }
+ private static void insertCert(String certPath) throws Exception {
// /data/local/tmp is not readable by system server. Copy a cert file to /data/fonts
final String copiedCert = "/data/fonts/debug_cert.der";
runShellCommand("cp " + certPath + " " + copiedCert, null);
runShellCommand("cmd font install-debug-cert " + copiedCert, null);
- // Assert that there are no errors.
- assertThat(result.second).isEmpty();
- String keyId = result.first.trim();
- assertThat(keyId).matches("^\\d+$");
- return keyId;
}
private int updateFontFile(String fontPath, String signaturePath) throws IOException {