diff options
921 files changed, 17179 insertions, 8117 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 20471682dd2e..a80194cf53d2 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -310,6 +310,8 @@ java_aconfig_library { aconfig_declarations { name: "android.os.flags-aconfig", package: "android.os", + exportable: true, + container: "system", srcs: ["core/java/android/os/*.aconfig"], } @@ -326,6 +328,24 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +java_aconfig_library { + name: "android.os.flags-aconfig-java-export", + aconfig_declarations: "android.os.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], + mode: "exported", +} + +cc_aconfig_library { + name: "android.os.flags-aconfig-cc", + aconfig_declarations: "android.os.flags-aconfig", +} + +cc_aconfig_library { + name: "android.os.flags-aconfig-cc-test", + aconfig_declarations: "android.os.flags-aconfig", + mode: "test", +} + // VirtualDeviceManager cc_aconfig_library { name: "android.companion.virtualdevice.flags-aconfig-cc", @@ -483,6 +503,13 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +java_aconfig_library { + name: "android.content.res.flags-aconfig-java-host", + aconfig_declarations: "android.content.res.flags-aconfig", + host_supported: true, + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // Media BetterTogether aconfig_declarations { name: "com.android.media.flags.bettertogether-aconfig", diff --git a/Android.bp b/Android.bp index 5ada10d19f5d..8d7ab983593d 100644 --- a/Android.bp +++ b/Android.bp @@ -386,6 +386,7 @@ java_defaults { // TODO(b/120066492): remove gps_debug and protolog.conf.json when the build // system propagates "required" properly. "gps_debug.conf", + "protolog.conf.json.gz", "core.protolog.pb", "framework-res", // any install dependencies should go into framework-minus-apex-install-dependencies diff --git a/Android.mk b/Android.mk index e2c1ed8e9ddb..81284d54b93a 100644 --- a/Android.mk +++ b/Android.mk @@ -15,67 +15,6 @@ # LOCAL_PATH := $(call my-dir) -$(eval $(call declare-1p-copy-files,frameworks/base,.ogg)) -$(eval $(call declare-1p-copy-files,frameworks/base,.kl)) -$(eval $(call declare-1p-copy-files,frameworks/base,.kcm)) -$(eval $(call declare-1p-copy-files,frameworks/base,.idc)) -$(eval $(call declare-1p-copy-files,frameworks/base,dirty-image-objects)) -$(eval $(call declare-1p-copy-files,frameworks/base/config,)) -$(eval $(call declare-1p-copy-files,frameworks/native/data,)) - -# Load framework-specific path mappings used later in the build. -include $(LOCAL_PATH)/pathmap.mk - -# Build the master framework library. -# The framework contains too many method references (>64K) for poor old DEX. -# So we first build the framework as a monolithic static library then split it -# up into smaller pieces. -# ============================================================ - -# embedded builds use nothing in frameworks/base -ifneq ($(ANDROID_BUILD_EMBEDDED),true) - -# Copy AIDL files to be preprocessed and included in the SDK, -# specified relative to the root of the build tree. -# ============================================================ -include $(CLEAR_VARS) - -# sdk.atree needs to copy the whole dir: $(OUT_DOCS)/offline-sdk to the final zip. -# So keep offline-sdk-timestamp target here, and unzip offline-sdk-docs.zip to -# $(OUT_DOCS)/offline-sdk. -$(OUT_DOCS)/offline-sdk-timestamp: $(OUT_DOCS)/offline-sdk-docs-docs.zip - $(hide) rm -rf $(OUT_DOCS)/offline-sdk - $(hide) mkdir -p $(OUT_DOCS)/offline-sdk - ( unzip -qo $< -d $(OUT_DOCS)/offline-sdk && touch -f $@ ) || exit 1 - -.PHONY: docs offline-sdk-docs -docs offline-sdk-docs: $(OUT_DOCS)/offline-sdk-timestamp - -SDK_METADATA_DIR :=$= $(call intermediates-dir-for,PACKAGING,framework-doc-stubs-metadata,,COMMON) -SDK_METADATA_FILES :=$= $(addprefix $(SDK_METADATA_DIR)/,\ - activity_actions.txt \ - broadcast_actions.txt \ - categories.txt \ - features.txt \ - service_actions.txt \ - widgets.txt) -SDK_METADATA :=$= $(firstword $(SDK_METADATA_FILES)) -$(SDK_METADATA): .KATI_IMPLICIT_OUTPUTS := $(filter-out $(SDK_METADATA),$(SDK_METADATA_FILES)) -$(SDK_METADATA): $(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/framework-doc-stubs-metadata.zip - rm -rf $(SDK_METADATA_DIR) - mkdir -p $(SDK_METADATA_DIR) - unzip -DDqo $< -d $(SDK_METADATA_DIR) - -.PHONY: framework-doc-stubs -framework-doc-stubs: $(SDK_METADATA) - -# Include subdirectory makefiles -# ============================================================ - -# If we're building with ONE_SHOT_MAKEFILE (mm, mmm), then what the framework -# team really wants is to build the stuff defined by this makefile. -ifeq (,$(ONE_SHOT_MAKEFILE)) +# TODO: Removed it after all Android.mk files in the subdirectories are +# converted to Android.bp. include $(call first-makefiles-under,$(LOCAL_PATH)) -endif - -endif # ANDROID_BUILD_EMBEDDED diff --git a/Ravenwood.bp b/Ravenwood.bp index 4791640239b3..f43c37bf637d 100644 --- a/Ravenwood.bp +++ b/Ravenwood.bp @@ -205,8 +205,10 @@ android_ravenwood_libgroup { // Provide runtime versions of utils linked in below "junit", "truth", + "flag-junit", "ravenwood-framework", "ravenwood-junit-impl", + "ravenwood-junit-impl-flag", "mockito-ravenwood-prebuilt", "inline-mockito-ravenwood-prebuilt", ], @@ -220,6 +222,7 @@ android_ravenwood_libgroup { libs: [ "junit", "truth", + "flag-junit", "ravenwood-framework", "ravenwood-junit", "mockito-ravenwood-prebuilt", diff --git a/apct-tests/perftests/rubidium/src/android/rubidium/js/JSScriptEnginePerfTests.java b/apct-tests/perftests/rubidium/src/android/rubidium/js/JSScriptEnginePerfTests.java index ba15796f47fe..fc3738c7134a 100644 --- a/apct-tests/perftests/rubidium/src/android/rubidium/js/JSScriptEnginePerfTests.java +++ b/apct-tests/perftests/rubidium/src/android/rubidium/js/JSScriptEnginePerfTests.java @@ -53,6 +53,7 @@ import com.android.adservices.service.adselection.AdSelectionConfigArgumentUtil; import com.android.adservices.service.adselection.AdWithBidArgumentUtil; import com.android.adservices.service.adselection.CustomAudienceBiddingSignalsArgumentUtil; import com.android.adservices.service.adselection.CustomAudienceScoringSignalsArgumentUtil; +import com.android.adservices.service.common.NoOpRetryStrategyImpl; import com.android.adservices.service.js.IsolateSettings; import com.android.adservices.service.js.JSScriptArgument; import com.android.adservices.service.js.JSScriptArrayArgument; @@ -411,7 +412,8 @@ public class JSScriptEnginePerfTests { jsScript, args, functionName, - IsolateSettings.forMaxHeapSizeEnforcementDisabled()); + IsolateSettings.forMaxHeapSizeEnforcementDisabled(), + new NoOpRetryStrategyImpl()); result.addListener(resultLatch::countDown, sExecutorService); return result; } @@ -430,7 +432,8 @@ public class JSScriptEnginePerfTests { wasmScript, args, functionName, - IsolateSettings.forMaxHeapSizeEnforcementDisabled()); + IsolateSettings.forMaxHeapSizeEnforcementDisabled(), + new NoOpRetryStrategyImpl()); result.addListener(resultLatch::countDown, sExecutorService); return result; } diff --git a/apex/jobscheduler/service/Android.bp b/apex/jobscheduler/service/Android.bp index 558629537253..0104ee14fec4 100644 --- a/apex/jobscheduler/service/Android.bp +++ b/apex/jobscheduler/service/Android.bp @@ -21,6 +21,7 @@ java_library { libs: [ "app-compat-annotations", + "error_prone_annotations", "framework", "services.core", "unsupportedappusage", diff --git a/cmds/svc/src/com/android/commands/svc/PowerCommand.java b/cmds/svc/src/com/android/commands/svc/PowerCommand.java index a7560b23c6bd..12b79f4c42f8 100644 --- a/cmds/svc/src/com/android/commands/svc/PowerCommand.java +++ b/cmds/svc/src/com/android/commands/svc/PowerCommand.java @@ -23,8 +23,6 @@ import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; -import android.os.SystemProperties; -import android.sysprop.InitProperties; public class PowerCommand extends Svc.Command { private static final int FORCE_SUSPEND_DELAY_DEFAULT_MILLIS = 0; @@ -142,12 +140,10 @@ public class PowerCommand extends Svc.Command { // Check if remote exception is benign during shutdown. Pm can be killed // before system server during shutdown, so remote exception can be ignored // if it is already in shutdown flow. + // sys.powerctl is no longer set to avoid a possible DOS attack (see + // bionic/libc/bionic/system_property_set.cpp) so we have no real way of knowing if a + // remote exception is real or simply because pm is killed (b/318323013) + // So we simply do not display anything. private void maybeLogRemoteException(String msg) { - String powerProp = SystemProperties.get("sys.powerctl"); - // Also check if userspace reboot is ongoing, since in case of userspace reboot value of the - // sys.powerctl property will be reset. - if (powerProp.isEmpty() && !InitProperties.userspace_reboot_in_progress().orElse(false)) { - System.err.println(msg); - } } } diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 9c1a8e854e92..0ab2588dd87e 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -652,6 +652,7 @@ package android.webkit { method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.webkit.WebViewProviderResponse> CREATOR; field public static final int STATUS_FAILED_LISTING_WEBVIEW_PACKAGES = 4; // 0x4 + field public static final int STATUS_FAILED_OTHER = 11; // 0xb field public static final int STATUS_FAILED_WAITING_FOR_RELRO = 3; // 0x3 field public static final int STATUS_SUCCESS = 0; // 0x0 field @Nullable public final android.content.pm.PackageInfo packageInfo; diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 2922dd9ef5ae..d190c62413e0 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -4386,7 +4386,7 @@ package android.content.pm { 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 public static final int PROTECTION_FLAG_RETAIL_DEMO = 16777216; // 0x1000000 + field @Deprecated 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 @@ -4821,7 +4821,7 @@ package android.hardware { method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(@NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener); method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener); method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean areAnySensorPrivacyTogglesEnabled(int); - method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @NonNull @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public java.util.Map<java.lang.String,java.lang.Boolean> getCameraPrivacyAllowlist(); + method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @NonNull @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public java.util.List<java.lang.String> getCameraPrivacyAllowlist(); method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public int getSensorPrivacyState(int, int); method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isCameraPrivacyEnabled(@NonNull String); method @Deprecated @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isSensorPrivacyEnabled(int); @@ -4844,6 +4844,12 @@ package android.hardware { method public boolean isEnabled(); } + @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") public static class SensorPrivacyManager.StateTypes { + field public static final int DISABLED = 2; // 0x2 + field public static final int ENABLED = 1; // 0x1 + field public static final int ENABLED_EXCEPT_ALLOWLISTED_APPS = 3; // 0x3 + } + } package android.hardware.biometrics { @@ -10758,7 +10764,7 @@ package android.os { method public final double readDouble(); method public final java.util.ArrayList<java.lang.Double> readDoubleVector(); method public final android.os.HwBlob readEmbeddedBuffer(long, long, long, boolean); - method @NonNull @Nullable public final android.os.HidlMemory readEmbeddedHidlMemory(long, long, long); + method @NonNull public final android.os.HidlMemory readEmbeddedHidlMemory(long, long, long); method @Nullable public final android.os.NativeHandle readEmbeddedNativeHandle(long, long); method public final float readFloat(); method public final java.util.ArrayList<java.lang.Float> readFloatVector(); @@ -12963,9 +12969,22 @@ package android.service.ondeviceintelligence { method public abstract void onGetReadOnlyFeatureFileDescriptorMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,android.os.ParcelFileDescriptor>>); method public abstract void onGetVersion(@NonNull java.util.function.LongConsumer); method public abstract void onListFeatures(@NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>); + method public final void updateProcessingState(@NonNull android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException>); field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceIntelligenceService"; } + public abstract static class OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException extends java.lang.Exception { + ctor public OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException(int); + ctor public OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException(int, @NonNull String); + method public int getErrorCode(); + } + + public static class OnDeviceIntelligenceService.OnDeviceUpdateProcessingException extends android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException { + ctor public OnDeviceIntelligenceService.OnDeviceUpdateProcessingException(int); + ctor public OnDeviceIntelligenceService.OnDeviceUpdateProcessingException(int, @NonNull String); + field public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 1; // 0x1 + } + @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceTrustedInferenceService extends android.app.Service { ctor public OnDeviceTrustedInferenceService(); method public final void fetchFeatureFileInputStreamMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.io.FileInputStream>>); @@ -12973,6 +12992,7 @@ package android.service.ondeviceintelligence { method @NonNull public abstract void onCountTokens(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<java.lang.Long,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>); method @NonNull public abstract void onProcessRequest(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>); method @NonNull public abstract void onProcessRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.StreamingResponseReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>); + method public abstract void onUpdateProcessingState(@NonNull android.os.Bundle, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException>); method public final java.io.FileInputStream openFileInput(@NonNull String) throws java.io.FileNotFoundException; method public final void openFileInputAsync(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.io.FileInputStream>) throws java.io.FileNotFoundException; field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceTrustedInferenceService"; diff --git a/core/api/test-current.txt b/core/api/test-current.txt index af40c3d658a7..53d0c032dbaf 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1366,7 +1366,7 @@ package android.credentials.selection { } @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public class IntentFactory { - method @NonNull public static android.content.Intent createCancelUiIntent(@NonNull android.os.IBinder, boolean, @NonNull String); + method @NonNull public static android.content.Intent createCancelUiIntent(@NonNull android.content.Context, @NonNull android.os.IBinder, boolean, @NonNull String); method @NonNull public static android.content.Intent createCredentialSelectorIntent(@NonNull android.content.Context, @NonNull android.credentials.selection.RequestInfo, @NonNull java.util.ArrayList<android.credentials.selection.ProviderData>, @NonNull java.util.ArrayList<android.credentials.selection.DisabledProviderData>, @NonNull android.os.ResultReceiver); } @@ -1519,6 +1519,7 @@ package android.graphics.fonts { package android.hardware { public final class SensorPrivacyManager { + method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setCameraPrivacyAllowlist(@NonNull java.util.List<java.lang.String>); method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacy(int, int, boolean); method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacyState(int, int, int); } @@ -3944,6 +3945,7 @@ package android.view.inputmethod { method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getInputMethodListAsUser(int); method public boolean hasActiveInputConnection(@Nullable android.view.View); method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean hasPendingImeVisibilityRequests(); + method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void hideSoftInputFromServerForTest(); method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean isCurrentRootView(@NonNull android.view.View); method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean isInputMethodPickerShown(); method @FlaggedApi("android.view.inputmethod.imm_userhandle_hostsidetests") @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public boolean isStylusHandwritingAvailableAsUser(@NonNull android.os.UserHandle); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 41151c0dc647..1d39186de183 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -40,6 +40,7 @@ import static android.window.ConfigurationHelper.shouldUpdateResources; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL; import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext; +import static com.android.window.flags.Flags.activityWindowInfoFlag; import android.annotation.NonNull; import android.annotation.Nullable; @@ -63,6 +64,7 @@ import android.app.servertransaction.ActivityLifecycleItem.LifecycleState; import android.app.servertransaction.ActivityRelaunchItem; import android.app.servertransaction.ActivityResultItem; import android.app.servertransaction.ClientTransaction; +import android.app.servertransaction.ClientTransactionListenerController; import android.app.servertransaction.DestroyActivityItem; import android.app.servertransaction.PauseActivityItem; import android.app.servertransaction.PendingTransactionActions; @@ -606,6 +608,8 @@ public final class ActivityThread extends ClientTransactionHandler Configuration overrideConfig; @NonNull private ActivityWindowInfo mActivityWindowInfo; + @Nullable + private ActivityWindowInfo mLastReportedActivityWindowInfo; // Used for consolidating configs before sending on to Activity. private final Configuration tmpConfig = new Configuration(); @@ -4180,6 +4184,9 @@ public final class ActivityThread extends ClientTransactionHandler pendingActions.setRestoreInstanceState(true); pendingActions.setCallOnPostCreate(true); } + + // Trigger ActivityWindowInfo callback if first launch or change from relaunch. + handleActivityWindowInfoChanged(r); } else { // If there was an error, for any reason, tell the activity manager to stop us. ActivityClient.getInstance().finishActivity(r.token, Activity.RESULT_CANCELED, @@ -4558,7 +4565,7 @@ public final class ActivityThread extends ClientTransactionHandler private void schedulePauseWithUserLeavingHint(ActivityClientRecord r) { final ClientTransaction transaction = ClientTransaction.obtain(mAppThread); final PauseActivityItem pauseActivityItem = PauseActivityItem.obtain(r.token, - r.activity.isFinishing(), /* userLeaving */ true, r.activity.mConfigChangeFlags, + r.activity.isFinishing(), /* userLeaving */ true, /* dontReport */ false, /* autoEnteringPip */ false); transaction.addTransactionItem(pauseActivityItem); executeTransaction(transaction); @@ -5432,13 +5439,12 @@ public final class ActivityThread extends ClientTransactionHandler @Override public void handlePauseActivity(ActivityClientRecord r, boolean finished, boolean userLeaving, - int configChanges, boolean autoEnteringPip, PendingTransactionActions pendingActions, + boolean autoEnteringPip, PendingTransactionActions pendingActions, String reason) { if (userLeaving) { performUserLeavingActivity(r); } - r.activity.mConfigChangeFlags |= configChanges; if (autoEnteringPip) { // Set mIsInPictureInPictureMode earlier in case of auto-enter-pip, see also // {@link Activity#enterPictureInPictureMode(PictureInPictureParams)}. @@ -5687,9 +5693,8 @@ public final class ActivityThread extends ClientTransactionHandler } @Override - public void handleStopActivity(ActivityClientRecord r, int configChanges, + public void handleStopActivity(ActivityClientRecord r, PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) { - r.activity.mConfigChangeFlags |= configChanges; final StopInfo stopInfo = new StopInfo(); performStopActivityInner(r, stopInfo, true /* saveState */, finalStateRequest, @@ -5859,11 +5864,10 @@ public final class ActivityThread extends ClientTransactionHandler /** Core implementation of activity destroy call. */ void performDestroyActivity(ActivityClientRecord r, boolean finishing, - int configChanges, boolean getNonConfigInstance, String reason) { + boolean getNonConfigInstance, String reason) { Class<? extends Activity> activityClass; if (localLOGV) Slog.v(TAG, "Performing finish of " + r); activityClass = r.activity.getClass(); - r.activity.mConfigChangeFlags |= configChanges; if (finishing) { r.activity.mFinished = true; } @@ -5928,9 +5932,9 @@ public final class ActivityThread extends ClientTransactionHandler } @Override - public void handleDestroyActivity(ActivityClientRecord r, boolean finishing, int configChanges, + public void handleDestroyActivity(ActivityClientRecord r, boolean finishing, boolean getNonConfigInstance, String reason) { - performDestroyActivity(r, finishing, configChanges, getNonConfigInstance, reason); + performDestroyActivity(r, finishing, getNonConfigInstance, reason); cleanUpPendingRemoveWindows(r, finishing); WindowManager wm = r.activity.getWindowManager(); View v = r.activity.mDecor; @@ -6130,7 +6134,7 @@ public final class ActivityThread extends ClientTransactionHandler r.activity.mChangingConfigurations = true; - handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents, + handleRelaunchActivityInner(r, tmp.pendingResults, tmp.pendingIntents, pendingActions, tmp.startsNotResumed, tmp.overrideConfig, tmp.mActivityWindowInfo, "handleRelaunchActivity"); } @@ -6199,7 +6203,7 @@ public final class ActivityThread extends ClientTransactionHandler executeTransaction(transaction); } - private void handleRelaunchActivityInner(@NonNull ActivityClientRecord r, int configChanges, + private void handleRelaunchActivityInner(@NonNull ActivityClientRecord r, @Nullable List<ResultInfo> pendingResults, @Nullable List<ReferrerIntent> pendingIntents, @NonNull PendingTransactionActions pendingActions, boolean startsNotResumed, @@ -6215,7 +6219,7 @@ public final class ActivityThread extends ClientTransactionHandler callActivityOnStop(r, true /* saveState */, reason); } - handleDestroyActivity(r, false, configChanges, true, reason); + handleDestroyActivity(r, false /* finishing */, true /* getNonConfigInstance */, reason); r.activity = null; r.window = null; @@ -6740,7 +6744,7 @@ public final class ActivityThread extends ClientTransactionHandler // Perform updates. r.overrideConfig = overrideConfig; r.mActivityWindowInfo = activityWindowInfo; - // TODO(b/287582673): notify on ActivityWindowInfo change + final ViewRootImpl viewRoot = r.activity.mDecor != null ? r.activity.mDecor.getViewRootImpl() : null; @@ -6763,6 +6767,22 @@ public final class ActivityThread extends ClientTransactionHandler viewRoot.updateConfiguration(displayId); } mSomeActivitiesChanged = true; + + // Trigger ActivityWindowInfo callback if changed. + handleActivityWindowInfoChanged(r); + } + + private void handleActivityWindowInfoChanged(@NonNull ActivityClientRecord r) { + if (!activityWindowInfoFlag()) { + return; + } + if (r.mActivityWindowInfo == null + || r.mActivityWindowInfo.equals(r.mLastReportedActivityWindowInfo)) { + return; + } + r.mLastReportedActivityWindowInfo = r.mActivityWindowInfo; + ClientTransactionListenerController.getInstance().onActivityWindowInfoChanged(r.token, + r.mActivityWindowInfo); } final void handleProfilerControl(boolean start, ProfilerInfo profilerInfo, int profileType) { diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java index b5b3669c1d80..01153c9e7efc 100644 --- a/core/java/android/app/ClientTransactionHandler.java +++ b/core/java/android/app/ClientTransactionHandler.java @@ -114,11 +114,11 @@ public abstract class ClientTransactionHandler { /** Destroy the activity. */ public abstract void handleDestroyActivity(@NonNull ActivityClientRecord r, boolean finishing, - int configChanges, boolean getNonConfigInstance, String reason); + boolean getNonConfigInstance, String reason); /** Pause the activity. */ public abstract void handlePauseActivity(@NonNull ActivityClientRecord r, boolean finished, - boolean userLeaving, int configChanges, boolean autoEnteringPip, + boolean userLeaving, boolean autoEnteringPip, PendingTransactionActions pendingActions, String reason); /** @@ -146,14 +146,13 @@ public abstract class ClientTransactionHandler { /** * Stop the activity. * @param r Target activity record. - * @param configChanges Activity configuration changes. * @param pendingActions Pending actions to be used on this or later stages of activity * transaction. * @param finalStateRequest Flag indicating if this call is handling final lifecycle state * request for a transaction. * @param reason Reason for performing this operation. */ - public abstract void handleStopActivity(@NonNull ActivityClientRecord r, int configChanges, + public abstract void handleStopActivity(@NonNull ActivityClientRecord r, PendingTransactionActions pendingActions, boolean finalStateRequest, String reason); /** Report that activity was stopped to server. */ diff --git a/core/java/android/app/LocalActivityManager.java b/core/java/android/app/LocalActivityManager.java index 1b19ecdd5931..095cfc5a7303 100644 --- a/core/java/android/app/LocalActivityManager.java +++ b/core/java/android/app/LocalActivityManager.java @@ -413,7 +413,7 @@ public class LocalActivityManager { if (localLOGV) Log.v(TAG, r.id + ": destroying"); final ActivityClientRecord clientRecord = mActivityThread.getActivityClient(r); if (clientRecord != null) { - mActivityThread.performDestroyActivity(clientRecord, finish, 0 /* configChanges */, + mActivityThread.performDestroyActivity(clientRecord, finish, false /* getNonConfigInstance */, "LocalActivityManager::performDestroy"); } r.activity = null; @@ -684,7 +684,7 @@ public class LocalActivityManager { if (localLOGV) Log.v(TAG, r.id + ": no corresponding record"); continue; } - mActivityThread.performDestroyActivity(clientRecord, finishing, 0 /* configChanges */, + mActivityThread.performDestroyActivity(clientRecord, finishing, false /* getNonConfigInstance */, "LocalActivityManager::dispatchDestroy"); } mActivities.clear(); diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java index b2be27f70ccc..4a06f7d1a1c3 100644 --- a/core/java/android/app/LocaleConfig.java +++ b/core/java/android/app/LocaleConfig.java @@ -248,7 +248,8 @@ public class LocaleConfig implements Parcelable { } /** - * Returns the default locale if specified, otherwise null + * Returns the locale the strings in values/strings.xml (the default strings in the directory + * with no locale qualifier) are in if specified, otherwise null * * @return The default Locale or null */ diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index d49a2542eed8..283e21aab39c 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -2052,10 +2052,12 @@ public class NotificationManager { /** Notification senders to prioritize for calls. One of: * PRIORITY_SENDERS_ANY, PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED */ + @PrioritySenders public final int priorityCallSenders; /** Notification senders to prioritize for messages. One of: * PRIORITY_SENDERS_ANY, PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED */ + @PrioritySenders public final int priorityMessageSenders; /** @@ -2063,6 +2065,7 @@ public class NotificationManager { * {@link #CONVERSATION_SENDERS_NONE}, {@link #CONVERSATION_SENDERS_IMPORTANT}, * {@link #CONVERSATION_SENDERS_ANYONE}. */ + @ConversationSenders public final int priorityConversationSenders; /** @@ -2630,16 +2633,19 @@ public class NotificationManager { } /** @hide **/ + @PrioritySenders public int allowCallsFrom() { return priorityCallSenders; } /** @hide **/ + @PrioritySenders public int allowMessagesFrom() { return priorityMessageSenders; } /** @hide **/ + @ConversationSenders public int allowConversationsFrom() { return priorityConversationSenders; } diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index 625526047212..8b84f062b7b5 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -124,6 +124,32 @@ public class ResourcesManager { */ private LocaleConfig mLocaleConfig = new LocaleConfig(LocaleList.getEmptyLocaleList()); + private final ArrayMap<String, SharedLibraryAssets> mSharedLibAssetsMap = + new ArrayMap<>(); + + /** + * The internal function to register the resources paths of a package (e.g. a shared library). + * This will collect the package resources' paths from its ApplicationInfo and add them to all + * existing and future contexts while the application is running. + */ + public void registerResourcePaths(@NonNull String uniqueId, @NonNull ApplicationInfo appInfo) { + SharedLibraryAssets sharedLibAssets = new SharedLibraryAssets(appInfo.sourceDir, + appInfo.splitSourceDirs, appInfo.sharedLibraryFiles, + appInfo.resourceDirs, appInfo.overlayPaths); + + synchronized (mLock) { + if (mSharedLibAssetsMap.containsKey(uniqueId)) { + Slog.v(TAG, "Package resources' paths for uniqueId: " + uniqueId + + " has already been registered, this is a no-op."); + return; + } + mSharedLibAssetsMap.put(uniqueId, sharedLibAssets); + appendLibAssetsLocked(sharedLibAssets.getAllAssetPaths()); + Slog.v(TAG, "The following resources' paths have been added: " + + Arrays.toString(sharedLibAssets.getAllAssetPaths())); + } + } + private static class ApkKey { public final String path; public final boolean sharedLib; @@ -278,6 +304,21 @@ public class ResourcesManager { public ResourcesManager() { } + /** + * Inject a customized ResourcesManager instance for testing, return the old ResourcesManager + * instance. + */ + @UnsupportedAppUsage + @VisibleForTesting + public static ResourcesManager setInstance(ResourcesManager resourcesManager) { + synchronized (ResourcesManager.class) { + ResourcesManager oldResourceManager = sResourcesManager; + sResourcesManager = resourcesManager; + return oldResourceManager; + } + + } + @UnsupportedAppUsage public static ResourcesManager getInstance() { synchronized (ResourcesManager.class) { @@ -1480,6 +1521,56 @@ public class ResourcesManager { } } + private void appendLibAssetsLocked(String[] libAssets) { + synchronized (mLock) { + // Record which ResourcesImpl need updating + // (and what ResourcesKey they should update to). + final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>(); + + final int implCount = mResourceImpls.size(); + for (int i = 0; i < implCount; i++) { + final ResourcesKey key = mResourceImpls.keyAt(i); + final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i); + final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null; + if (impl == null) { + Slog.w(TAG, "Found a ResourcesImpl which is null, skip it and continue to " + + "append shared library assets for next ResourcesImpl."); + continue; + } + + var newDirs = new ArrayList<String>(); + var dirsSet = new ArraySet<String>(); + if (key.mLibDirs != null) { + final int dirsLength = key.mLibDirs.length; + for (int k = 0; k < dirsLength; k++) { + newDirs.add(key.mLibDirs[k]); + dirsSet.add(key.mLibDirs[k]); + } + } + final int assetsLength = libAssets.length; + for (int j = 0; j < assetsLength; j++) { + if (dirsSet.add(libAssets[j])) { + newDirs.add(libAssets[j]); + } + } + String[] newLibAssets = newDirs.toArray(new String[0]); + if (!Arrays.equals(newLibAssets, key.mLibDirs)) { + updatedResourceKeys.put(impl, new ResourcesKey( + key.mResDir, + key.mSplitResDirs, + key.mOverlayPaths, + newLibAssets, + key.mDisplayId, + key.mOverrideConfiguration, + key.mCompatInfo, + key.mLoaders)); + } + } + + redirectResourcesToNewImplLocked(updatedResourceKeys); + } + } + private void applyNewResourceDirsLocked(@Nullable final String[] oldSourceDirs, @NonNull final ApplicationInfo appInfo) { try { @@ -1689,4 +1780,50 @@ public class ResourcesManager { } } } + + public static class SharedLibraryAssets{ + private final String[] mAssetPaths; + + SharedLibraryAssets(String sourceDir, String[] splitSourceDirs, String[] sharedLibraryFiles, + String[] resourceDirs, String[] overlayPaths) { + mAssetPaths = collectAssetPaths(sourceDir, splitSourceDirs, sharedLibraryFiles, + resourceDirs, overlayPaths); + } + + private @NonNull String[] collectAssetPaths(String sourceDir, String[] splitSourceDirs, + String[] sharedLibraryFiles, String[] resourceDirs, String[] overlayPaths) { + final String[][] inputLists = { + splitSourceDirs, sharedLibraryFiles, resourceDirs, overlayPaths + }; + + final ArraySet<String> assetPathSet = new ArraySet<>(); + final List<String> assetPathList = new ArrayList<>(); + if (sourceDir != null) { + assetPathSet.add(sourceDir); + assetPathList.add(sourceDir); + } + + for (int i = 0; i < inputLists.length; i++) { + if (inputLists[i] != null) { + for (int j = 0; j < inputLists[i].length; j++) { + if (assetPathSet.add(inputLists[i][j])) { + assetPathList.add(inputLists[i][j]); + } + } + } + } + return assetPathList.toArray(new String[0]); + } + + /** + * @return all the asset paths of this collected in this class. + */ + public @NonNull String[] getAllAssetPaths() { + return mAssetPaths; + } + } + + public @NonNull ArrayMap<String, SharedLibraryAssets> getSharedLibAssetsMap() { + return new ArrayMap<>(mSharedLibAssetsMap); + } } diff --git a/core/java/android/app/admin/AccountTypePolicyKey.java b/core/java/android/app/admin/AccountTypePolicyKey.java index d81eb20512a2..51f313755e59 100644 --- a/core/java/android/app/admin/AccountTypePolicyKey.java +++ b/core/java/android/app/admin/AccountTypePolicyKey.java @@ -19,12 +19,12 @@ package android.app.admin; import static android.app.admin.PolicyUpdateReceiver.EXTRA_ACCOUNT_TYPE; import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_BUNDLE_KEY; import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_KEY; -import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; +import android.app.admin.flags.Flags; import android.os.Bundle; import android.os.Parcel; @@ -54,7 +54,7 @@ public final class AccountTypePolicyKey extends PolicyKey { @TestApi public AccountTypePolicyKey(@NonNull String key, @NonNull String accountType) { super(key); - if (devicePolicySizeTrackingEnabled()) { + if (Flags.devicePolicySizeTrackingInternalEnabled()) { PolicySizeVerifier.enforceMaxStringLength(accountType, "accountType"); } mAccountType = Objects.requireNonNull((accountType)); diff --git a/core/java/android/app/admin/BundlePolicyValue.java b/core/java/android/app/admin/BundlePolicyValue.java index cc5e75fac0ea..cb5e9861141d 100644 --- a/core/java/android/app/admin/BundlePolicyValue.java +++ b/core/java/android/app/admin/BundlePolicyValue.java @@ -16,10 +16,9 @@ package android.app.admin; -import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled; - import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.admin.flags.Flags; import android.os.Bundle; import android.os.Parcel; @@ -32,8 +31,8 @@ public final class BundlePolicyValue extends PolicyValue<Bundle> { public BundlePolicyValue(Bundle value) { super(value); - if (devicePolicySizeTrackingEnabled()) { - PolicySizeVerifier.enforceMaxParcelableFieldsLength(value); + if (Flags.devicePolicySizeTrackingInternalEnabled()) { + PolicySizeVerifier.enforceMaxBundleFieldsLength(value); } } diff --git a/core/java/android/app/admin/ComponentNamePolicyValue.java b/core/java/android/app/admin/ComponentNamePolicyValue.java index 4d36195613ad..a957dbf132bb 100644 --- a/core/java/android/app/admin/ComponentNamePolicyValue.java +++ b/core/java/android/app/admin/ComponentNamePolicyValue.java @@ -16,10 +16,9 @@ package android.app.admin; -import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled; - import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.admin.flags.Flags; import android.content.ComponentName; import android.os.Parcel; @@ -32,7 +31,7 @@ public final class ComponentNamePolicyValue extends PolicyValue<ComponentName> { public ComponentNamePolicyValue(@NonNull ComponentName value) { super(value); - if (devicePolicySizeTrackingEnabled()) { + if (Flags.devicePolicySizeTrackingInternalEnabled()) { PolicySizeVerifier.enforceMaxComponentNameLength(value); } } diff --git a/core/java/android/app/admin/IntentFilterPolicyKey.java b/core/java/android/app/admin/IntentFilterPolicyKey.java index de7ff9f0ad0f..7526a7b2c934 100644 --- a/core/java/android/app/admin/IntentFilterPolicyKey.java +++ b/core/java/android/app/admin/IntentFilterPolicyKey.java @@ -19,7 +19,6 @@ package android.app.admin; import static android.app.admin.PolicyUpdateReceiver.EXTRA_INTENT_FILTER; import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_BUNDLE_KEY; import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_KEY; -import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled; import android.annotation.NonNull; import android.annotation.Nullable; @@ -60,9 +59,6 @@ public final class IntentFilterPolicyKey extends PolicyKey { @TestApi public IntentFilterPolicyKey(@NonNull String identifier, @NonNull IntentFilter filter) { super(identifier); - if (devicePolicySizeTrackingEnabled()) { - PolicySizeVerifier.enforceMaxParcelableFieldsLength(filter); - } mFilter = Objects.requireNonNull(filter); } diff --git a/core/java/android/app/admin/LockTaskPolicy.java b/core/java/android/app/admin/LockTaskPolicy.java index 9d6ce243a19b..a36ea0508a95 100644 --- a/core/java/android/app/admin/LockTaskPolicy.java +++ b/core/java/android/app/admin/LockTaskPolicy.java @@ -16,11 +16,10 @@ package android.app.admin; -import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled; - import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.app.admin.flags.Flags; import android.os.Parcel; import android.os.Parcelable; @@ -136,7 +135,7 @@ public final class LockTaskPolicy extends PolicyValue<LockTaskPolicy> { } private void setPackagesInternal(Set<String> packages) { - if (devicePolicySizeTrackingEnabled()) { + if (Flags.devicePolicySizeTrackingInternalEnabled()) { for (String p : packages) { PolicySizeVerifier.enforceMaxPackageNameLength(p); } diff --git a/core/java/android/app/admin/PackagePermissionPolicyKey.java b/core/java/android/app/admin/PackagePermissionPolicyKey.java index 2241fddb7320..389585f036db 100644 --- a/core/java/android/app/admin/PackagePermissionPolicyKey.java +++ b/core/java/android/app/admin/PackagePermissionPolicyKey.java @@ -20,12 +20,12 @@ import static android.app.admin.PolicyUpdateReceiver.EXTRA_PACKAGE_NAME; import static android.app.admin.PolicyUpdateReceiver.EXTRA_PERMISSION_NAME; import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_BUNDLE_KEY; import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_KEY; -import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; +import android.app.admin.flags.Flags; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -59,7 +59,7 @@ public final class PackagePermissionPolicyKey extends PolicyKey { public PackagePermissionPolicyKey(@NonNull String identifier, @NonNull String packageName, @NonNull String permissionName) { super(identifier); - if (devicePolicySizeTrackingEnabled()) { + if (Flags.devicePolicySizeTrackingInternalEnabled()) { PolicySizeVerifier.enforceMaxPackageNameLength(packageName); PolicySizeVerifier.enforceMaxStringLength(permissionName, "permissionName"); } diff --git a/core/java/android/app/admin/PackagePolicyKey.java b/core/java/android/app/admin/PackagePolicyKey.java index 2ea17a18f6a6..68dc797f6513 100644 --- a/core/java/android/app/admin/PackagePolicyKey.java +++ b/core/java/android/app/admin/PackagePolicyKey.java @@ -19,12 +19,12 @@ package android.app.admin; import static android.app.admin.PolicyUpdateReceiver.EXTRA_PACKAGE_NAME; import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_BUNDLE_KEY; import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_KEY; -import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; +import android.app.admin.flags.Flags; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -55,7 +55,7 @@ public final class PackagePolicyKey extends PolicyKey { @TestApi public PackagePolicyKey(@NonNull String key, @NonNull String packageName) { super(key); - if (devicePolicySizeTrackingEnabled()) { + if (Flags.devicePolicySizeTrackingInternalEnabled()) { PolicySizeVerifier.enforceMaxPackageNameLength(packageName); } mPackageName = Objects.requireNonNull((packageName)); diff --git a/core/java/android/app/admin/PolicySizeVerifier.java b/core/java/android/app/admin/PolicySizeVerifier.java index 792ebc6ad297..7f8e50ec4420 100644 --- a/core/java/android/app/admin/PolicySizeVerifier.java +++ b/core/java/android/app/admin/PolicySizeVerifier.java @@ -17,12 +17,12 @@ package android.app.admin; import android.content.ComponentName; +import android.os.Bundle; import android.os.Parcelable; import android.os.PersistableBundle; import com.android.internal.util.Preconditions; -import java.lang.reflect.Field; import java.util.ArrayDeque; import java.util.Queue; @@ -71,44 +71,51 @@ public class PolicySizeVerifier { for (String key : current.keySet()) { enforceMaxStringLength(key, "key in " + argName); Object value = current.get(key); - if (value instanceof String) { - enforceMaxStringLength((String) value, "string value in " + argName); - } else if (value instanceof String[]) { - for (String str : (String[]) value) { + if (value instanceof String str) { + enforceMaxStringLength(str, "string value in " + argName); + } else if (value instanceof String[] strArray) { + for (String str : strArray) { enforceMaxStringLength(str, "string value in " + argName); } - } else if (value instanceof PersistableBundle) { - queue.add((PersistableBundle) value); + } else if (value instanceof PersistableBundle persistableBundle) { + queue.add(persistableBundle); } } } } /** - * Throw if Parcelable contains any string that's too long to be serialized. + * Throw if bundle contains any string that's too long to be serialized. This follows the + * serialization logic in BundlePolicySerializer#writeBundle. */ - public static void enforceMaxParcelableFieldsLength(Parcelable parcelable) { - // TODO(b/326662716) rework to protect against infinite recursion. - if (true) { - return; - } - Class<?> clazz = parcelable.getClass(); - - Field[] fields = clazz.getDeclaredFields(); - for (Field field : fields) { - field.setAccessible(true); - try { - Object value = field.get(parcelable); - if (value instanceof String) { - String stringValue = (String) value; - enforceMaxStringLength(stringValue, field.getName()); + public static void enforceMaxBundleFieldsLength(Bundle bundle) { + Queue<Bundle> queue = new ArrayDeque<>(); + queue.add(bundle); + while (!queue.isEmpty()) { + Bundle current = queue.remove(); + for (String key : current.keySet()) { + enforceMaxStringLength(key, "key in Bundle"); + Object value = current.get(key); + if (value instanceof String str) { + enforceMaxStringLength(str, "string value in Bundle with " + + "key" + key); + } else if (value instanceof String[] strArray) { + for (String str : strArray) { + enforceMaxStringLength(str, "string value in Bundle with" + + " key" + key); + } + } else if (value instanceof Bundle b) { + queue.add(b); } - - if (value instanceof Parcelable) { - enforceMaxParcelableFieldsLength((Parcelable) value); + else if (value instanceof Parcelable[] parcelableArray) { + for (Parcelable parcelable : parcelableArray) { + if (!(parcelable instanceof Bundle)) { + throw new IllegalArgumentException("bundle-array can only hold " + + "Bundles"); + } + queue.add((Bundle) parcelable); + } } - } catch (IllegalAccessException e) { - e.printStackTrace(); } } } diff --git a/core/java/android/app/admin/StringPolicyValue.java b/core/java/android/app/admin/StringPolicyValue.java index f4d4adcfcedb..8995c0f20de8 100644 --- a/core/java/android/app/admin/StringPolicyValue.java +++ b/core/java/android/app/admin/StringPolicyValue.java @@ -16,10 +16,9 @@ package android.app.admin; -import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled; - import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.admin.flags.Flags; import android.os.Parcel; import java.util.Objects; @@ -31,7 +30,7 @@ public final class StringPolicyValue extends PolicyValue<String> { public StringPolicyValue(@NonNull String value) { super(value); - if (devicePolicySizeTrackingEnabled()) { + if (Flags.devicePolicySizeTrackingInternalEnabled()) { PolicySizeVerifier.enforceMaxStringLength(value, "policyValue"); } } diff --git a/core/java/android/app/admin/StringSetPolicyValue.java b/core/java/android/app/admin/StringSetPolicyValue.java index 82fe761a414f..f37dfee0f9dc 100644 --- a/core/java/android/app/admin/StringSetPolicyValue.java +++ b/core/java/android/app/admin/StringSetPolicyValue.java @@ -16,10 +16,9 @@ package android.app.admin; -import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled; - import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.admin.flags.Flags; import android.os.Parcel; import java.util.HashSet; @@ -33,7 +32,7 @@ public final class StringSetPolicyValue extends PolicyValue<Set<String>> { public StringSetPolicyValue(@NonNull Set<String> value) { super(value); - if (devicePolicySizeTrackingEnabled()) { + if (Flags.devicePolicySizeTrackingInternalEnabled()) { for (String str : value) { PolicySizeVerifier.enforceMaxStringLength(str, "policyValue"); } diff --git a/core/java/android/app/admin/UserRestrictionPolicyKey.java b/core/java/android/app/admin/UserRestrictionPolicyKey.java index d69a5f08ce2e..ee90ccd9417f 100644 --- a/core/java/android/app/admin/UserRestrictionPolicyKey.java +++ b/core/java/android/app/admin/UserRestrictionPolicyKey.java @@ -17,11 +17,11 @@ package android.app.admin; import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_KEY; -import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled; import android.annotation.NonNull; import android.annotation.SystemApi; import android.annotation.TestApi; +import android.app.admin.flags.Flags; import android.os.Bundle; import android.os.Parcel; @@ -45,7 +45,7 @@ public final class UserRestrictionPolicyKey extends PolicyKey { @TestApi public UserRestrictionPolicyKey(@NonNull String identifier, @NonNull String restriction) { super(identifier); - if (devicePolicySizeTrackingEnabled()) { + if (Flags.devicePolicySizeTrackingInternalEnabled()) { PolicySizeVerifier.enforceMaxStringLength(restriction, "restriction"); } mRestriction = Objects.requireNonNull(restriction); diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index e1a69139d88d..19270199696e 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -10,7 +10,14 @@ flag { flag { name: "device_policy_size_tracking_enabled" namespace: "enterprise" - description: "Add feature to track the total policy size and have a max threshold." + description: "Add feature to track the total policy size and have a max threshold - public API changes" + bug: "281543351" +} + +flag { + name: "device_policy_size_tracking_internal_enabled" + namespace: "enterprise" + description: "Add feature to track the total policy size and have a max threshold - internal changes" bug: "281543351" } diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java index 79696e047904..48081bb04863 100644 --- a/core/java/android/app/servertransaction/ClientTransaction.java +++ b/core/java/android/app/servertransaction/ClientTransaction.java @@ -23,6 +23,7 @@ import android.annotation.Nullable; import android.app.ClientTransactionHandler; import android.app.IApplicationThread; import android.compat.annotation.UnsupportedAppUsage; +import android.os.Build; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; @@ -54,10 +55,12 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { @Nullable private List<ClientTransactionItem> mTransactionItems; - /** A list of individual callbacks to a client. */ - // TODO(b/324203798): cleanup after remove UnsupportedAppUsage - @UnsupportedAppUsage + /** @deprecated use {@link #getTransactionItems} instead. */ @Nullable + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, + trackingBug = 324203798, + publicAlternatives = "Use {@code #getTransactionItems()}") + @Deprecated private List<ClientTransactionItem> mActivityCallbacks; /** @@ -126,42 +129,42 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { setActivityTokenIfNotSet(activityCallback); } - /** - * Gets the list of callbacks. - * @deprecated use {@link #getTransactionItems()} instead. - */ - // TODO(b/324203798): cleanup after remove UnsupportedAppUsage - @Nullable + /** @deprecated use {@link #getTransactionItems()} instead. */ @VisibleForTesting - @UnsupportedAppUsage + @Nullable + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, + trackingBug = 324203798, + publicAlternatives = "Use {@code #getTransactionItems()}") @Deprecated public List<ClientTransactionItem> getCallbacks() { return mActivityCallbacks; } /** - * @deprecated a transaction can contain {@link ClientTransactionItem} of different activities, + * A transaction can contain {@link ClientTransactionItem} of different activities, * this must not be used. For any unsupported app usages, please be aware that this is set to * the activity of the first item in {@link #getTransactionItems()}. + * + * @deprecated use {@link ClientTransactionItem#getActivityToken()} instead. */ - // TODO(b/324203798): cleanup after remove UnsupportedAppUsage @VisibleForTesting @Nullable - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, + trackingBug = 324203798, + publicAlternatives = "Use {@code android.app.servertransaction" + + ".ClientTransactionItem#getActivityToken()}") @Deprecated public IBinder getActivityToken() { return mActivityToken; } - /** - * Gets the target state lifecycle request. - * @deprecated use {@link #getTransactionItems()} instead. - */ - // TODO(b/324203798): cleanup after remove UnsupportedAppUsage + /** @deprecated use {@link #getTransactionItems()} instead. */ @VisibleForTesting(visibility = PACKAGE) - @UnsupportedAppUsage - @Deprecated @Nullable + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, + trackingBug = 324203798, + publicAlternatives = "Use {@code #getTransactionItems()}") + @Deprecated public ActivityLifecycleItem getLifecycleStateRequest() { return mLifecycleStateRequest; } diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java index 1a8136e06c28..7383d07c82e9 100644 --- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java +++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java @@ -16,16 +16,24 @@ package android.app.servertransaction; +import static com.android.window.flags.Flags.activityWindowInfoFlag; import static com.android.window.flags.Flags.bundleClientTransactionFlag; import static java.util.Objects.requireNonNull; import android.annotation.NonNull; +import android.app.Activity; import android.app.ActivityThread; import android.hardware.display.DisplayManagerGlobal; +import android.os.IBinder; +import android.util.ArraySet; +import android.window.ActivityWindowInfo; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import java.util.function.BiConsumer; + /** * Singleton controller to manage listeners to individual {@link ClientTransaction}. * @@ -35,8 +43,14 @@ public class ClientTransactionListenerController { private static ClientTransactionListenerController sController; + private final Object mLock = new Object(); private final DisplayManagerGlobal mDisplayManager; + /** Listeners registered via {@link #registerActivityWindowInfoChangedListener(BiConsumer)}. */ + @GuardedBy("mLock") + private final ArraySet<BiConsumer<IBinder, ActivityWindowInfo>> + mActivityWindowInfoChangedListeners = new ArraySet<>(); + /** Gets the singleton controller. */ @NonNull public static ClientTransactionListenerController getInstance() { @@ -62,6 +76,57 @@ public class ClientTransactionListenerController { } /** + * Registers to listen on activity {@link ActivityWindowInfo} change. + * The listener will be invoked with two parameters: {@link Activity#getActivityToken()} and + * {@link ActivityWindowInfo}. + */ + public void registerActivityWindowInfoChangedListener( + @NonNull BiConsumer<IBinder, ActivityWindowInfo> listener) { + if (!activityWindowInfoFlag()) { + return; + } + synchronized (mLock) { + mActivityWindowInfoChangedListeners.add(listener); + } + } + + /** + * Unregisters the listener that was previously registered via + * {@link #registerActivityWindowInfoChangedListener(BiConsumer)} + */ + public void unregisterActivityWindowInfoChangedListener( + @NonNull BiConsumer<IBinder, ActivityWindowInfo> listener) { + if (!activityWindowInfoFlag()) { + return; + } + synchronized (mLock) { + mActivityWindowInfoChangedListeners.remove(listener); + } + } + + /** + * Called when receives a {@link ClientTransaction} that is updating an activity's + * {@link ActivityWindowInfo}. + */ + public void onActivityWindowInfoChanged(@NonNull IBinder activityToken, + @NonNull ActivityWindowInfo activityWindowInfo) { + if (!activityWindowInfoFlag()) { + return; + } + final Object[] activityWindowInfoChangedListeners; + synchronized (mLock) { + if (mActivityWindowInfoChangedListeners.isEmpty()) { + return; + } + activityWindowInfoChangedListeners = mActivityWindowInfoChangedListeners.toArray(); + } + for (Object activityWindowInfoChangedListener : activityWindowInfoChangedListeners) { + ((BiConsumer<IBinder, ActivityWindowInfo>) activityWindowInfoChangedListener) + .accept(activityToken, activityWindowInfo); + } + } + + /** * Called when receives a {@link ClientTransaction} that is updating display-related * window configuration. */ diff --git a/core/java/android/app/servertransaction/DestroyActivityItem.java b/core/java/android/app/servertransaction/DestroyActivityItem.java index f9cf075d6062..b0213d7356df 100644 --- a/core/java/android/app/servertransaction/DestroyActivityItem.java +++ b/core/java/android/app/servertransaction/DestroyActivityItem.java @@ -33,7 +33,6 @@ import android.os.Trace; public class DestroyActivityItem extends ActivityLifecycleItem { private boolean mFinished; - private int mConfigChanges; @Override public void preExecute(@NonNull ClientTransactionHandler client) { @@ -44,7 +43,7 @@ public class DestroyActivityItem extends ActivityLifecycleItem { public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r, @NonNull PendingTransactionActions pendingActions) { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy"); - client.handleDestroyActivity(r, mFinished, mConfigChanges, + client.handleDestroyActivity(r, mFinished, false /* getNonConfigInstance */, "DestroyActivityItem"); Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } @@ -67,15 +66,13 @@ public class DestroyActivityItem extends ActivityLifecycleItem { /** Obtain an instance initialized with provided params. */ @NonNull - public static DestroyActivityItem obtain(@NonNull IBinder activityToken, boolean finished, - int configChanges) { + public static DestroyActivityItem obtain(@NonNull IBinder activityToken, boolean finished) { DestroyActivityItem instance = ObjectPool.obtain(DestroyActivityItem.class); if (instance == null) { instance = new DestroyActivityItem(); } instance.setActivityToken(activityToken); instance.mFinished = finished; - instance.mConfigChanges = configChanges; return instance; } @@ -84,7 +81,6 @@ public class DestroyActivityItem extends ActivityLifecycleItem { public void recycle() { super.recycle(); mFinished = false; - mConfigChanges = 0; ObjectPool.recycle(this); } @@ -95,14 +91,12 @@ public class DestroyActivityItem extends ActivityLifecycleItem { public void writeToParcel(@NonNull Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeBoolean(mFinished); - dest.writeInt(mConfigChanges); } /** Read from Parcel. */ private DestroyActivityItem(@NonNull Parcel in) { super(in); mFinished = in.readBoolean(); - mConfigChanges = in.readInt(); } public static final @NonNull Creator<DestroyActivityItem> CREATOR = new Creator<>() { @@ -124,7 +118,7 @@ public class DestroyActivityItem extends ActivityLifecycleItem { return false; } final DestroyActivityItem other = (DestroyActivityItem) o; - return mFinished == other.mFinished && mConfigChanges == other.mConfigChanges; + return mFinished == other.mFinished; } @Override @@ -132,14 +126,12 @@ public class DestroyActivityItem extends ActivityLifecycleItem { int result = 17; result = 31 * result + super.hashCode(); result = 31 * result + (mFinished ? 1 : 0); - result = 31 * result + mConfigChanges; return result; } @Override public String toString() { return "DestroyActivityItem{" + super.toString() - + ",finished=" + mFinished - + ",mConfigChanges=" + mConfigChanges + "}"; + + ",finished=" + mFinished + "}"; } } diff --git a/core/java/android/app/servertransaction/PauseActivityItem.java b/core/java/android/app/servertransaction/PauseActivityItem.java index 8f1e90b985e6..d230284287b6 100644 --- a/core/java/android/app/servertransaction/PauseActivityItem.java +++ b/core/java/android/app/servertransaction/PauseActivityItem.java @@ -37,7 +37,6 @@ public class PauseActivityItem extends ActivityLifecycleItem { private boolean mFinished; private boolean mUserLeaving; - private int mConfigChanges; private boolean mDontReport; private boolean mAutoEnteringPip; @@ -45,7 +44,7 @@ public class PauseActivityItem extends ActivityLifecycleItem { public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r, @NonNull PendingTransactionActions pendingActions) { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityPause"); - client.handlePauseActivity(r, mFinished, mUserLeaving, mConfigChanges, mAutoEnteringPip, + client.handlePauseActivity(r, mFinished, mUserLeaving, mAutoEnteringPip, pendingActions, "PAUSE_ACTIVITY_ITEM"); Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } @@ -72,7 +71,7 @@ public class PauseActivityItem extends ActivityLifecycleItem { /** Obtain an instance initialized with provided params. */ @NonNull public static PauseActivityItem obtain(@NonNull IBinder activityToken, boolean finished, - boolean userLeaving, int configChanges, boolean dontReport, boolean autoEnteringPip) { + boolean userLeaving, boolean dontReport, boolean autoEnteringPip) { PauseActivityItem instance = ObjectPool.obtain(PauseActivityItem.class); if (instance == null) { instance = new PauseActivityItem(); @@ -80,7 +79,6 @@ public class PauseActivityItem extends ActivityLifecycleItem { instance.setActivityToken(activityToken); instance.mFinished = finished; instance.mUserLeaving = userLeaving; - instance.mConfigChanges = configChanges; instance.mDontReport = dontReport; instance.mAutoEnteringPip = autoEnteringPip; @@ -91,7 +89,7 @@ public class PauseActivityItem extends ActivityLifecycleItem { @NonNull public static PauseActivityItem obtain(@NonNull IBinder activityToken) { return obtain(activityToken, false /* finished */, false /* userLeaving */, - 0 /* configChanges */, true /* dontReport */, false /* autoEnteringPip*/); + true /* dontReport */, false /* autoEnteringPip*/); } @Override @@ -99,7 +97,6 @@ public class PauseActivityItem extends ActivityLifecycleItem { super.recycle(); mFinished = false; mUserLeaving = false; - mConfigChanges = 0; mDontReport = false; mAutoEnteringPip = false; ObjectPool.recycle(this); @@ -113,7 +110,6 @@ public class PauseActivityItem extends ActivityLifecycleItem { super.writeToParcel(dest, flags); dest.writeBoolean(mFinished); dest.writeBoolean(mUserLeaving); - dest.writeInt(mConfigChanges); dest.writeBoolean(mDontReport); dest.writeBoolean(mAutoEnteringPip); } @@ -123,7 +119,6 @@ public class PauseActivityItem extends ActivityLifecycleItem { super(in); mFinished = in.readBoolean(); mUserLeaving = in.readBoolean(); - mConfigChanges = in.readInt(); mDontReport = in.readBoolean(); mAutoEnteringPip = in.readBoolean(); } @@ -148,7 +143,7 @@ public class PauseActivityItem extends ActivityLifecycleItem { } final PauseActivityItem other = (PauseActivityItem) o; return mFinished == other.mFinished && mUserLeaving == other.mUserLeaving - && mConfigChanges == other.mConfigChanges && mDontReport == other.mDontReport + && mDontReport == other.mDontReport && mAutoEnteringPip == other.mAutoEnteringPip; } @@ -158,7 +153,6 @@ public class PauseActivityItem extends ActivityLifecycleItem { result = 31 * result + super.hashCode(); result = 31 * result + (mFinished ? 1 : 0); result = 31 * result + (mUserLeaving ? 1 : 0); - result = 31 * result + mConfigChanges; result = 31 * result + (mDontReport ? 1 : 0); result = 31 * result + (mAutoEnteringPip ? 1 : 0); return result; @@ -169,7 +163,6 @@ public class PauseActivityItem extends ActivityLifecycleItem { return "PauseActivityItem{" + super.toString() + ",finished=" + mFinished + ",userLeaving=" + mUserLeaving - + ",configChanges=" + mConfigChanges + ",dontReport=" + mDontReport + ",autoEnteringPip=" + mAutoEnteringPip + "}"; } diff --git a/core/java/android/app/servertransaction/StopActivityItem.java b/core/java/android/app/servertransaction/StopActivityItem.java index b8ce52da5a0c..def7b3fd9987 100644 --- a/core/java/android/app/servertransaction/StopActivityItem.java +++ b/core/java/android/app/servertransaction/StopActivityItem.java @@ -19,7 +19,6 @@ package android.app.servertransaction; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; import android.annotation.NonNull; -import android.annotation.Nullable; import android.app.ActivityThread.ActivityClientRecord; import android.app.ClientTransactionHandler; import android.os.IBinder; @@ -34,13 +33,11 @@ public class StopActivityItem extends ActivityLifecycleItem { private static final String TAG = "StopActivityItem"; - private int mConfigChanges; - @Override public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r, @NonNull PendingTransactionActions pendingActions) { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStop"); - client.handleStopActivity(r, mConfigChanges, pendingActions, + client.handleStopActivity(r, pendingActions, true /* finalStateRequest */, "STOP_ACTIVITY_ITEM"); Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } @@ -63,16 +60,14 @@ public class StopActivityItem extends ActivityLifecycleItem { /** * Obtain an instance initialized with provided params. * @param activityToken the activity that stops. - * @param configChanges Configuration pieces that changed. */ @NonNull - public static StopActivityItem obtain(@NonNull IBinder activityToken, int configChanges) { + public static StopActivityItem obtain(@NonNull IBinder activityToken) { StopActivityItem instance = ObjectPool.obtain(StopActivityItem.class); if (instance == null) { instance = new StopActivityItem(); } instance.setActivityToken(activityToken); - instance.mConfigChanges = configChanges; return instance; } @@ -80,23 +75,14 @@ public class StopActivityItem extends ActivityLifecycleItem { @Override public void recycle() { super.recycle(); - mConfigChanges = 0; ObjectPool.recycle(this); } // Parcelable implementation - /** Write to Parcel. */ - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - super.writeToParcel(dest, flags); - dest.writeInt(mConfigChanges); - } - /** Read from Parcel. */ private StopActivityItem(@NonNull Parcel in) { super(in); - mConfigChanges = in.readInt(); } public static final @NonNull Creator<StopActivityItem> CREATOR = new Creator<>() { @@ -110,28 +96,7 @@ public class StopActivityItem extends ActivityLifecycleItem { }; @Override - public boolean equals(@Nullable Object o) { - if (this == o) { - return true; - } - if (!super.equals(o)) { - return false; - } - final StopActivityItem other = (StopActivityItem) o; - return mConfigChanges == other.mConfigChanges; - } - - @Override - public int hashCode() { - int result = 17; - result = 31 * result + super.hashCode(); - result = 31 * result + mConfigChanges; - return result; - } - - @Override public String toString() { - return "StopActivityItem{" + super.toString() - + ",configChanges=" + mConfigChanges + "}"; + return "StopActivityItem{" + super.toString() + "}"; } } diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java index fa73c99be2b8..c83719149821 100644 --- a/core/java/android/app/servertransaction/TransactionExecutor.java +++ b/core/java/android/app/servertransaction/TransactionExecutor.java @@ -334,18 +334,18 @@ public class TransactionExecutor { break; case ON_PAUSE: mTransactionHandler.handlePauseActivity(r, false /* finished */, - false /* userLeaving */, 0 /* configChanges */, + false /* userLeaving */, false /* autoEnteringPip */, mPendingActions, "LIFECYCLER_PAUSE_ACTIVITY"); break; case ON_STOP: - mTransactionHandler.handleStopActivity(r, 0 /* configChanges */, + mTransactionHandler.handleStopActivity(r, mPendingActions, false /* finalStateRequest */, "LIFECYCLER_STOP_ACTIVITY"); break; case ON_DESTROY: mTransactionHandler.handleDestroyActivity(r, false /* finishing */, - 0 /* configChanges */, false /* getNonConfigInstance */, + false /* getNonConfigInstance */, "performLifecycleSequence. cycling to:" + path.get(size - 1)); break; case ON_RESTART: diff --git a/core/java/android/app/servertransaction/TransactionExecutorHelper.java b/core/java/android/app/servertransaction/TransactionExecutorHelper.java index 475c6fb9a48a..710261ab4c97 100644 --- a/core/java/android/app/servertransaction/TransactionExecutorHelper.java +++ b/core/java/android/app/servertransaction/TransactionExecutorHelper.java @@ -200,7 +200,7 @@ public class TransactionExecutorHelper { lifecycleItem = PauseActivityItem.obtain(r.token); break; case ON_STOP: - lifecycleItem = StopActivityItem.obtain(r.token, 0 /* configChanges */); + lifecycleItem = StopActivityItem.obtain(r.token); break; default: lifecycleItem = ResumeActivityItem.obtain(r.token, false /* isForward */, diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java index 39f6de75928a..00d534370fa1 100644 --- a/core/java/android/companion/virtual/VirtualDeviceInternal.java +++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java @@ -16,6 +16,9 @@ package android.companion.virtual; +import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM; +import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO; + import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; @@ -352,12 +355,20 @@ public class VirtualDeviceInternal { @Nullable Executor executor, @Nullable VirtualAudioDevice.AudioConfigurationChangeCallback callback) { if (mVirtualAudioDevice == null) { - Context context = mContext; - if (Flags.deviceAwareRecordAudioPermission()) { - context = mContext.createDeviceContext(getDeviceId()); + try { + Context context = mContext; + if (Flags.deviceAwareRecordAudioPermission()) { + // When using a default policy for audio device-aware RECORD_AUDIO permission + // should not take effect, thus register policies with the default context. + if (mVirtualDevice.getDevicePolicy(POLICY_TYPE_AUDIO) == DEVICE_POLICY_CUSTOM) { + context = mContext.createDeviceContext(getDeviceId()); + } + } + mVirtualAudioDevice = new VirtualAudioDevice(context, mVirtualDevice, display, + executor, callback, () -> mVirtualAudioDevice = null); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); } - mVirtualAudioDevice = new VirtualAudioDevice(context, mVirtualDevice, display, - executor, callback, () -> mVirtualAudioDevice = null); } return mVirtualAudioDevice; } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index fd2af99b6a7e..42dd87a711f3 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1064,7 +1064,11 @@ public class Intent implements Parcelable, Cloneable { } if (sender != null) { - intent.putExtra(EXTRA_CHOOSER_RESULT_INTENT_SENDER, sender); + if (android.service.chooser.Flags.enableChooserResult()) { + intent.putExtra(EXTRA_CHOOSER_RESULT_INTENT_SENDER, sender); + } else { + intent.putExtra(EXTRA_CHOSEN_COMPONENT_INTENT_SENDER, sender); + } } // Migrate any clip data and flags from target. diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java index cdda12eebdc4..3f941dad2c3f 100644 --- a/core/java/android/content/pm/PermissionInfo.java +++ b/core/java/android/content/pm/PermissionInfo.java @@ -273,6 +273,10 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { * to the <code>retailDemo</code> value of * {@link android.R.attr#protectionLevel}. * + * @deprecated This flag has been replaced by the + * {@link android.R.string#config_defaultRetailDemo retail demo role} and is a + * no-op since {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}. + * * @hide */ @SystemApi diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java index 3e9f260566bd..8a3a3ad56a7b 100644 --- a/core/java/android/content/pm/UserInfo.java +++ b/core/java/android/content/pm/UserInfo.java @@ -112,6 +112,12 @@ public class UserInfo implements Parcelable { /** * Indicates that this user is disabled. * + * <p> This is currently used to indicate that a Managed Profile, when created via + * DevicePolicyManager, has not yet been provisioned; once the DPC provisions it, a DPM call + * will manually set it to enabled. + * + * <p>Users that are slated for deletion are also generally set to disabled. + * * <p>Note: If an ephemeral user is disabled, it shouldn't be later re-enabled. Ephemeral users * are disabled as their removal is in progress to indicate that they shouldn't be re-entered. */ @@ -398,6 +404,7 @@ public class UserInfo implements Parcelable { return UserManager.isUserTypePrivateProfile(userType); } + /** See {@link #FLAG_DISABLED}*/ @UnsupportedAppUsage public boolean isEnabled() { return (flags & FLAG_DISABLED) != FLAG_DISABLED; diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index 5e9d8f0a9b7e..610057bffdbf 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -208,6 +208,14 @@ flag { } flag { + name: "restrict_nonpreloads_system_shareduids" + namespace: "package_manager_service" + description: "Feature flag to restrict apps from joining system shared uids" + bug: "308573169" + is_fixed_read_only: true +} + +flag { name: "min_target_sdk_24" namespace: "responsible_apis" description: "Feature flag to bump min target sdk to 24" diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index d259e9755a41..273e40a21bb2 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -471,6 +471,16 @@ public final class AssetManager implements AutoCloseable { return addAssetPathInternal(path, true /*overlay*/, false /*appAsLib*/); } + /** + * @hide + */ + public void addSharedLibraryPaths(@NonNull String[] paths) { + final int length = paths.length; + for (int i = 0; i < length; i++) { + addAssetPathInternal(paths[i], false, true); + } + } + private int addAssetPathInternal(String path, boolean overlay, boolean appAsLib) { Objects.requireNonNull(path, "path"); synchronized (this) { diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 7fba3e890ec6..1f5f88f51d55 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -43,6 +43,7 @@ import android.annotation.StyleRes; import android.annotation.StyleableRes; import android.annotation.XmlRes; import android.app.Application; +import android.app.ResourcesManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.pm.ActivityInfo; @@ -2854,6 +2855,11 @@ public class Resources { @FlaggedApi(android.content.res.Flags.FLAG_REGISTER_RESOURCE_PATHS) public static void registerResourcePaths(@NonNull String uniqueId, @NonNull ApplicationInfo appInfo) { - throw new UnsupportedOperationException("The implementation has not been done yet."); + if (Flags.registerResourcePaths()) { + ResourcesManager.getInstance().registerResourcePaths(uniqueId, appInfo); + } else { + throw new UnsupportedOperationException("Flag " + Flags.FLAG_REGISTER_RESOURCE_PATHS + + " is disabled."); + } } } diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index 079c2c1ab7c9..8d045aaf4d81 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -29,6 +29,7 @@ import android.annotation.StyleRes; import android.annotation.StyleableRes; import android.app.LocaleConfig; import android.app.ResourcesManager; +import android.app.ResourcesManager.SharedLibraryAssets; import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo.Config; @@ -47,6 +48,7 @@ import android.os.Build; import android.os.LocaleList; import android.os.ParcelFileDescriptor; import android.os.Trace; +import android.util.ArrayMap; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; @@ -197,6 +199,14 @@ public class ResourcesImpl { public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics, @Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) { mAssets = assets; + if (Flags.registerResourcePaths()) { + ArrayMap<String, SharedLibraryAssets> sharedLibMap = + ResourcesManager.getInstance().getSharedLibAssetsMap(); + final int size = sharedLibMap.size(); + for (int i = 0; i < size; i++) { + assets.addSharedLibraryPaths(sharedLibMap.valueAt(i).getAllAssetPaths()); + } + } mMetrics.setToDefaults(); mDisplayAdjustments = displayAdjustments; mConfiguration.setToDefaults(); diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java index 2e63664df7aa..3a9a0f911660 100644 --- a/core/java/android/credentials/CredentialManager.java +++ b/core/java/android/credentials/CredentialManager.java @@ -23,6 +23,7 @@ import android.annotation.Hide; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.annotation.TestApi; @@ -31,6 +32,7 @@ import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.IntentSender; +import android.content.pm.PackageManager; import android.os.Binder; import android.os.Bundle; import android.os.CancellationSignal; @@ -57,6 +59,7 @@ import java.util.concurrent.Executor; * to authenticate to the app. */ @SystemService(Context.CREDENTIAL_SERVICE) +@RequiresFeature(PackageManager.FEATURE_CREDENTIALS) public final class CredentialManager { private static final String TAG = "CredentialManager"; private static final Bundle OPTIONS_SENDER_BAL_OPTIN = ActivityOptions.makeBasic() diff --git a/core/java/android/credentials/selection/IntentFactory.java b/core/java/android/credentials/selection/IntentFactory.java index 4b0fa6d32af7..79fba9b19250 100644 --- a/core/java/android/credentials/selection/IntentFactory.java +++ b/core/java/android/credentials/selection/IntentFactory.java @@ -80,17 +80,7 @@ public class IntentFactory { ArrayList<DisabledProviderData> disabledProviderDataList, @NonNull ResultReceiver resultReceiver) { Intent intent = new Intent(); - ComponentName componentName = - ComponentName.unflattenFromString( - Resources.getSystem() - .getString( - com.android.internal.R.string - .config_credentialManagerDialogComponent)); - ComponentName oemOverrideComponentName = getOemOverrideComponentName(context); - if (oemOverrideComponentName != null) { - componentName = oemOverrideComponentName; - } - intent.setComponent(componentName); + setCredentialSelectorUiComponentName(context, intent); intent.putParcelableArrayListExtra( ProviderData.EXTRA_DISABLED_PROVIDER_DATA_LIST, disabledProviderDataList); intent.putExtra(RequestInfo.EXTRA_REQUEST_INFO, requestInfo); @@ -100,6 +90,24 @@ public class IntentFactory { return intent; } + private static void setCredentialSelectorUiComponentName(@NonNull Context context, + @NonNull Intent intent) { + if (configurableSelectorUiEnabled()) { + ComponentName componentName = getOemOverrideComponentName(context); + if (componentName == null) { + componentName = ComponentName.unflattenFromString(Resources.getSystem().getString( + com.android.internal.R.string + .config_fallbackCredentialManagerDialogComponent)); + } + intent.setComponent(componentName); + } else { + ComponentName componentName = ComponentName.unflattenFromString(Resources.getSystem() + .getString(com.android.internal.R.string + .config_fallbackCredentialManagerDialogComponent)); + intent.setComponent(componentName); + } + } + /** * Returns null if there is not an enabled and valid oem override component. It means the * default platform UI component name should be used instead. @@ -107,44 +115,39 @@ public class IntentFactory { @Nullable private static ComponentName getOemOverrideComponentName(@NonNull Context context) { ComponentName result = null; - if (configurableSelectorUiEnabled()) { - if (Resources.getSystem().getBoolean( - com.android.internal.R.bool.config_enableOemCredentialManagerDialogComponent)) { - String oemComponentString = - Resources.getSystem() - .getString( - com.android.internal.R.string - .config_oemCredentialManagerDialogComponent); - if (!TextUtils.isEmpty(oemComponentString)) { - ComponentName oemComponentName = ComponentName.unflattenFromString( - oemComponentString); - if (oemComponentName != null) { - try { - ActivityInfo info = context.getPackageManager().getActivityInfo( - oemComponentName, - PackageManager.ComponentInfoFlags.of( - PackageManager.MATCH_SYSTEM_ONLY)); - if (info.enabled && info.exported) { - Slog.i(TAG, - "Found enabled oem CredMan UI component." - + oemComponentString); - result = oemComponentName; - } else { - Slog.i(TAG, - "Found enabled oem CredMan UI component but it was not " - + "enabled."); - } - } catch (PackageManager.NameNotFoundException e) { - Slog.i(TAG, "Unable to find oem CredMan UI component: " - + oemComponentString + "."); - } + String oemComponentString = + Resources.getSystem() + .getString( + com.android.internal.R.string + .config_oemCredentialManagerDialogComponent); + if (!TextUtils.isEmpty(oemComponentString)) { + ComponentName oemComponentName = ComponentName.unflattenFromString( + oemComponentString); + if (oemComponentName != null) { + try { + ActivityInfo info = context.getPackageManager().getActivityInfo( + oemComponentName, + PackageManager.ComponentInfoFlags.of( + PackageManager.MATCH_SYSTEM_ONLY)); + if (info.enabled && info.exported) { + Slog.i(TAG, + "Found enabled oem CredMan UI component." + + oemComponentString); + result = oemComponentName; } else { - Slog.i(TAG, "Invalid OEM ComponentName format."); + Slog.i(TAG, + "Found enabled oem CredMan UI component but it was not " + + "enabled."); } - } else { - Slog.i(TAG, "Invalid empty OEM component name."); + } catch (PackageManager.NameNotFoundException e) { + Slog.i(TAG, "Unable to find oem CredMan UI component: " + + oemComponentString + "."); } + } else { + Slog.i(TAG, "Invalid OEM ComponentName format."); } + } else { + Slog.i(TAG, "Invalid empty OEM component name."); } return result; } @@ -186,16 +189,11 @@ public class IntentFactory { * Creates an Intent that cancels any UI matching the given request token id. */ @NonNull - public static Intent createCancelUiIntent(@NonNull IBinder requestToken, - boolean shouldShowCancellationUi, @NonNull String appPackageName) { + public static Intent createCancelUiIntent(@NonNull Context context, + @NonNull IBinder requestToken, boolean shouldShowCancellationUi, + @NonNull String appPackageName) { Intent intent = new Intent(); - ComponentName componentName = - ComponentName.unflattenFromString( - Resources.getSystem() - .getString( - com.android.internal.R.string - .config_credentialManagerDialogComponent)); - intent.setComponent(componentName); + setCredentialSelectorUiComponentName(context, intent); intent.putExtra(CancelSelectionRequest.EXTRA_CANCEL_UI_REQUEST, new CancelSelectionRequest(new RequestToken(requestToken), shouldShowCancellationUi, appPackageName)); diff --git a/core/java/android/hardware/ISensorPrivacyManager.aidl b/core/java/android/hardware/ISensorPrivacyManager.aidl index 851ce2add94f..19d10294a75a 100644 --- a/core/java/android/hardware/ISensorPrivacyManager.aidl +++ b/core/java/android/hardware/ISensorPrivacyManager.aidl @@ -16,7 +16,6 @@ package android.hardware; -import android.hardware.CameraPrivacyAllowlistEntry; import android.hardware.ISensorPrivacyListener; /** @hide */ @@ -48,7 +47,7 @@ interface ISensorPrivacyManager { void setToggleSensorPrivacyForProfileGroup(int userId, int source, int sensor, boolean enable); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY)") - List<CameraPrivacyAllowlistEntry> getCameraPrivacyAllowlist(); + List<String> getCameraPrivacyAllowlist(); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY)") int getToggleSensorPrivacyState(int toggleType, int sensor); @@ -62,6 +61,10 @@ interface ISensorPrivacyManager { @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY)") boolean isCameraPrivacyEnabled(String packageName); + /** @hide */ + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY)") + void setCameraPrivacyAllowlist(in List<String> allowlist); + // =============== End of transactions used on native side as well ============================ void suppressToggleSensorPrivacyReminders(int userId, int sensor, IBinder token, diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java index 6294a8d617de..08b906439b08 100644 --- a/core/java/android/hardware/SensorPrivacyManager.java +++ b/core/java/android/hardware/SensorPrivacyManager.java @@ -43,7 +43,7 @@ import com.android.internal.camera.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.Map; +import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; @@ -204,6 +204,8 @@ public final class SensorPrivacyManager { * Types of state which can exist for the sensor privacy toggle * @hide */ + @SystemApi + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) public static class StateTypes { private StateTypes() {} @@ -217,30 +219,12 @@ public final class SensorPrivacyManager { */ public static final int DISABLED = SensorPrivacyIndividualEnabledSensorProto.DISABLED; - /** - * Constant indicating privacy is enabled except for the automotive driver assistance apps - * which are helpful for driving. - */ - @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) - public static final int AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS = - SensorPrivacyIndividualEnabledSensorProto.AUTO_DRIVER_ASSISTANCE_HELPFUL_APPS; - /** * Constant indicating privacy is enabled except for the automotive driver assistance apps * which are required by car manufacturer for driving. */ - @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) - public static final int AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS = - SensorPrivacyIndividualEnabledSensorProto.AUTO_DRIVER_ASSISTANCE_REQUIRED_APPS; - - /** - * Constant indicating privacy is enabled except for the automotive driver assistance apps - * which are both helpful for driving and also apps required by car manufacturer for - * driving. - */ - @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) - public static final int AUTOMOTIVE_DRIVER_ASSISTANCE_APPS = - SensorPrivacyIndividualEnabledSensorProto.AUTO_DRIVER_ASSISTANCE_APPS; + public static final int ENABLED_EXCEPT_ALLOWLISTED_APPS = + SensorPrivacyIndividualEnabledSensorProto.ENABLED_EXCEPT_ALLOWLISTED_APPS; /** * Types of state which can exist for a sensor privacy toggle @@ -250,9 +234,7 @@ public final class SensorPrivacyManager { @IntDef(value = { ENABLED, DISABLED, - AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS, - AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS, - AUTOMOTIVE_DRIVER_ASSISTANCE_APPS + ENABLED_EXCEPT_ALLOWLISTED_APPS }) @Retention(RetentionPolicy.SOURCE) public @interface StateType {} @@ -369,9 +351,6 @@ public final class SensorPrivacyManager { private final ArrayMap<Pair<Integer, OnSensorPrivacyChangedListener>, OnSensorPrivacyChangedListener> mLegacyToggleListeners = new ArrayMap<>(); - @GuardedBy("mLock") - private ArrayMap<String, Boolean> mCameraPrivacyAllowlist = null; - /** The singleton ISensorPrivacyListener for IPC which will be used to dispatch to local * listeners */ @NonNull @@ -397,7 +376,8 @@ public final class SensorPrivacyManager { @Override @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) - public void onSensorPrivacyStateChanged(int toggleType, int sensor, int state) { + public void onSensorPrivacyStateChanged(@ToggleType int toggleType, + @Sensors.Sensor int sensor, @StateTypes.StateType int state) { synchronized (mLock) { for (int i = 0; i < mToggleListeners.size(); i++) { OnSensorPrivacyChangedListener listener = mToggleListeners.keyAt(i); @@ -725,6 +705,8 @@ public final class SensorPrivacyManager { /** * Returns sensor privacy state for a specific sensor. * + * @param toggleType The type of toggle to use + * @param sensor The sensor to check * @return int sensor privacy state. * * @hide @@ -741,9 +723,10 @@ public final class SensorPrivacyManager { } } - /** + /** * Returns if camera privacy is enabled for a specific package. * + * @param packageName The package to check * @return boolean sensor privacy state. * * @hide @@ -763,29 +746,41 @@ public final class SensorPrivacyManager { * Returns camera privacy allowlist. * * @return List of automotive driver assistance packages for - * privacy allowlisting. The returned map includes the package - * name as key and the value is a Boolean which tells if that package - * is required by the car manufacturer as mandatory package for driving. + * privacy allowlisting. * * @hide */ @SystemApi @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY) @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) - public @NonNull Map<String, Boolean> getCameraPrivacyAllowlist() { + public @NonNull List<String> getCameraPrivacyAllowlist() { synchronized (mLock) { - if (mCameraPrivacyAllowlist == null) { - mCameraPrivacyAllowlist = new ArrayMap<>(); - try { - for (CameraPrivacyAllowlistEntry entry : - mService.getCameraPrivacyAllowlist()) { - mCameraPrivacyAllowlist.put(entry.packageName, entry.isMandatory); - } - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + try { + return mService.getCameraPrivacyAllowlist(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Sets camera privacy allowlist. + * + * @param allowlist List of automotive driver assistance packages for + * privacy allowlisting. + * + * @hide + */ + @TestApi + @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY) + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + public void setCameraPrivacyAllowlist(@NonNull List<String> allowlist) { + synchronized (mLock) { + try { + mService.setCameraPrivacyAllowlist(allowlist); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); } - return mCameraPrivacyAllowlist; } } @@ -867,6 +862,7 @@ public final class SensorPrivacyManager { /** * Sets sensor privacy to the specified state for an individual sensor. * + * @param source the source using which the sensor is toggled * @param sensor the sensor which to change the state for * @param state the state to which sensor privacy should be set. * diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 57b437f4bacf..dc8f4b448931 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -3521,7 +3521,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>When the key is present, only a PRIVATE/YUV output of the specified size is guaranteed * to be supported by the camera HAL in the secure camera mode. Any other format or * resolutions might not be supported. Use - * {@link CameraManager#isSessionConfigurationWithParametersSupported } + * {@link CameraDevice#isSessionConfigurationSupported } * API to query if a secure session configuration is supported if the device supports this * API.</p> * <p>If this key returns null on a device with SECURE_IMAGE_DATA capability, the application @@ -5046,18 +5046,18 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri /** * <p>The version of the session configuration query - * {@link android.hardware.camera2.CameraManager#isSessionConfigurationWithParametersSupported } + * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported } * API</p> * <p>The possible values in this key correspond to the values defined in * android.os.Build.VERSION_CODES. Each version defines a set of feature combinations the * camera device must reliably report whether they are supported via - * {@link android.hardware.camera2.CameraManager#isSessionConfigurationWithParametersSupported } + * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported } * API. And the version is always less or equal to android.os.Build.VERSION.SDK_INT.</p> * <p>If set to UPSIDE_DOWN_CAKE, this camera device doesn't support - * {@link android.hardware.camera2.CameraManager#isSessionConfigurationWithParametersSupported }. + * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }. * Calling the method for this camera ID throws an UnsupportedOperationException.</p> * <p>If set to VANILLA_ICE_CREAM, the application can call - * {@link android.hardware.camera2.CameraManager#isSessionConfigurationWithParametersSupported } + * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported } * to check if the combinations of below features are supported.</p> * <ul> * <li>A subset of LIMITED-level device stream combinations.</li> @@ -6082,11 +6082,11 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri /** * <p>Minimum and maximum padding zoom factors supported by this camera device for - * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } used for the + * android.efv.paddingZoomFactor used for the * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } * extension.</p> * <p>The minimum and maximum padding zoom factors supported by the device for - * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } used as part of the + * android.efv.paddingZoomFactor used as part of the * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } * extension feature. This extension specific camera characteristic can be queried using * {@link android.hardware.camera2.CameraExtensionCharacteristics#get }.</p> diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index e24c98e98c5d..9fb561bb4211 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -911,10 +911,10 @@ public abstract class CameraMetadata<TKey> { * </ul> * <p>Combinations of logical and physical streams, or physical streams from different * physical cameras are not guaranteed. However, if the camera device supports - * {@link CameraManager#isSessionConfigurationWithParametersSupported }, + * {@link CameraDevice#isSessionConfigurationSupported }, * application must be able to query whether a stream combination involving physical * streams is supported by calling - * {@link CameraManager#isSessionConfigurationWithParametersSupported }.</p> + * {@link CameraDevice#isSessionConfigurationSupported }.</p> * <p>Camera application shouldn't assume that there are at most 1 rear camera and 1 front * camera in the system. For an application that switches between front and back cameras, * the recommendation is to switch between the first rear camera and the first front diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java index 9fbe348f1e9a..f3b7b919d87d 100644 --- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java +++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java @@ -260,7 +260,7 @@ public final class MandatoryStreamCombination { * smaller sizes, then the resulting * {@link android.hardware.camera2.params.SessionConfiguration session configuration} can * be tested either by calling {@link CameraDevice#createCaptureSession} or - * {@link CameraManager#isSessionConfigurationWithParametersSupported}. + * {@link CameraDeviceSetup#isSessionConfigurationSupported}. * * @return non-modifiable ascending list of available sizes. */ diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl index 1f5495999416..2816f777e8ab 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -27,6 +27,7 @@ import android.hardware.input.IKeyboardBacklightListener; import android.hardware.input.IKeyboardBacklightState; import android.hardware.input.IStickyModifierStateListener; import android.hardware.input.ITabletModeChangedListener; +import android.hardware.input.KeyboardLayoutSelectionResult; import android.hardware.input.TouchCalibration; import android.os.CombinedVibration; import android.hardware.input.IInputSensorEventListener; @@ -120,8 +121,9 @@ interface IInputManager { String keyboardLayoutDescriptor); // New Keyboard layout config APIs - String getKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier, int userId, - in InputMethodInfo imeInfo, in InputMethodSubtype imeSubtype); + KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice( + in InputDeviceIdentifier identifier, int userId, in InputMethodInfo imeInfo, + in InputMethodSubtype imeSubtype); @EnforcePermission("SET_KEYBOARD_LAYOUT") @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index 744dfae97108..a1242fb43bbd 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -784,10 +784,10 @@ public final class InputManager { * * @hide */ - @Nullable - public String getKeyboardLayoutForInputDevice(@NonNull InputDeviceIdentifier identifier, - @UserIdInt int userId, @NonNull InputMethodInfo imeInfo, - @Nullable InputMethodSubtype imeSubtype) { + @NonNull + public KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice( + @NonNull InputDeviceIdentifier identifier, @UserIdInt int userId, + @NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype) { try { return mIm.getKeyboardLayoutForInputDevice(identifier, userId, imeInfo, imeSubtype); } catch (RemoteException ex) { diff --git a/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl b/core/java/android/hardware/input/KeyboardLayoutSelectionResult.aidl index 838e41ee1c08..13be2ff5ffb7 100644 --- a/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl +++ b/core/java/android/hardware/input/KeyboardLayoutSelectionResult.aidl @@ -1,11 +1,11 @@ -/** - * Copyright (c) 2024, The Android Open Source Project +/* + * 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 + * 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, @@ -14,11 +14,6 @@ * limitations under the License. */ -package android.hardware; - -/** @hide */ -parcelable CameraPrivacyAllowlistEntry { - String packageName; - boolean isMandatory; -} +package android.hardware.input; +parcelable KeyboardLayoutSelectionResult; diff --git a/core/java/android/hardware/input/KeyboardLayoutSelectionResult.java b/core/java/android/hardware/input/KeyboardLayoutSelectionResult.java new file mode 100644 index 000000000000..5a1c9478f629 --- /dev/null +++ b/core/java/android/hardware/input/KeyboardLayoutSelectionResult.java @@ -0,0 +1,260 @@ +/* + * 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. + */ + +package android.hardware.input; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcelable; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodSubtype; + +import com.android.internal.util.DataClass; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Provides information about the selected layout and the selection criteria when the caller calls + * {@link InputManager#getKeyboardLayoutForInputDevice(InputDeviceIdentifier, int, InputMethodInfo, + * InputMethodSubtype)} + * + * @hide + */ + +@DataClass(genParcelable = true, genToString = true, genEqualsHashCode = true) +public final class KeyboardLayoutSelectionResult implements Parcelable { + @Nullable + private final String mLayoutDescriptor; + + /** Unspecified layout selection criteria */ + public static final int LAYOUT_SELECTION_CRITERIA_UNSPECIFIED = 0; + + /** Manual selection by user */ + public static final int LAYOUT_SELECTION_CRITERIA_USER = 1; + + /** Auto-detection based on device provided language tag and layout type */ + public static final int LAYOUT_SELECTION_CRITERIA_DEVICE = 2; + + /** Auto-detection based on IME provided language tag and layout type */ + public static final int LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD = 3; + + /** Default selection */ + public static final int LAYOUT_SELECTION_CRITERIA_DEFAULT = 4; + + /** Failed layout selection */ + public static final KeyboardLayoutSelectionResult FAILED = new KeyboardLayoutSelectionResult( + null, LAYOUT_SELECTION_CRITERIA_UNSPECIFIED); + + @LayoutSelectionCriteria + private final int mSelectionCriteria; + + + + // Code below generated by codegen v1.0.23. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/hardware/input/KeyboardLayoutSelectionResult.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @IntDef(prefix = "LAYOUT_SELECTION_CRITERIA_", value = { + LAYOUT_SELECTION_CRITERIA_UNSPECIFIED, + LAYOUT_SELECTION_CRITERIA_USER, + LAYOUT_SELECTION_CRITERIA_DEVICE, + LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD, + LAYOUT_SELECTION_CRITERIA_DEFAULT + }) + @Retention(RetentionPolicy.SOURCE) + @DataClass.Generated.Member + public @interface LayoutSelectionCriteria {} + + @DataClass.Generated.Member + public static String layoutSelectionCriteriaToString(@LayoutSelectionCriteria int value) { + switch (value) { + case LAYOUT_SELECTION_CRITERIA_UNSPECIFIED: + return "LAYOUT_SELECTION_CRITERIA_UNSPECIFIED"; + case LAYOUT_SELECTION_CRITERIA_USER: + return "LAYOUT_SELECTION_CRITERIA_USER"; + case LAYOUT_SELECTION_CRITERIA_DEVICE: + return "LAYOUT_SELECTION_CRITERIA_DEVICE"; + case LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD: + return "LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD"; + case LAYOUT_SELECTION_CRITERIA_DEFAULT: + return "LAYOUT_SELECTION_CRITERIA_DEFAULT"; + default: return Integer.toHexString(value); + } + } + + @DataClass.Generated.Member + public KeyboardLayoutSelectionResult( + @Nullable String layoutDescriptor, + @LayoutSelectionCriteria int selectionCriteria) { + this.mLayoutDescriptor = layoutDescriptor; + this.mSelectionCriteria = selectionCriteria; + + if (!(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_UNSPECIFIED) + && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_USER) + && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEVICE) + && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD) + && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEFAULT)) { + throw new java.lang.IllegalArgumentException( + "selectionCriteria was " + mSelectionCriteria + " but must be one of: " + + "LAYOUT_SELECTION_CRITERIA_UNSPECIFIED(" + LAYOUT_SELECTION_CRITERIA_UNSPECIFIED + "), " + + "LAYOUT_SELECTION_CRITERIA_USER(" + LAYOUT_SELECTION_CRITERIA_USER + "), " + + "LAYOUT_SELECTION_CRITERIA_DEVICE(" + LAYOUT_SELECTION_CRITERIA_DEVICE + "), " + + "LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD(" + LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD + "), " + + "LAYOUT_SELECTION_CRITERIA_DEFAULT(" + LAYOUT_SELECTION_CRITERIA_DEFAULT + ")"); + } + + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public @Nullable String getLayoutDescriptor() { + return mLayoutDescriptor; + } + + @DataClass.Generated.Member + public @LayoutSelectionCriteria int getSelectionCriteria() { + return mSelectionCriteria; + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "KeyboardLayoutSelectionResult { " + + "layoutDescriptor = " + mLayoutDescriptor + ", " + + "selectionCriteria = " + layoutSelectionCriteriaToString(mSelectionCriteria) + + " }"; + } + + @Override + @DataClass.Generated.Member + public boolean equals(@Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(KeyboardLayoutSelectionResult other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + KeyboardLayoutSelectionResult that = (KeyboardLayoutSelectionResult) o; + //noinspection PointlessBooleanExpression + return true + && java.util.Objects.equals(mLayoutDescriptor, that.mLayoutDescriptor) + && mSelectionCriteria == that.mSelectionCriteria; + } + + @Override + @DataClass.Generated.Member + public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + java.util.Objects.hashCode(mLayoutDescriptor); + _hash = 31 * _hash + mSelectionCriteria; + return _hash; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + byte flg = 0; + if (mLayoutDescriptor != null) flg |= 0x1; + dest.writeByte(flg); + if (mLayoutDescriptor != null) dest.writeString(mLayoutDescriptor); + dest.writeInt(mSelectionCriteria); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ KeyboardLayoutSelectionResult(@NonNull android.os.Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + byte flg = in.readByte(); + String layoutDescriptor = (flg & 0x1) == 0 ? null : in.readString(); + int selectionCriteria = in.readInt(); + + this.mLayoutDescriptor = layoutDescriptor; + this.mSelectionCriteria = selectionCriteria; + + if (!(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_UNSPECIFIED) + && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_USER) + && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEVICE) + && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD) + && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEFAULT)) { + throw new java.lang.IllegalArgumentException( + "selectionCriteria was " + mSelectionCriteria + " but must be one of: " + + "LAYOUT_SELECTION_CRITERIA_UNSPECIFIED(" + LAYOUT_SELECTION_CRITERIA_UNSPECIFIED + "), " + + "LAYOUT_SELECTION_CRITERIA_USER(" + LAYOUT_SELECTION_CRITERIA_USER + "), " + + "LAYOUT_SELECTION_CRITERIA_DEVICE(" + LAYOUT_SELECTION_CRITERIA_DEVICE + "), " + + "LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD(" + LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD + "), " + + "LAYOUT_SELECTION_CRITERIA_DEFAULT(" + LAYOUT_SELECTION_CRITERIA_DEFAULT + ")"); + } + + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<KeyboardLayoutSelectionResult> CREATOR + = new Parcelable.Creator<KeyboardLayoutSelectionResult>() { + @Override + public KeyboardLayoutSelectionResult[] newArray(int size) { + return new KeyboardLayoutSelectionResult[size]; + } + + @Override + public KeyboardLayoutSelectionResult createFromParcel(@NonNull android.os.Parcel in) { + return new KeyboardLayoutSelectionResult(in); + } + }; + + @DataClass.Generated( + time = 1709568115865L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/hardware/input/KeyboardLayoutSelectionResult.java", + inputSignatures = "private final @android.annotation.Nullable java.lang.String mLayoutDescriptor\npublic static final int LAYOUT_SELECTION_CRITERIA_UNSPECIFIED\npublic static final int LAYOUT_SELECTION_CRITERIA_USER\npublic static final int LAYOUT_SELECTION_CRITERIA_DEVICE\npublic static final int LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD\npublic static final int LAYOUT_SELECTION_CRITERIA_DEFAULT\npublic static final android.hardware.input.KeyboardLayoutSelectionResult FAILED\nprivate final @android.hardware.input.KeyboardLayoutSelectionResult.LayoutSelectionCriteria int mSelectionCriteria\nclass KeyboardLayoutSelectionResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genToString=true, genEqualsHashCode=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index f5b58b920efb..9dc8c5d9f2e9 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -453,7 +453,7 @@ class IInputMethodWrapper extends IInputMethod.Stub @BinderThread @Override - public void showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken, + public void showSoftInput(IBinder showInputToken, @NonNull ImeTracker.Token statsToken, @InputMethod.ShowFlags int flags, ResultReceiver resultReceiver) { ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER); mCaller.executeOrSendMessage(mCaller.obtainMessageIOOO(DO_SHOW_SOFT_INPUT, @@ -462,7 +462,7 @@ class IInputMethodWrapper extends IInputMethod.Stub @BinderThread @Override - public void hideSoftInput(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken, + public void hideSoftInput(IBinder hideInputToken, @NonNull ImeTracker.Token statsToken, int flags, ResultReceiver resultReceiver) { ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER); mCaller.executeOrSendMessage(mCaller.obtainMessageIOOO(DO_HIDE_SOFT_INPUT, diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 2c7ca27e6b07..4dbdd91d5fc7 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -701,7 +701,13 @@ public class InputMethodService extends AbstractInputMethodService { */ private IBinder mCurHideInputToken; - /** The token tracking the current IME request or {@code null} otherwise. */ + /** + * The token tracking the current IME request. + * + * <p> This exists as a workaround to changing the signatures of public methods. It will get + * set to a {@code non-null} value before every call that uses it, stored locally inside the + * callee, and immediately after reset to {@code null} from the callee. + */ @Nullable private ImeTracker.Token mCurStatsToken; @@ -907,14 +913,13 @@ public class InputMethodService extends AbstractInputMethodService { @MainThread @Override public void hideSoftInputWithToken(int flags, ResultReceiver resultReceiver, - IBinder hideInputToken, @Nullable ImeTracker.Token statsToken) { + IBinder hideInputToken, @NonNull ImeTracker.Token statsToken) { mSystemCallingHideSoftInput = true; mCurHideInputToken = hideInputToken; mCurStatsToken = statsToken; try { hideSoftInput(flags, resultReceiver); } finally { - mCurStatsToken = null; mCurHideInputToken = null; mSystemCallingHideSoftInput = false; } @@ -926,23 +931,33 @@ public class InputMethodService extends AbstractInputMethodService { @MainThread @Override public void hideSoftInput(int flags, ResultReceiver resultReceiver) { - ImeTracker.forLogging().onProgress( - mCurStatsToken, ImeTracker.PHASE_IME_HIDE_SOFT_INPUT); if (DEBUG) Log.v(TAG, "hideSoftInput()"); + + final var statsToken = mCurStatsToken != null ? mCurStatsToken + : createStatsToken(false /* show */, + SoftInputShowHideReason.HIDE_SOFT_INPUT_LEGACY_DIRECT, + ImeTracker.isFromUser(mRootView)); + mCurStatsToken = null; + + // TODO(b/148086656): Disallow IME developers from calling InputMethodImpl methods. if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R && !mSystemCallingHideSoftInput) { Log.e(TAG, "IME shouldn't call hideSoftInput on itself." + " Use requestHideSelf(int) itself"); + ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_IME_HIDE_SOFT_INPUT); return; } + ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_HIDE_SOFT_INPUT); + + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.hideSoftInput"); ImeTracing.getInstance().triggerServiceDump( "InputMethodService.InputMethodImpl#hideSoftInput", mDumper, null /* icProto */); final boolean wasVisible = isInputViewShown(); - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.hideSoftInput"); mShowInputFlags = 0; mShowInputRequested = false; + mCurStatsToken = statsToken; hideWindow(); final boolean isVisible = isInputViewShown(); final boolean visibilityChanged = isVisible != wasVisible; @@ -963,14 +978,13 @@ public class InputMethodService extends AbstractInputMethodService { @Override public void showSoftInputWithToken(@InputMethod.ShowFlags int flags, ResultReceiver resultReceiver, IBinder showInputToken, - @Nullable ImeTracker.Token statsToken) { + @NonNull ImeTracker.Token statsToken) { mSystemCallingShowSoftInput = true; mCurShowInputToken = showInputToken; mCurStatsToken = statsToken; try { showSoftInput(flags, resultReceiver); } finally { - mCurStatsToken = null; mCurShowInputToken = null; mSystemCallingShowSoftInput = false; } @@ -982,16 +996,23 @@ public class InputMethodService extends AbstractInputMethodService { @MainThread @Override public void showSoftInput(@InputMethod.ShowFlags int flags, ResultReceiver resultReceiver) { - ImeTracker.forLogging().onProgress( - mCurStatsToken, ImeTracker.PHASE_IME_SHOW_SOFT_INPUT); if (DEBUG) Log.v(TAG, "showSoftInput()"); + + final var statsToken = mCurStatsToken != null ? mCurStatsToken + : createStatsToken(true /* show */, + SoftInputShowHideReason.SHOW_SOFT_INPUT_LEGACY_DIRECT, + ImeTracker.isFromUser(mRootView)); + mCurStatsToken = null; + // TODO(b/148086656): Disallow IME developers from calling InputMethodImpl methods. if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R && !mSystemCallingShowSoftInput) { - Log.e(TAG," IME shouldn't call showSoftInput on itself." + Log.e(TAG, "IME shouldn't call showSoftInput on itself." + " Use requestShowSelf(int) itself"); + ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_IME_SHOW_SOFT_INPUT); return; } + ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_SHOW_SOFT_INPUT); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.showSoftInput"); ImeTracing.getInstance().triggerServiceDump( @@ -999,11 +1020,12 @@ public class InputMethodService extends AbstractInputMethodService { null /* icProto */); final boolean wasVisible = isInputViewShown(); if (dispatchOnShowInputRequested(flags, false)) { - ImeTracker.forLogging().onProgress(mCurStatsToken, + ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE); - showWindow(true); + mCurStatsToken = statsToken; + showWindow(true /* showInput */); } else { - ImeTracker.forLogging().onFailed(mCurStatsToken, + ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE); } setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition); @@ -1895,21 +1917,23 @@ public class InputMethodService extends AbstractInputMethodService { if (showingInput) { // If we were last showing the soft keyboard, try to do so again. if (dispatchOnShowInputRequested(showFlags, true)) { - showWindow(true); + showWindowWithToken(true /* showInput */, + SoftInputShowHideReason.RESET_NEW_CONFIGURATION); if (completions != null) { mCurCompletions = completions; onDisplayCompletions(completions); } } else { - hideWindow(); + hideWindowWithToken(SoftInputShowHideReason.RESET_NEW_CONFIGURATION); } } else if (mCandidatesVisibility == View.VISIBLE) { // If the candidates are currently visible, make sure the // window is shown for them. - showWindow(false); + showWindowWithToken(false /* showInput */, + SoftInputShowHideReason.RESET_NEW_CONFIGURATION); } else { // Otherwise hide the window. - hideWindow(); + hideWindowWithToken(SoftInputShowHideReason.RESET_NEW_CONFIGURATION); } // If user uses hard keyboard, IME button should always be shown. boolean showing = onEvaluateInputViewShown(); @@ -2368,13 +2392,15 @@ public class InputMethodService extends AbstractInputMethodService { // has not asked for the input view to be shown, then we need // to update whether the window is shown. if (shown) { - showWindow(false); + showWindowWithToken(false /* showInput */, + SoftInputShowHideReason.UPDATE_CANDIDATES_VIEW_VISIBILITY); } else { - hideWindow(); + hideWindowWithToken( + SoftInputShowHideReason.UPDATE_CANDIDATES_VIEW_VISIBILITY); } } } - + void updateCandidatesVisibility(boolean shown) { int vis = shown ? View.VISIBLE : getCandidatesHiddenVisibility(); if (mCandidatesVisibility != vis) { @@ -3009,6 +3035,19 @@ public class InputMethodService extends AbstractInputMethodService { return result; } + /** + * Utility function that creates an IME request tracking token before + * calling {@link #showWindow}. + * + * @param showInput whether the input window should be shown. + * @param reason the reason why the IME request was created. + */ + private void showWindowWithToken(boolean showInput, @SoftInputShowHideReason int reason) { + mCurStatsToken = createStatsToken(true /* show */, reason, + ImeTracker.isFromUser(mRootView)); + showWindow(showInput); + } + public void showWindow(boolean showInput) { if (DEBUG) Log.v(TAG, "Showing window: showInput=" + showInput + " mShowInputRequested=" + mShowInputRequested @@ -3018,11 +3057,20 @@ public class InputMethodService extends AbstractInputMethodService { + " mInputStarted=" + mInputStarted + " mShowInputFlags=" + mShowInputFlags); + final var statsToken = mCurStatsToken != null ? mCurStatsToken + : createStatsToken(true /* show */, + SoftInputShowHideReason.SHOW_WINDOW_LEGACY_DIRECT, + ImeTracker.isFromUser(mRootView)); + mCurStatsToken = null; + if (mInShowWindow) { Log.w(TAG, "Re-entrance in to showWindow"); + ImeTracker.forLogging().onCancelled(statsToken, ImeTracker.PHASE_IME_SHOW_WINDOW); return; } + ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_SHOW_WINDOW); + ImeTracing.getInstance().triggerServiceDump("InputMethodService#showWindow", mDumper, null /* icProto */); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.showWindow"); @@ -3046,7 +3094,7 @@ public class InputMethodService extends AbstractInputMethodService { if (DEBUG) Log.v(TAG, "showWindow: draw decorView!"); mWindow.show(); mDecorViewWasVisible = true; - applyVisibilityInInsetsConsumerIfNecessary(true); + applyVisibilityInInsetsConsumerIfNecessary(true /* setVisible */, statsToken); cancelImeSurfaceRemoval(); mInShowWindow = false; Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); @@ -3137,13 +3185,15 @@ public class InputMethodService extends AbstractInputMethodService { * Applies the IME visibility in {@link android.view.ImeInsetsSourceConsumer}. * * @param setVisible {@code true} to make it visible, false to hide it. + * @param statsToken the token tracking the current IME request. */ - private void applyVisibilityInInsetsConsumerIfNecessary(boolean setVisible) { + private void applyVisibilityInInsetsConsumerIfNecessary(boolean setVisible, + @NonNull ImeTracker.Token statsToken) { ImeTracing.getInstance().triggerServiceDump( "InputMethodService#applyVisibilityInInsetsConsumerIfNecessary", mDumper, null /* icProto */); mPrivOps.applyImeVisibilityAsync(setVisible - ? mCurShowInputToken : mCurHideInputToken, setVisible, mCurStatsToken); + ? mCurShowInputToken : mCurHideInputToken, setVisible, statsToken); } private void finishViews(boolean finishingInput) { @@ -3159,12 +3209,35 @@ public class InputMethodService extends AbstractInputMethodService { mCandidatesViewStarted = false; } + /** + * Utility function that creates an IME request tracking token before + * calling {@link #hideWindow}. + * + * @param reason the reason why the IME request was created. + */ + private void hideWindowWithToken(@SoftInputShowHideReason int reason) { + // TODO(b/303041796): this should be handled by ImeTracker.isFromUser after fixing it + // to work with onClickListeners + final boolean isFromUser = ImeTracker.isFromUser(mRootView) + || reason == SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_BACK_KEY; + mCurStatsToken = createStatsToken(false /* show */, reason, isFromUser); + hideWindow(); + } + public void hideWindow() { if (DEBUG) Log.v(TAG, "CALL: hideWindow"); + + final var statsToken = mCurStatsToken != null ? mCurStatsToken + : createStatsToken(false /* show */, + SoftInputShowHideReason.HIDE_WINDOW_LEGACY_DIRECT, + ImeTracker.isFromUser(mRootView)); + mCurStatsToken = null; + + ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_HIDE_WINDOW); ImeTracing.getInstance().triggerServiceDump("InputMethodService#hideWindow", mDumper, null /* icProto */); setImeWindowStatus(0, mBackDisposition); - applyVisibilityInInsetsConsumerIfNecessary(false); + applyVisibilityInInsetsConsumerIfNecessary(false /* setVisible */, statsToken); mWindowVisible = false; finishViews(false /* finishingInput */); if (mDecorViewVisible) { @@ -3440,9 +3513,14 @@ public class InputMethodService extends AbstractInputMethodService { private void requestHideSelf(@InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason) { + // TODO(b/303041796): this should be handled by ImeTracker.isFromUser after fixing it + // to work with onClickListeners + final boolean isFromUser = ImeTracker.isFromUser(mRootView) + || reason == SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_BACK_KEY; + final var statsToken = createStatsToken(false /* show */, reason, isFromUser); ImeTracing.getInstance().triggerServiceDump("InputMethodService#requestHideSelf", mDumper, null /* icProto */); - mPrivOps.hideMySoftInput(flags, reason); + mPrivOps.hideMySoftInput(statsToken, flags, reason); } /** @@ -3450,9 +3528,16 @@ public class InputMethodService extends AbstractInputMethodService { * interact with it. */ public final void requestShowSelf(@InputMethodManager.ShowFlags int flags) { + requestShowSelf(flags, SoftInputShowHideReason.SHOW_SOFT_INPUT_FROM_IME); + } + + private void requestShowSelf(@InputMethodManager.ShowFlags int flags, + @SoftInputShowHideReason int reason) { + final var statsToken = createStatsToken(true /* show */, reason, + ImeTracker.isFromUser(mRootView)); ImeTracing.getInstance().triggerServiceDump("InputMethodService#requestShowSelf", mDumper, null /* icProto */); - mPrivOps.showMySoftInput(flags); + mPrivOps.showMySoftInput(statsToken, flags, reason); } private boolean handleBack(boolean doIt) { @@ -3472,7 +3557,7 @@ public class InputMethodService extends AbstractInputMethodService { // If we have the window visible for some other reason -- // most likely to show candidates -- then just get rid // of it. This really shouldn't happen, but just in case... - if (doIt) hideWindow(); + if (doIt) hideWindowWithToken(SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_BACK_KEY); } return true; } @@ -3627,10 +3712,11 @@ public class InputMethodService extends AbstractInputMethodService { @InputMethodManager.HideFlags int hideFlags) { if (DEBUG) Log.v(TAG, "toggleSoftInput()"); if (isInputViewShown()) { - requestHideSelf( - hideFlags, SoftInputShowHideReason.HIDE_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT); + requestHideSelf(hideFlags, + SoftInputShowHideReason.HIDE_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT); } else { - requestShowSelf(showFlags); + requestShowSelf(showFlags, + SoftInputShowHideReason.SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT); } } @@ -4272,6 +4358,20 @@ public class InputMethodService extends AbstractInputMethodService { } /** + * Creates an IME request tracking token. + * + * @param show whether this is a show or a hide request. + * @param reason the reason why the IME request was created. + * @param isFromUser whether this request was created directly from user interaction. + */ + @NonNull + private ImeTracker.Token createStatsToken(boolean show, @SoftInputShowHideReason int reason, + boolean isFromUser) { + return ImeTracker.forLogging().onStart(show ? ImeTracker.TYPE_SHOW : ImeTracker.TYPE_HIDE, + ImeTracker.ORIGIN_IME, reason, isFromUser); + } + + /** * Performs a dump of the InputMethodService's internal state. Override * to add your own information to the dump. */ diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java index 05a1abeaf479..b41753413baf 100644 --- a/core/java/android/os/BatteryConsumer.java +++ b/core/java/android/os/BatteryConsumer.java @@ -240,7 +240,7 @@ public abstract class BatteryConsumer { new Dimensions(POWER_COMPONENT_ANY, PROCESS_STATE_ANY); /** - * Identifies power attribution dimensions that are captured by an data element of + * Identifies power attribution dimensions that are captured by a data element of * a BatteryConsumer. These Keys are used to access those values and to set them using * Builders. See for example {@link #getConsumedPower(Key)}. * diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index e09094203ad4..c611cb972b2c 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -1587,7 +1587,7 @@ public abstract class BatteryStats { outNumOfInterest[0] = numOfInterest; } - // The estimated time is the average time we spend in each level, multipled + // The estimated time is the average time we spend in each level, multiplied // by 100 -- the total number of battery levels return (total / numOfInterest) * 100; } @@ -2019,7 +2019,7 @@ public abstract class BatteryStats { public static final int EVENT_TOP = 0x0003; // Event is about active sync operations. public static final int EVENT_SYNC = 0x0004; - // Events for all additional wake locks aquired/release within a wake block. + // Events for all additional wake locks acquired/release within a wake block. // These are not generated by default. public static final int EVENT_WAKE_LOCK = 0x0005; // Event is about an application executing a scheduled job. @@ -3419,7 +3419,7 @@ public abstract class BatteryStats { * incoming service calls from apps. The result is returned as an array of longs, * organized as a sequence like this: * <pre> - * cluster1-speeed1, cluster1-speed2, ..., cluster2-speed1, cluster2-speed2, ... + * cluster1-speed1, cluster1-speed2, ..., cluster2-speed1, cluster2-speed2, ... * </pre> * * @see com.android.internal.os.CpuScalingPolicies#getPolicies diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index 3149de4c39e7..beb9a935a6ee 100644 --- a/core/java/android/os/GraphicsEnvironment.java +++ b/core/java/android/os/GraphicsEnvironment.java @@ -120,7 +120,6 @@ public class GraphicsEnvironment { private ClassLoader mClassLoader; private String mLibrarySearchPaths; private String mLibraryPermittedPaths; - private GameManager mGameManager; private int mAngleOptInIndex = -1; private boolean mShouldUseAngle = false; @@ -134,8 +133,6 @@ public class GraphicsEnvironment { final ApplicationInfo appInfoWithMetaData = getAppInfoWithMetadata(context, pm, packageName); - mGameManager = context.getSystemService(GameManager.class); - Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupGpuLayers"); setupGpuLayers(context, coreSettings, pm, packageName, appInfoWithMetaData); Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS); @@ -161,9 +158,11 @@ public class GraphicsEnvironment { Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS); Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "notifyGraphicsEnvironmentSetup"); - if (mGameManager != null - && appInfoWithMetaData.category == ApplicationInfo.CATEGORY_GAME) { - mGameManager.notifyGraphicsEnvironmentSetup(); + if (appInfoWithMetaData.category == ApplicationInfo.CATEGORY_GAME) { + final GameManager gameManager = context.getSystemService(GameManager.class); + if (gameManager != null) { + gameManager.notifyGraphicsEnvironmentSetup(); + } } Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS); } diff --git a/core/java/android/os/HwParcel.java b/core/java/android/os/HwParcel.java index 9fd37d4548ac..fb500a96c737 100644 --- a/core/java/android/os/HwParcel.java +++ b/core/java/android/os/HwParcel.java @@ -618,7 +618,7 @@ public class HwParcel { */ @FastNative @NonNull - public native final @Nullable + public native final HidlMemory readEmbeddedHidlMemory(long fieldHandle, long parentHandle, long offset); /** diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index ccb534eb1019..9757a1096a30 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -4708,6 +4708,9 @@ public class UserManager { * Sets the user as enabled, if such an user exists. * * <p>Note that the default is true, it's only that managed profiles might not be enabled. + * (Managed profiles created by DevicePolicyManager will start out disabled, and DPM will later + * toggle them to enabled once they are provisioned. This is the primary purpose of the + * {@link UserInfo#FLAG_DISABLED} flag.) * Also ephemeral users can be disabled to indicate that their removal is in progress and they * shouldn't be re-entered. Therefore ephemeral users should not be re-enabled once disabled. * @@ -5259,7 +5262,7 @@ public class UserManager { /** * Returns list of the profiles of userId including userId itself. - * Note that this returns only enabled. + * Note that this returns only {@link UserInfo#isEnabled() enabled} profiles. * <p>Note that this includes all profile types (not including Restricted profiles). * * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} or diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index abfa4e3dd8dc..d9400accb4bd 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -1,4 +1,5 @@ package: "android.os" +container: "system" flag { name: "android_os_build_vanilla_ice_cream" @@ -40,6 +41,7 @@ flag { namespace: "profile_experiences" description: "Guards a new Private Profile type in UserManager - everything from its setup to config to deletion." bug: "299069460" + is_exported: true } flag { diff --git a/core/java/android/os/health/HealthStatsWriter.java b/core/java/android/os/health/HealthStatsWriter.java index d4d10b056c5c..4118775af28a 100644 --- a/core/java/android/os/health/HealthStatsWriter.java +++ b/core/java/android/os/health/HealthStatsWriter.java @@ -58,7 +58,7 @@ public class HealthStatsWriter { * Construct a HealthStatsWriter object with the given constants. * * The "getDataType()" of the resulting HealthStats object will be the - * short name of the java class that the Constants object was initalized + * short name of the java class that the Constants object was initialized * with. */ public HealthStatsWriter(HealthKeys.Constants constants) { diff --git a/core/java/android/os/image/DynamicSystemClient.java b/core/java/android/os/image/DynamicSystemClient.java index 88096ab91261..ada708bda34c 100644 --- a/core/java/android/os/image/DynamicSystemClient.java +++ b/core/java/android/os/image/DynamicSystemClient.java @@ -52,7 +52,7 @@ import java.util.concurrent.Executor; * * After the installation is completed, the device will be running in the new system on next the * reboot. Then, when the user reboots the device again, it will leave {@code DynamicSystem} and go - * back to the original system. While running in {@code DynamicSystem}, persitent storage for + * back to the original system. While running in {@code DynamicSystem}, persistent storage for * factory reset protection (FRP) remains unchanged. Since the user is running the new system with * a temporarily created data partition, their original user data are kept unchanged.</p> * diff --git a/core/java/android/os/image/DynamicSystemManager.java b/core/java/android/os/image/DynamicSystemManager.java index 536795bafb1c..8ce87e97da9c 100644 --- a/core/java/android/os/image/DynamicSystemManager.java +++ b/core/java/android/os/image/DynamicSystemManager.java @@ -172,7 +172,7 @@ public class DynamicSystemManager { } } /** - * Finish a previously started installation. Installations without a cooresponding + * Finish a previously started installation. Installations without a corresponding * finishInstallation() will be cleaned up during device boot. */ @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM) diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 5a095410bef6..d45a17f7194e 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -1395,7 +1395,7 @@ public class StorageManager { // Package name can be null if the activity thread is running but the app // hasn't bound yet. In this case we fall back to the first package in the // current UID. This works for runtime permissions as permission state is - // per UID and permission realted app ops are updated for all UID packages. + // per UID and permission related app ops are updated for all UID packages. String[] packageNames = ActivityThread.getPackageManager().getPackagesForUid( android.os.Process.myUid()); if (packageNames == null || packageNames.length <= 0) { diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java index e1f112af41e7..4cf2fd4dce69 100644 --- a/core/java/android/os/storage/StorageVolume.java +++ b/core/java/android/os/storage/StorageVolume.java @@ -62,7 +62,7 @@ import java.util.UUID; * <li>To get access to standard directories (like the {@link Environment#DIRECTORY_PICTURES}), they * can use the {@link #createAccessIntent(String)}. This is the recommend way, since it provides a * simpler API and narrows the access to the given directory (and its descendants). - * <li>To get access to any directory (and its descendants), they can use the Storage Acess + * <li>To get access to any directory (and its descendants), they can use the Storage Access * Framework APIs (such as {@link Intent#ACTION_OPEN_DOCUMENT} and * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}, although these APIs do not guarantee the user will * select this specific volume. diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index fa9f03da6372..410f51045b9d 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -1788,6 +1788,9 @@ public final class PermissionManager { /** * Gets the permission states for requested package and persistent device. + * <p> + * <strong>Note: </strong>Default device permissions are not inherited in this API. Returns the + * exact permission states for the requested device. * * @param packageName name of the package you are checking against * @param persistentDeviceId id of the persistent device you are checking against @@ -2073,5 +2076,29 @@ public final class PermissionManager { return new PermissionState[size]; } }; + + /** @hide */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PermissionState that = (PermissionState) o; + return mGranted == that.mGranted && mFlags == that.mFlags; + } + + /** @hide */ + @Override + public int hashCode() { + return Objects.hash(mGranted, mFlags); + } + + /** @hide */ + @Override + public String toString() { + return "PermissionState{" + + "mGranted=" + mGranted + + ", mFlags=" + mFlags + + '}'; + } } } diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index de7008b19f54..dc782d4e1e9b 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -138,3 +138,11 @@ flag { bug: "325356776" } +flag { + name: "runtime_permission_appops_mapping_enabled" + is_fixed_read_only: true + namespace: "permissions" + description: "Use runtime permission state to determine appop state" + bug: "266164193" +} + diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index c03dc718d1a1..120846ca593b 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -667,7 +667,8 @@ public class CallLog { @FlaggedApi(Flags.FLAG_BUSINESS_CALL_COMPOSER) public @NonNull AddCallParametersBuilder setAssertedDisplayName( String assertedDisplayName) { - if (assertedDisplayName.length() > MAX_NUMBER_OF_CHARACTERS) { + if (assertedDisplayName != null + && assertedDisplayName.length() > MAX_NUMBER_OF_CHARACTERS) { throw new IllegalArgumentException("assertedDisplayName exceeds the character" + " limit of " + MAX_NUMBER_OF_CHARACTERS + "."); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 51585af10f5d..eea6464c9047 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -11576,6 +11576,15 @@ public final class Settings { "extra_low_power_warning_acknowledged"; /** + * Whether the emergency thermal alert would be disabled + * (0: default) or not (1). + * + * @hide + */ + public static final String EMERGENCY_THERMAL_ALERT_DISABLED = + "emergency_thermal_alert_disabled"; + + /** * 0 (default) Auto battery saver suggestion has not been suppressed. 1) it has been * suppressed. * @hide diff --git a/services/core/java/com/android/server/notification/ZenAdapters.java b/core/java/android/service/notification/ZenAdapters.java index 37b263c3e3bd..b249815ce8db 100644 --- a/services/core/java/com/android/server/notification/ZenAdapters.java +++ b/core/java/android/service/notification/ZenAdapters.java @@ -14,25 +14,28 @@ * limitations under the License. */ -package com.android.server.notification; +package android.service.notification; +import android.annotation.NonNull; import android.app.Flags; import android.app.NotificationManager.Policy; -import android.service.notification.ZenModeConfig; -import android.service.notification.ZenPolicy; /** * Converters between different Zen representations. + * @hide */ -class ZenAdapters { +public class ZenAdapters { - static ZenPolicy notificationPolicyToZenPolicy(Policy policy) { + /** Maps {@link Policy} to {@link ZenPolicy}. */ + @NonNull + public static ZenPolicy notificationPolicyToZenPolicy(@NonNull Policy policy) { ZenPolicy.Builder zenPolicyBuilder = new ZenPolicy.Builder() .allowAlarms(policy.allowAlarms()) .allowCalls( policy.allowCalls() - ? ZenModeConfig.getZenPolicySenders(policy.allowCallsFrom()) - : ZenPolicy.PEOPLE_TYPE_NONE) + ? notificationPolicySendersToZenPolicyPeopleType( + policy.allowCallsFrom()) + : ZenPolicy.PEOPLE_TYPE_NONE) .allowConversations( policy.allowConversations() ? notificationPolicyConversationSendersToZenPolicy( @@ -42,7 +45,8 @@ class ZenAdapters { .allowMedia(policy.allowMedia()) .allowMessages( policy.allowMessages() - ? ZenModeConfig.getZenPolicySenders(policy.allowMessagesFrom()) + ? notificationPolicySendersToZenPolicyPeopleType( + policy.allowMessagesFrom()) : ZenPolicy.PEOPLE_TYPE_NONE) .allowReminders(policy.allowReminders()) .allowRepeatCallers(policy.allowRepeatCallers()) @@ -65,9 +69,58 @@ class ZenAdapters { return zenPolicyBuilder.build(); } + /** Maps {@link ZenPolicy.PeopleType} enum to {@link Policy.PrioritySenders}. */ + @Policy.PrioritySenders + public static int zenPolicyPeopleTypeToNotificationPolicySenders( + @ZenPolicy.PeopleType int zpPeopleType, @Policy.PrioritySenders int defaultResult) { + switch (zpPeopleType) { + case ZenPolicy.PEOPLE_TYPE_ANYONE: + return Policy.PRIORITY_SENDERS_ANY; + case ZenPolicy.PEOPLE_TYPE_CONTACTS: + return Policy.PRIORITY_SENDERS_CONTACTS; + case ZenPolicy.PEOPLE_TYPE_STARRED: + return Policy.PRIORITY_SENDERS_STARRED; + default: + return defaultResult; + } + } + + /** Maps {@link Policy.PrioritySenders} enum to {@link ZenPolicy.PeopleType}. */ + @ZenPolicy.PeopleType + public static int notificationPolicySendersToZenPolicyPeopleType( + @Policy.PrioritySenders int npPrioritySenders) { + switch (npPrioritySenders) { + case Policy.PRIORITY_SENDERS_ANY: + return ZenPolicy.PEOPLE_TYPE_ANYONE; + case Policy.PRIORITY_SENDERS_CONTACTS: + return ZenPolicy.PEOPLE_TYPE_CONTACTS; + case Policy.PRIORITY_SENDERS_STARRED: + default: + return ZenPolicy.PEOPLE_TYPE_STARRED; + } + } + + /** Maps {@link ZenPolicy.ConversationSenders} enum to {@link Policy.ConversationSenders}. */ + @Policy.ConversationSenders + public static int zenPolicyConversationSendersToNotificationPolicy( + @ZenPolicy.ConversationSenders int zpConversationSenders, + @Policy.ConversationSenders int defaultResult) { + switch (zpConversationSenders) { + case ZenPolicy.CONVERSATION_SENDERS_ANYONE: + return Policy.CONVERSATION_SENDERS_ANYONE; + case ZenPolicy.CONVERSATION_SENDERS_IMPORTANT: + return Policy.CONVERSATION_SENDERS_IMPORTANT; + case ZenPolicy.CONVERSATION_SENDERS_NONE: + return Policy.CONVERSATION_SENDERS_NONE; + default: + return defaultResult; + } + } + + /** Maps {@link Policy.ConversationSenders} enum to {@link ZenPolicy.ConversationSenders}. */ @ZenPolicy.ConversationSenders private static int notificationPolicyConversationSendersToZenPolicy( - int npPriorityConversationSenders) { + @Policy.ConversationSenders int npPriorityConversationSenders) { switch (npPriorityConversationSenders) { case Policy.CONVERSATION_SENDERS_ANYONE: return ZenPolicy.CONVERSATION_SENDERS_ANYONE; @@ -75,8 +128,7 @@ class ZenAdapters { return ZenPolicy.CONVERSATION_SENDERS_IMPORTANT; case Policy.CONVERSATION_SENDERS_NONE: return ZenPolicy.CONVERSATION_SENDERS_NONE; - case Policy.CONVERSATION_SENDERS_UNSET: - default: + default: // including Policy.CONVERSATION_SENDERS_UNSET return ZenPolicy.CONVERSATION_SENDERS_UNSET; } } diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index d9ca935b35b3..1d6dd1ebd54a 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -24,6 +24,9 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCRE import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF; +import static android.service.notification.ZenAdapters.notificationPolicySendersToZenPolicyPeopleType; +import static android.service.notification.ZenAdapters.zenPolicyConversationSendersToNotificationPolicy; +import static android.service.notification.ZenAdapters.zenPolicyPeopleTypeToNotificationPolicySenders; import android.annotation.FlaggedApi; import android.annotation.IntDef; @@ -1269,11 +1272,11 @@ public class ZenModeConfig implements Parcelable { public ZenPolicy toZenPolicy() { ZenPolicy.Builder builder = new ZenPolicy.Builder() .allowCalls(allowCalls - ? ZenModeConfig.getZenPolicySenders(allowCallsFrom) + ? notificationPolicySendersToZenPolicyPeopleType(allowCallsFrom) : ZenPolicy.PEOPLE_TYPE_NONE) .allowRepeatCallers(allowRepeatCallers) .allowMessages(allowMessages - ? ZenModeConfig.getZenPolicySenders(allowMessagesFrom) + ? notificationPolicySendersToZenPolicyPeopleType(allowMessagesFrom) : ZenPolicy.PEOPLE_TYPE_NONE) .allowReminders(allowReminders) .allowEvents(allowEvents) @@ -1333,14 +1336,14 @@ public class ZenModeConfig implements Parcelable { if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_MESSAGES, isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_MESSAGES, defaultPolicy))) { priorityCategories |= Policy.PRIORITY_CATEGORY_MESSAGES; - messageSenders = getNotificationPolicySenders(zenPolicy.getPriorityMessageSenders(), - messageSenders); + messageSenders = zenPolicyPeopleTypeToNotificationPolicySenders( + zenPolicy.getPriorityMessageSenders(), messageSenders); } if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_CONVERSATIONS, isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_CONVERSATIONS, defaultPolicy))) { priorityCategories |= Policy.PRIORITY_CATEGORY_CONVERSATIONS; - conversationSenders = getConversationSendersWithDefault( + conversationSenders = zenPolicyConversationSendersToNotificationPolicy( zenPolicy.getPriorityConversationSenders(), conversationSenders); } else { conversationSenders = CONVERSATION_SENDERS_NONE; @@ -1349,8 +1352,8 @@ public class ZenModeConfig implements Parcelable { if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_CALLS, isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_CALLS, defaultPolicy))) { priorityCategories |= Policy.PRIORITY_CATEGORY_CALLS; - callSenders = getNotificationPolicySenders(zenPolicy.getPriorityCallSenders(), - callSenders); + callSenders = zenPolicyPeopleTypeToNotificationPolicySenders( + zenPolicy.getPriorityCallSenders(), callSenders); } if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_REPEAT_CALLERS, @@ -1449,47 +1452,6 @@ public class ZenModeConfig implements Parcelable { return (policy.suppressedVisualEffects & visualEffect) == 0; } - private static int getNotificationPolicySenders(@ZenPolicy.PeopleType int senders, - int defaultPolicySender) { - switch (senders) { - case ZenPolicy.PEOPLE_TYPE_ANYONE: - return Policy.PRIORITY_SENDERS_ANY; - case ZenPolicy.PEOPLE_TYPE_CONTACTS: - return Policy.PRIORITY_SENDERS_CONTACTS; - case ZenPolicy.PEOPLE_TYPE_STARRED: - return Policy.PRIORITY_SENDERS_STARRED; - default: - return defaultPolicySender; - } - } - - private static int getConversationSendersWithDefault(@ZenPolicy.ConversationSenders int senders, - int defaultPolicySender) { - switch (senders) { - case ZenPolicy.CONVERSATION_SENDERS_ANYONE: - case ZenPolicy.CONVERSATION_SENDERS_IMPORTANT: - case ZenPolicy.CONVERSATION_SENDERS_NONE: - return senders; - default: - return defaultPolicySender; - } - } - - /** - * Maps NotificationManager.Policy senders type to ZenPolicy.PeopleType - */ - public static @ZenPolicy.PeopleType int getZenPolicySenders(int senders) { - switch (senders) { - case Policy.PRIORITY_SENDERS_ANY: - return ZenPolicy.PEOPLE_TYPE_ANYONE; - case Policy.PRIORITY_SENDERS_CONTACTS: - return ZenPolicy.PEOPLE_TYPE_CONTACTS; - case Policy.PRIORITY_SENDERS_STARRED: - default: - return ZenPolicy.PEOPLE_TYPE_STARRED; - } - } - public Policy toNotificationPolicy() { int priorityCategories = 0; int priorityCallSenders = Policy.PRIORITY_SENDERS_CONTACTS; @@ -1524,7 +1486,7 @@ public class ZenModeConfig implements Parcelable { } priorityCallSenders = sourceToPrioritySenders(allowCallsFrom, priorityCallSenders); priorityMessageSenders = sourceToPrioritySenders(allowMessagesFrom, priorityMessageSenders); - priorityConversationSenders = getConversationSendersWithDefault( + priorityConversationSenders = zenPolicyConversationSendersToNotificationPolicy( allowConversationsFrom, priorityConversationSenders); int state = areChannelsBypassingDnd ? Policy.STATE_CHANNELS_BYPASSING_DND : 0; @@ -1559,15 +1521,6 @@ public class ZenModeConfig implements Parcelable { } } - private static int prioritySendersToSource(int prioritySenders, int def) { - switch (prioritySenders) { - case Policy.PRIORITY_SENDERS_CONTACTS: return SOURCE_CONTACT; - case Policy.PRIORITY_SENDERS_STARRED: return SOURCE_STAR; - case Policy.PRIORITY_SENDERS_ANY: return SOURCE_ANYONE; - default: return def; - } - } - private static int normalizePrioritySenders(int prioritySenders, int def) { if (!(prioritySenders == Policy.PRIORITY_SENDERS_CONTACTS || prioritySenders == Policy.PRIORITY_SENDERS_STARRED diff --git a/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl b/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl index bbb4bc6c0272..e44c69c4df28 100644 --- a/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl +++ b/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl @@ -26,6 +26,7 @@ import android.app.ondeviceintelligence.IFeatureCallback; import android.app.ondeviceintelligence.IListFeaturesCallback; import android.app.ondeviceintelligence.IFeatureDetailsCallback; import com.android.internal.infra.AndroidFuture; +import android.service.ondeviceintelligence.IRemoteProcessingService; /** @@ -41,4 +42,5 @@ oneway interface IOnDeviceIntelligenceService { void getReadOnlyFileDescriptor(in String fileName, in AndroidFuture<ParcelFileDescriptor> future); void getReadOnlyFeatureFileDescriptorMap(in Feature feature, in RemoteCallback remoteCallback); void requestFeatureDownload(in Feature feature, in ICancellationSignal cancellationSignal, in IDownloadCallback downloadCallback); + void registerRemoteServices(in IRemoteProcessingService remoteProcessingService); }
\ No newline at end of file diff --git a/core/java/android/service/ondeviceintelligence/IOnDeviceTrustedInferenceService.aidl b/core/java/android/service/ondeviceintelligence/IOnDeviceTrustedInferenceService.aidl index 08eb9278fcc4..e3fda04e6592 100644 --- a/core/java/android/service/ondeviceintelligence/IOnDeviceTrustedInferenceService.aidl +++ b/core/java/android/service/ondeviceintelligence/IOnDeviceTrustedInferenceService.aidl @@ -23,8 +23,10 @@ import android.app.ondeviceintelligence.IProcessingSignal; import android.app.ondeviceintelligence.Content; import android.app.ondeviceintelligence.Feature; import android.os.ICancellationSignal; +import android.os.PersistableBundle; +import android.os.Bundle; import android.service.ondeviceintelligence.IRemoteStorageService; - +import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback; /** * Interface for a concrete implementation to provide on device trusted inference. @@ -41,4 +43,6 @@ oneway interface IOnDeviceTrustedInferenceService { void processRequestStreaming(in Feature feature, in Content request, in int requestType, in ICancellationSignal cancellationSignal, in IProcessingSignal processingSignal, in IStreamingResponseCallback callback); + void updateProcessingState(in Bundle processingState, + in IProcessingUpdateStatusCallback callback); }
\ No newline at end of file diff --git a/core/java/android/service/ondeviceintelligence/IProcessingUpdateStatusCallback.aidl b/core/java/android/service/ondeviceintelligence/IProcessingUpdateStatusCallback.aidl new file mode 100644 index 000000000000..7ead8690abb4 --- /dev/null +++ b/core/java/android/service/ondeviceintelligence/IProcessingUpdateStatusCallback.aidl @@ -0,0 +1,14 @@ +package android.service.ondeviceintelligence; + +import android.os.PersistableBundle; + +/** + * Interface for receiving status from a updateProcessingState call from on-device intelligence + * service. + * + * @hide + */ +interface IProcessingUpdateStatusCallback { + void onSuccess(in PersistableBundle statusParams) = 1; + void onFailure(int errorCode, in String errorMessage) = 2; +} diff --git a/core/java/android/service/ondeviceintelligence/IRemoteProcessingService.aidl b/core/java/android/service/ondeviceintelligence/IRemoteProcessingService.aidl new file mode 100644 index 000000000000..32a8a6a70406 --- /dev/null +++ b/core/java/android/service/ondeviceintelligence/IRemoteProcessingService.aidl @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2022 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.service.ondeviceintelligence; + +import android.os.Bundle; +import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback; + + +/** + * Interface for a concrete implementation to provide methods to update state of a remote service. + * + * @hide + */ +oneway interface IRemoteProcessingService { + void updateProcessingState(in Bundle processingState, + in IProcessingUpdateStatusCallback callback); +} diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java index 0cba1d37721a..46ba25d40bec 100644 --- a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java +++ b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java @@ -18,6 +18,7 @@ package android.service.ondeviceintelligence; import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE; +import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; @@ -47,11 +48,18 @@ import android.util.Slog; import com.android.internal.infra.AndroidFuture; +import androidx.annotation.IntDef; + import java.io.File; import java.io.FileNotFoundException; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.Executor; import java.util.function.Consumer; import java.util.function.LongConsumer; @@ -64,6 +72,10 @@ import java.util.function.LongConsumer; * {@code config_defaultOnDeviceIntelligenceService}. If this config has no value, a stub is * returned. * + * <p> Similar to {@link OnDeviceIntelligenceManager} class, the contracts in this service are + * defined to be open-ended in general, to allow interoperability. Therefore, it is recommended + * that implementations of this system-service expose this API to the clients via a library which + * has more defined contract.</p> * <pre> * {@literal * <service android:name=".SampleOnDeviceIntelligenceService" @@ -78,6 +90,8 @@ import java.util.function.LongConsumer; public abstract class OnDeviceIntelligenceService extends Service { private static final String TAG = OnDeviceIntelligenceService.class.getSimpleName(); + private volatile IRemoteProcessingService mRemoteProcessingService; + /** * The {@link Intent} that must be declared as handled by the service. To be supported, the * service must also require the @@ -88,6 +102,7 @@ public abstract class OnDeviceIntelligenceService extends Service { public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceIntelligenceService"; + /** * @hide */ @@ -167,12 +182,58 @@ public abstract class OnDeviceIntelligenceService extends Service { remoteCallback.sendResult(bundle); }); } + + @Override + public void registerRemoteServices( + IRemoteProcessingService remoteProcessingService) { + mRemoteProcessingService = remoteProcessingService; + } }; } Slog.w(TAG, "Incorrect service interface, returning null."); return null; } + /** + * Invoked by the {@link OnDeviceIntelligenceService} inorder to send updates to the inference + * service if there is a state change to be performed. + * + * @param processingState the updated state to be applied. + * @param callbackExecutor executor to the run status callback on. + * @param statusReceiver receiver to get status of the update state operation. + */ + public final void updateProcessingState(@NonNull Bundle processingState, + @NonNull @CallbackExecutor Executor callbackExecutor, + @NonNull OutcomeReceiver<PersistableBundle, OnDeviceUpdateProcessingException> statusReceiver) { + Objects.requireNonNull(callbackExecutor); + if (mRemoteProcessingService == null) { + throw new IllegalStateException("Remote processing service is unavailable."); + } + try { + mRemoteProcessingService.updateProcessingState(processingState, + new IProcessingUpdateStatusCallback.Stub() { + @Override + public void onSuccess(PersistableBundle result) { + Binder.withCleanCallingIdentity(() -> { + callbackExecutor.execute( + () -> statusReceiver.onResult(result)); + }); + } + + @Override + public void onFailure(int errorCode, String errorMessage) { + Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( + () -> statusReceiver.onError( + new OnDeviceUpdateProcessingException( + errorCode, errorMessage)))); + } + }); + } catch (RemoteException e) { + Slog.e(TAG, "Error in updateProcessingState: " + e); + throw new RuntimeException(e); + } + } + private OutcomeReceiver<Feature, OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> wrapFeatureCallback( IFeatureCallback featureCallback) { @@ -197,7 +258,6 @@ public abstract class OnDeviceIntelligenceService extends Service { } } }; - } private OutcomeReceiver<List<Feature>, @@ -380,4 +440,60 @@ public abstract class OnDeviceIntelligenceService extends Service { * @param versionConsumer consumer to populate the version. */ public abstract void onGetVersion(@NonNull LongConsumer versionConsumer); + + + /** + * Exception type to be populated when calls to {@link #updateProcessingState} fail. + */ + public static class OnDeviceUpdateProcessingException extends + OnDeviceIntelligenceServiceException { + /** + * The connection to remote service failed and the processing state could not be updated. + */ + public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 1; + + + /** + * @hide + */ + @IntDef(value = { + PROCESSING_UPDATE_STATUS_CONNECTION_FAILED + }, open = true) + @Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER, + ElementType.FIELD}) + @Retention(RetentionPolicy.SOURCE) + public @interface ErrorCode { + } + + public OnDeviceUpdateProcessingException(@ErrorCode int errorCode) { + super(errorCode); + } + + public OnDeviceUpdateProcessingException(@ErrorCode int errorCode, + @NonNull String errorMessage) { + super(errorCode, errorMessage); + } + } + + /** + * Exception type to be used for surfacing errors to service implementation. + */ + public abstract static class OnDeviceIntelligenceServiceException extends Exception { + private final int mErrorCode; + + public OnDeviceIntelligenceServiceException(int errorCode) { + this.mErrorCode = errorCode; + } + + public OnDeviceIntelligenceServiceException(int errorCode, + @NonNull String errorMessage) { + super(errorMessage); + this.mErrorCode = errorCode; + } + + public int getErrorCode() { + return mErrorCode; + } + + } } diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceTrustedInferenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceTrustedInferenceService.java index 96982e3d7829..86001975cc09 100644 --- a/core/java/android/service/ondeviceintelligence/OnDeviceTrustedInferenceService.java +++ b/core/java/android/service/ondeviceintelligence/OnDeviceTrustedInferenceService.java @@ -36,13 +36,16 @@ import android.app.ondeviceintelligence.ProcessingSignal; import android.app.ondeviceintelligence.StreamingResponseReceiver; import android.content.Context; import android.content.Intent; +import android.os.Bundle; import android.os.CancellationSignal; import android.os.IBinder; import android.os.ICancellationSignal; import android.os.OutcomeReceiver; import android.os.ParcelFileDescriptor; +import android.os.PersistableBundle; import android.os.RemoteCallback; import android.os.RemoteException; +import android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException; import android.util.Log; import android.util.Slog; @@ -65,6 +68,11 @@ import java.util.function.Consumer; * non-streaming fashion. Also, provides a way to register a storage service that will be used to * read-only access files from the {@link OnDeviceIntelligenceService} counterpart. </p> * + * <p> Similar to {@link OnDeviceIntelligenceManager} class, the contracts in this service are + * defined to be open-ended in general, to allow interoperability. Therefore, it is recommended + * that implementations of this system-service expose this API to the clients via a library which + * has more defined contract.</p> + * * <pre> * {@literal * <service android:name=".SampleTrustedInferenceService" @@ -152,6 +160,17 @@ public abstract class OnDeviceTrustedInferenceService extends Service { wrapResponseCallback(callback) ); } + + @Override + public void updateProcessingState(Bundle processingState, + IProcessingUpdateStatusCallback callback) { + Objects.requireNonNull(processingState); + Objects.requireNonNull(callback); + + OnDeviceTrustedInferenceService.this.onUpdateProcessingState(processingState, + wrapOutcomeReceiver(callback) + ); + } }; } Slog.w(TAG, "Incorrect service interface, returning null."); @@ -233,6 +252,21 @@ public abstract class OnDeviceTrustedInferenceService extends Service { @NonNull OutcomeReceiver<Content, OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> callback); + + /** + * Invoked when processing environment needs to be updated or refreshed with fresh + * configuration, files or state. + * + * @param processingState contains updated state and params that are to be applied to the + * processing environmment, + * @param callback callback to populate the update status and if there are params + * associated with the status. + */ + public abstract void onUpdateProcessingState(@NonNull Bundle processingState, + @NonNull OutcomeReceiver<PersistableBundle, + OnDeviceUpdateProcessingException> callback); + + /** * Overrides {@link Context#openFileInput} to read files with the given file names under the * internal app storage of the {@link OnDeviceIntelligenceService}, i.e., only files stored in @@ -407,4 +441,31 @@ public abstract class OnDeviceTrustedInferenceService extends Service { } }; } + + @NonNull + private static OutcomeReceiver<PersistableBundle, OnDeviceUpdateProcessingException> wrapOutcomeReceiver( + IProcessingUpdateStatusCallback callback) { + return new OutcomeReceiver<>() { + @Override + public void onResult(@NonNull PersistableBundle result) { + try { + callback.onSuccess(result); + } catch (RemoteException e) { + Slog.e(TAG, "Error sending result: " + e); + + } + } + + @Override + public void onError( + @androidx.annotation.NonNull OnDeviceUpdateProcessingException error) { + try { + callback.onFailure(error.getErrorCode(), error.getMessage()); + } catch (RemoteException e) { + Slog.e(TAG, "Error sending exception details: " + e); + } + } + }; + } + } diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index 20adc5427495..306410c9a98b 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -519,7 +519,7 @@ public class VoiceInteractionService extends Service { @NonNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale, @NonNull @CallbackExecutor Executor executor, @NonNull AlwaysOnHotwordDetector.Callback callback) { - // TODO(b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning + // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning Objects.requireNonNull(keyphrase); Objects.requireNonNull(locale); @@ -545,10 +545,6 @@ public class VoiceInteractionService extends Service { @NonNull SoundTrigger.ModuleProperties moduleProperties, @NonNull @CallbackExecutor Executor executor, @NonNull AlwaysOnHotwordDetector.Callback callback) { - // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the - // {@link #createAlwaysOnHotwordDetectorForTest(String, Locale, - // SoundTrigger.ModuleProperties, AlwaysOnHotwordDetector.Callback)} and replace with the - // permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched. Objects.requireNonNull(keyphrase); Objects.requireNonNull(locale); @@ -615,11 +611,6 @@ public class VoiceInteractionService extends Service { @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) { - // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the - // {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory, - // AlwaysOnHotwordDetector.Callback)} and replace with the permission - // RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched. - return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, /* supportHotwordDetectionService= */ true, options, sharedMemory, /* modulProperties */ null, /* executor= */ null, callback); @@ -671,11 +662,7 @@ public class VoiceInteractionService extends Service { @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @NonNull @CallbackExecutor Executor executor, @NonNull AlwaysOnHotwordDetector.Callback callback) { - // TODO(b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning - // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the - // {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory, - // Executor, AlwaysOnHotwordDetector.Callback)} and replace with the permission - // RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched. + // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning Objects.requireNonNull(keyphrase); Objects.requireNonNull(locale); @@ -702,10 +689,6 @@ public class VoiceInteractionService extends Service { @NonNull SoundTrigger.ModuleProperties moduleProperties, @NonNull @CallbackExecutor Executor executor, @NonNull AlwaysOnHotwordDetector.Callback callback) { - // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the - // {@link #createAlwaysOnHotwordDetectorForTest(String, Locale, PersistableBundle, - // SharedMemory, SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)} - // and replace with the permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched. Objects.requireNonNull(keyphrase); Objects.requireNonNull(locale); diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl index 11180aef4479..5ee526e0343d 100644 --- a/core/java/android/view/IWindow.aidl +++ b/core/java/android/view/IWindow.aidl @@ -73,7 +73,7 @@ oneway interface IWindow { * * @param types internal insets types (WindowInsets.Type.InsetsType) to show * @param fromIme true if this request originated from IME (InputMethodService). - * @param statsToken the token tracking the current IME show request or {@code null} otherwise. + * @param statsToken the token tracking the current IME request or {@code null} otherwise. */ void showInsets(int types, boolean fromIme, in @nullable ImeTracker.Token statsToken); @@ -82,7 +82,7 @@ oneway interface IWindow { * * @param types internal insets types (WindowInsets.Type.InsetsType) to hide * @param fromIme true if this request originated from IME (InputMethodService). - * @param statsToken the token tracking the current IME hide request or {@code null} otherwise. + * @param statsToken the token tracking the current IME request or {@code null} otherwise. */ void hideInsets(int types, boolean fromIme, in @nullable ImeTracker.Token statsToken); diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java index de809c8489fd..821e13d85370 100644 --- a/core/java/android/view/ImeInsetsSourceConsumer.java +++ b/core/java/android/view/ImeInsetsSourceConsumer.java @@ -21,9 +21,9 @@ import static android.view.ImeInsetsSourceConsumerProto.HAS_PENDING_REQUEST; import static android.view.ImeInsetsSourceConsumerProto.INSETS_SOURCE_CONSUMER; import static android.view.ImeInsetsSourceConsumerProto.IS_REQUESTED_VISIBLE_AWAITING_CONTROL; +import android.annotation.NonNull; import android.annotation.Nullable; import android.os.IBinder; -import android.os.Process; import android.os.Trace; import android.util.proto.ProtoOutputStream; import android.view.SurfaceControl.Transaction; @@ -70,7 +70,11 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { if (!isShowRequested()) { mIsRequestedVisibleAwaitingControl = false; if (!running && !mHasPendingRequest) { - notifyHidden(null /* statsToken */); + final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE, + ImeTracker.ORIGIN_CLIENT, + SoftInputShowHideReason.HIDE_SOFT_INPUT_ON_ANIMATION_STATE_CHANGED, + mController.getHost().isHandlingPointerEvent() /* fromUser */); + notifyHidden(statsToken); removeSurface(); } } @@ -144,9 +148,17 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { void requestHide(boolean fromIme, @Nullable ImeTracker.Token statsToken) { if (!fromIme) { + // Create a new token to track the hide request when we have control, + // as we use the passed in token for the insets animation already. + final var notifyStatsToken = getControl() != null + ? ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE, + ImeTracker.ORIGIN_CLIENT, + SoftInputShowHideReason.HIDE_SOFT_INPUT_REQUEST_HIDE_WITH_CONTROL, + mController.getHost().isHandlingPointerEvent() /* fromUser */) + : statsToken; // The insets might be controlled by a remote target. Let the server know we are // requested to hide. - notifyHidden(statsToken); + notifyHidden(notifyStatsToken); } if (mAnimationState == ANIMATION_STATE_SHOW) { mHasPendingRequest = true; @@ -157,21 +169,9 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { * Notify {@link com.android.server.inputmethod.InputMethodManagerService} that * IME insets are hidden. * - * @param statsToken the token tracking the current IME hide request or {@code null} otherwise. + * @param statsToken the token tracking the current IME request or {@code null} otherwise. */ - private void notifyHidden(@Nullable ImeTracker.Token statsToken) { - // Create a new stats token to track the hide request when: - // - we do not already have one, or - // - we do already have one, but we have control and use the passed in token - // for the insets animation already. - if (statsToken == null || getControl() != null) { - statsToken = ImeTracker.forLogging().onRequestHide(null /* component */, - Process.myUid(), - ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, - SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API, - mController.getHost().isHandlingPointerEvent() /* fromUser */); - } - + private void notifyHidden(@NonNull ImeTracker.Token statsToken) { ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_INSETS_CONSUMER_NOTIFY_HIDDEN); diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index 7f1e037e92d4..85c779bc8c79 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -43,7 +43,6 @@ import static android.view.WindowInsets.Type.ime; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY; -import static android.view.inputmethod.ImeTracker.TOKEN_NONE; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; @@ -165,9 +164,9 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro mStatsToken = statsToken; if (DEBUG_IME_VISIBILITY && (types & ime()) != 0) { EventLog.writeEvent(IMF_IME_ANIM_START, - mStatsToken != null ? mStatsToken.getTag() : TOKEN_NONE, mAnimationType, - mCurrentAlpha, "Current:" + mCurrentInsets, "Shown:" + mShownInsets, - "Hidden:" + mHiddenInsets); + mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE, + mAnimationType, mCurrentAlpha, "Current:" + mCurrentInsets, + "Shown:" + mShownInsets, "Hidden:" + mHiddenInsets); } mController.startAnimation(this, listener, types, mAnimation, new Bounds(mHiddenInsets, mShownInsets)); @@ -245,6 +244,7 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro } @Override + @Nullable public ImeTracker.Token getStatsToken() { return mStatsToken; } @@ -330,8 +330,8 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro mListener.onFinished(this); if (DEBUG_IME_VISIBILITY && (mTypes & ime()) != 0) { EventLog.writeEvent(IMF_IME_ANIM_FINISH, - mStatsToken != null ? mStatsToken.getTag() : TOKEN_NONE, mAnimationType, - mCurrentAlpha, shown ? 1 : 0, Objects.toString(insets)); + mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE, + mAnimationType, mCurrentAlpha, shown ? 1 : 0, Objects.toString(insets)); } } @@ -355,8 +355,8 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro if (DEBUG) Log.d(TAG, "notify Control request cancelled for types: " + mTypes); if (DEBUG_IME_VISIBILITY && (mTypes & ime()) != 0) { EventLog.writeEvent(IMF_IME_ANIM_CANCEL, - mStatsToken != null ? mStatsToken.getTag() : TOKEN_NONE, mAnimationType, - Objects.toString(mPendingInsets)); + mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE, + mAnimationType, Objects.toString(mPendingInsets)); } releaseLeashes(); } diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java index 079991a81e77..92e20e09d8c4 100644 --- a/core/java/android/view/InsetsAnimationThreadControlRunner.java +++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java @@ -137,6 +137,7 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro } @Override + @Nullable public ImeTracker.Token getStatsToken() { return mControl.getStatsToken(); } diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 1803a6ed237f..6cc4b20dcde9 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -28,7 +28,6 @@ import static android.view.WindowInsets.Type.LAST; import static android.view.WindowInsets.Type.all; import static android.view.WindowInsets.Type.captionBar; import static android.view.WindowInsets.Type.ime; -import static android.view.inputmethod.ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL; import android.animation.AnimationHandler; import android.animation.Animator; @@ -47,7 +46,6 @@ import android.graphics.Rect; import android.os.CancellationSignal; import android.os.Handler; import android.os.IBinder; -import android.os.Process; import android.os.Trace; import android.text.TextUtils; import android.util.IntArray; @@ -659,6 +657,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private final Runnable mAnimCallback; /** Pending control request that is waiting on IME to be ready to be shown */ + @Nullable private PendingControlRequest mPendingImeControlRequest; private int mWindowType; @@ -1043,12 +1042,18 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation hideTypes[0] &= ~animatingTypes; if (showTypes[0] != 0) { - applyAnimation(showTypes[0], true /* show */, false /* fromIme */, - null /* statsToken */); + final var statsToken = (showTypes[0] & ime()) == 0 ? null + : ImeTracker.forLogging().onStart(ImeTracker.TYPE_SHOW, + ImeTracker.ORIGIN_CLIENT, SoftInputShowHideReason.CONTROLS_CHANGED, + mHost.isHandlingPointerEvent() /* fromUser */); + applyAnimation(showTypes[0], true /* show */, false /* fromIme */, statsToken); } if (hideTypes[0] != 0) { - applyAnimation(hideTypes[0], false /* show */, false /* fromIme */, - null /* statsToken */); + final var statsToken = (hideTypes[0] & ime()) == 0 ? null + : ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE, + ImeTracker.ORIGIN_CLIENT, SoftInputShowHideReason.CONTROLS_CHANGED, + mHost.isHandlingPointerEvent() /* fromUser */); + applyAnimation(hideTypes[0], false /* show */, false /* fromIme */, statsToken); } if (mControllableTypes != controllableTypes) { @@ -1064,15 +1069,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @Override public void show(@InsetsType int types) { - ImeTracker.Token statsToken = null; - if ((types & ime()) != 0) { - statsToken = ImeTracker.forLogging().onRequestShow(null /* component */, - Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT, - SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API, - mHost.isHandlingPointerEvent() /* fromUser */); - } - - show(types, false /* fromIme */, statsToken); + show(types, false /* fromIme */, null /* statsToken */); } @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) @@ -1080,6 +1077,13 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @Nullable ImeTracker.Token statsToken) { if ((types & ime()) != 0) { Log.d(TAG, "show(ime(), fromIme=" + fromIme + ")"); + + if (statsToken == null) { + statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_SHOW, + ImeTracker.ORIGIN_CLIENT, + SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API, + mHost.isHandlingPointerEvent() /* fromUser */); + } } if (fromIme) { ImeTracing.getInstance().triggerClientDump("InsetsController#show", @@ -1148,9 +1152,11 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } /** - * Handle the {@link #mPendingImeControlRequest} when - * - The IME insets is ready to show. - * - The IME insets has being requested invisible. + * Handle the {@link #mPendingImeControlRequest} when: + * <ul> + * <li> The IME insets is ready to show. + * <li> The IME insets has being requested invisible. + * </ul> */ private void handlePendingControlRequest(@Nullable ImeTracker.Token statsToken) { PendingControlRequest pendingRequest = mPendingImeControlRequest; @@ -1170,20 +1176,22 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @Override public void hide(@InsetsType int types) { - ImeTracker.Token statsToken = null; - if ((types & ime()) != 0) { - statsToken = ImeTracker.forLogging().onRequestHide(null /* component */, - Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, - SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API, - mHost.isHandlingPointerEvent() /* fromUser */); - } - - hide(types, false /* fromIme */, statsToken); + hide(types, false /* fromIme */, null /* statsToken */); } @VisibleForTesting public void hide(@InsetsType int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) { + if ((types & ime()) != 0) { + Log.d(TAG, "hide(ime(), fromIme=" + fromIme + ")"); + + if (statsToken == null) { + statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE, + ImeTracker.ORIGIN_CLIENT, + SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API, + mHost.isHandlingPointerEvent() /* fromUser */); + } + } if (fromIme) { ImeTracing.getInstance().triggerClientDump("InsetsController#hide", mHost.getInputMethodManager(), null /* icProto */); @@ -1307,10 +1315,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation if (monitoredAnimation && (types & Type.ime()) != 0) { if (animationType == ANIMATION_TYPE_SHOW) { ImeTracker.forLatency().onShowCancelled(statsToken, - PHASE_CLIENT_ANIMATION_CANCEL, ActivityThread::currentApplication); + ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL, + ActivityThread::currentApplication); } else { ImeTracker.forLatency().onHideCancelled(statsToken, - PHASE_CLIENT_ANIMATION_CANCEL, ActivityThread::currentApplication); + ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL, + ActivityThread::currentApplication); } ImeTracker.forLogging().onCancelled(statsToken, ImeTracker.PHASE_CLIENT_CONTROL_ANIMATION); @@ -1602,12 +1612,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private void cancelAnimation(InsetsAnimationControlRunner control, boolean invokeCallback) { if (invokeCallback) { ImeTracker.forLogging().onCancelled(control.getStatsToken(), - PHASE_CLIENT_ANIMATION_CANCEL); + ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL); control.cancel(); } else { // Succeeds if invokeCallback is false (i.e. when called from notifyFinished). ImeTracker.forLogging().onProgress(control.getStatsToken(), - PHASE_CLIENT_ANIMATION_CANCEL); + ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL); } if (DEBUG) { Log.d(TAG, TextUtils.formatSimple( diff --git a/core/java/android/view/InsetsResizeAnimationRunner.java b/core/java/android/view/InsetsResizeAnimationRunner.java index bffaeea6a731..ebdddd537ae3 100644 --- a/core/java/android/view/InsetsResizeAnimationRunner.java +++ b/core/java/android/view/InsetsResizeAnimationRunner.java @@ -29,6 +29,7 @@ import static android.view.InsetsController.ANIMATION_TYPE_RESIZE; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; +import android.annotation.Nullable; import android.graphics.Insets; import android.graphics.Rect; import android.util.SparseArray; @@ -92,6 +93,7 @@ public class InsetsResizeAnimationRunner implements InsetsAnimationControlRunner } @Override + @Nullable public ImeTracker.Token getStatsToken() { // Return null as resizing the IME view is not explicitly tracked. return null; diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index 0ce61bb17774..fdb2a6ee1791 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -314,7 +314,7 @@ public class InsetsSourceConsumer { * @param fromController {@code true} if request is coming from controller. * (e.g. in IME case, controller is * {@link android.inputmethodservice.InputMethodService}). - * @param statsToken the token tracking the current IME show request or {@code null} otherwise. + * @param statsToken the token tracking the current IME request or {@code null} otherwise. * * @implNote The {@code statsToken} is ignored here, and only handled in * {@link ImeInsetsSourceConsumer} for IME animations only. diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index 68e8c726a209..67a397828fa0 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -1733,6 +1733,8 @@ public final class MotionEvent extends InputEvent implements Parcelable { private static native long nativeCopy(long destNativePtr, long sourceNativePtr, boolean keepHistory); @CriticalNative + private static native long nativeSplit(long destNativePtr, long sourceNativePtr, int idBits); + @CriticalNative private static native int nativeGetId(long nativePtr); @CriticalNative private static native int nativeGetDeviceId(long nativePtr); @@ -3767,86 +3769,23 @@ public final class MotionEvent extends InputEvent implements Parcelable { } /** - * Splits a motion event such that it includes only a subset of pointer ids. + * Splits a motion event such that it includes only a subset of pointer IDs. + * @param idBits the bitset indicating all of the pointer IDs from this motion event that should + * be in the new split event. idBits must be a non-empty subset of the pointer IDs + * contained in this event. * @hide */ + // TODO(b/327503168): Pass downTime as a parameter to split. @UnsupportedAppUsage + @NonNull public final MotionEvent split(int idBits) { - MotionEvent ev = obtain(); - synchronized (gSharedTempLock) { - final int oldPointerCount = nativeGetPointerCount(mNativePtr); - ensureSharedTempPointerCapacity(oldPointerCount); - final PointerProperties[] pp = gSharedTempPointerProperties; - final PointerCoords[] pc = gSharedTempPointerCoords; - final int[] map = gSharedTempPointerIndexMap; - - final int oldAction = nativeGetAction(mNativePtr); - final int oldActionMasked = oldAction & ACTION_MASK; - final int oldActionPointerIndex = (oldAction & ACTION_POINTER_INDEX_MASK) - >> ACTION_POINTER_INDEX_SHIFT; - int newActionPointerIndex = -1; - int newPointerCount = 0; - for (int i = 0; i < oldPointerCount; i++) { - nativeGetPointerProperties(mNativePtr, i, pp[newPointerCount]); - final int idBit = 1 << pp[newPointerCount].id; - if ((idBit & idBits) != 0) { - if (i == oldActionPointerIndex) { - newActionPointerIndex = newPointerCount; - } - map[newPointerCount] = i; - newPointerCount += 1; - } - } - - if (newPointerCount == 0) { - throw new IllegalArgumentException("idBits did not match any ids in the event"); - } - - final int newAction; - if (oldActionMasked == ACTION_POINTER_DOWN || oldActionMasked == ACTION_POINTER_UP) { - if (newActionPointerIndex < 0) { - // An unrelated pointer changed. - newAction = ACTION_MOVE; - } else if (newPointerCount == 1) { - // The first/last pointer went down/up. - newAction = oldActionMasked == ACTION_POINTER_DOWN - ? ACTION_DOWN - : (getFlags() & FLAG_CANCELED) == 0 ? ACTION_UP : ACTION_CANCEL; - } else { - // A secondary pointer went down/up. - newAction = oldActionMasked - | (newActionPointerIndex << ACTION_POINTER_INDEX_SHIFT); - } - } else { - // Simple up/down/cancel/move or other motion action. - newAction = oldAction; - } - - final int historySize = nativeGetHistorySize(mNativePtr); - for (int h = 0; h <= historySize; h++) { - final int historyPos = h == historySize ? HISTORY_CURRENT : h; - - for (int i = 0; i < newPointerCount; i++) { - nativeGetPointerCoords(mNativePtr, map[i], historyPos, pc[i]); - } - - final long eventTimeNanos = nativeGetEventTimeNanos(mNativePtr, historyPos); - if (h == 0) { - ev.initialize(nativeGetDeviceId(mNativePtr), nativeGetSource(mNativePtr), - nativeGetDisplayId(mNativePtr), - newAction, nativeGetFlags(mNativePtr), - nativeGetEdgeFlags(mNativePtr), nativeGetMetaState(mNativePtr), - nativeGetButtonState(mNativePtr), nativeGetClassification(mNativePtr), - nativeGetXOffset(mNativePtr), nativeGetYOffset(mNativePtr), - nativeGetXPrecision(mNativePtr), nativeGetYPrecision(mNativePtr), - nativeGetDownTimeNanos(mNativePtr), eventTimeNanos, - newPointerCount, pp, pc); - } else { - nativeAddBatch(ev.mNativePtr, eventTimeNanos, pc, 0); - } - } - return ev; + if (idBits == 0) { + throw new IllegalArgumentException( + "idBits must contain at least one pointer from this motion event"); } + MotionEvent event = obtain(); + event.mNativePtr = nativeSplit(event.mNativePtr, this.mNativePtr, idBits); + return event; } /** diff --git a/core/java/android/view/ScrollFeedbackProvider.java b/core/java/android/view/ScrollFeedbackProvider.java index 8a44d4fa5934..798203fadb4d 100644 --- a/core/java/android/view/ScrollFeedbackProvider.java +++ b/core/java/android/view/ScrollFeedbackProvider.java @@ -21,8 +21,11 @@ import android.annotation.NonNull; import android.view.flags.Flags; /** - * Interface to represent an entity giving consistent feedback for different events surrounding view - * scroll. + * Provides feedback to the user for scroll events on a {@link View}. The type of feedback provided + * to the user may depend on the {@link InputDevice} that generated the scroll events. + * + * <p>An example of the type of feedback that this interface may provide is haptic feedback (that + * is, tactile feedback that provide the user physical feedback for their scroll). * * <p>The interface provides methods for the client to report different scroll events. The client * should report all scroll events that they want to be considered for scroll feedback using the diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 828004b6b235..d29963c6a82b 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -25,6 +25,7 @@ import static android.view.Surface.FRAME_RATE_CATEGORY_LOW; import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL; import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE; import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE; +import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED; import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS; @@ -5629,7 +5630,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @Nullable private ViewTranslationCallback mViewTranslationCallback; - private float mFrameContentVelocity = 0; + private float mFrameContentVelocity = -1; @Nullable @@ -5660,6 +5661,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, protected long mMinusTwoFrameIntervalMillis = 0; private int mLastFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; + private float mLastFrameX = Float.NaN; + private float mLastFrameY = Float.NaN; + @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) public static final float REQUESTED_FRAME_RATE_CATEGORY_DEFAULT = Float.NaN; @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) @@ -24597,7 +24601,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public void draw(@NonNull Canvas canvas) { final int privateFlags = mPrivateFlags; mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN; - mFrameContentVelocity = 0; + + mFrameContentVelocity = -1; + mLastFrameX = mLeft + mRenderNode.getTranslationX(); + mLastFrameY = mTop + mRenderNode.getTranslationY(); /* * Draw traversal performs several drawing steps which must be executed @@ -33673,6 +33680,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (sToolkitMetricsForFrameRateDecisionFlagValue) { viewRootImpl.recordViewPercentage(sizePercentage); } + if (viewVelocityApi()) { + float velocity = mFrameContentVelocity; + if (velocity < 0f) { + velocity = calculateVelocity(); + } + if (velocity > 0f) { + float frameRate = convertVelocityToFrameRate(velocity); + viewRootImpl.votePreferredFrameRate(frameRate, FRAME_RATE_COMPATIBILITY_GTE); + return; + } + } if (!Float.isNaN(mPreferredFrameRate)) { if (mPreferredFrameRate < 0) { if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) { @@ -33695,6 +33713,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } + private float convertVelocityToFrameRate(float velocityPps) { + float density = getResources().getDisplayMetrics().density; + float velocityDps = velocityPps / density; + // Choose a frame rate in increments of 10fps + return Math.min(140f, 60f + (10f * (float) Math.floor(velocityDps / 300f))); + } + + private float calculateVelocity() { + // This current calculation is very simple. If something on the screen moved, then + // it votes for the highest velocity. If it doesn't move, then return 0. + float x = mLeft + mRenderNode.getTranslationX(); + float y = mTop + mRenderNode.getTranslationY(); + + return (!Float.isNaN(mLastFrameX) && (x != mLastFrameX || y != mLastFrameY)) + ? 100_000f : 0f; + } + /** * Set the current velocity of the View, we only track positive value. * We will use the velocity information to adjust the frame rate when applicable. diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 708751a25053..6d8524d5f2e2 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -31,6 +31,7 @@ import static android.view.Surface.FRAME_RATE_CATEGORY_LOW; import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL; import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE; import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE; +import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE; import static android.view.View.PFLAG_DRAW_ANIMATION; import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN; import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; @@ -1210,6 +1211,9 @@ public final class ViewRootImpl implements ViewParent, mSensitiveContentProtectionService = ISensitiveContentProtectionManager.Stub.asInterface( ServiceManager.getService(Context.SENSITIVE_CONTENT_PROTECTION_SERVICE)); + if (mSensitiveContentProtectionService == null) { + Log.e(TAG, "SensitiveContentProtectionService shouldn't be null"); + } } else { mSensitiveContentProtectionService = null; } @@ -4179,6 +4183,9 @@ public final class ViewRootImpl implements ViewParent, */ void notifySensitiveContentAppProtection(boolean showSensitiveContent) { try { + if (mSensitiveContentProtectionService == null) { + return; + } // The window would be blocked during screen share if it shows sensitive content. mSensitiveContentProtectionService.setSensitiveContentProtection( getWindowToken(), mContext.getPackageName(), showSensitiveContent); @@ -7562,7 +7569,8 @@ public final class ViewRootImpl implements ViewParent, } // For the variable refresh rate project - if (handled && shouldTouchBoost(action, mWindowAttributes.type)) { + if (handled && shouldTouchBoost(action & MotionEvent.ACTION_MASK, + mWindowAttributes.type)) { // set the frame rate to the maximum value. mIsTouchBoosting = true; setPreferredFrameRateCategory(mPreferredFrameRateCategory); @@ -12389,6 +12397,17 @@ public final class ViewRootImpl implements ViewParent, mFrameRateCompatibility).applyAsyncUnsafe(); mLastPreferredFrameRate = preferredFrameRate; } + if (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE && mIsTouchBoosting) { + // We've received a velocity, so we'll let the velocity control the + // frame rate unless we receive additional motion events. + mIsTouchBoosting = false; + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + Trace.instant( + Trace.TRACE_TAG_VIEW, + "ViewRootImpl#setFrameRate velocity used, no touch boost on next frame" + ); + } + } } catch (Exception e) { Log.e(mTag, "Unable to set frame rate", e); } finally { @@ -12414,9 +12433,8 @@ public final class ViewRootImpl implements ViewParent, } private boolean shouldTouchBoost(int motionEventAction, int windowType) { - boolean desiredAction = motionEventAction == MotionEvent.ACTION_DOWN - || motionEventAction == MotionEvent.ACTION_MOVE - || motionEventAction == MotionEvent.ACTION_UP; + // boost for almost all input + boolean desiredAction = motionEventAction != MotionEvent.ACTION_OUTSIDE; boolean undesiredType = windowType == TYPE_INPUT_METHOD && sToolkitFrameRateTypingReadOnlyFlagValue; // use toolkitSetFrameRate flag to gate the change @@ -12522,6 +12540,14 @@ public final class ViewRootImpl implements ViewParent, } /** + * Returns whether touch boost is currently enabled. + */ + @VisibleForTesting + public boolean getIsTouchBoosting() { + return mIsTouchBoosting; + } + + /** * Get the value of mFrameRateCompatibility */ @VisibleForTesting diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 64e5a5bb87a2..cf6b9e5b3bae 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -1160,12 +1160,10 @@ public final class AutofillManager { // denylist only applies to not important views if (!view.isImportantForAutofill() && isActivityDeniedForAutofill()) { - Log.d(TAG, "view is not autofillable - activity denied for autofill"); return false; } if (isActivityAllowedForAutofill()) { - Log.d(TAG, "view is autofillable - activity allowed for autofill"); return true; } diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java index 491b0e349cde..cedf8d04ed99 100644 --- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java +++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java @@ -25,7 +25,6 @@ import android.annotation.RequiresNoPermission; import android.annotation.RequiresPermission; import android.annotation.UserIdInt; import android.content.Context; -import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.os.ResultReceiver; @@ -298,7 +297,7 @@ final class IInputMethodManagerGlobalInvoker { @AnyThread static boolean showSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken, - @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, + @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, int lastClickToolType, @Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { final IInputMethodManager service = getService(); @@ -315,7 +314,7 @@ final class IInputMethodManagerGlobalInvoker { @AnyThread static boolean hideSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken, - @Nullable ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags, + @NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags, @Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { final IInputMethodManager service = getService(); if (service == null) { @@ -331,6 +330,20 @@ final class IInputMethodManagerGlobalInvoker { // TODO(b/293640003): Remove method once Flags.useZeroJankProxy() is enabled. @AnyThread + @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD) + static void hideSoftInputFromServerForTest() { + final IInputMethodManager service = getService(); + if (service == null) { + return; + } + try { + service.hideSoftInputFromServerForTest(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @AnyThread @NonNull @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true) static InputBindResult startInputOrWindowGainedFocus(@StartInputReason int startInputReason, @@ -654,35 +667,18 @@ final class IInputMethodManagerGlobalInvoker { } } - /** @see com.android.server.inputmethod.ImeTrackerService#onRequestShow */ - @AnyThread - @NonNull - static ImeTracker.Token onRequestShow(@NonNull String tag, int uid, - @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason, boolean fromUser) { - final IImeTracker service = getImeTrackerService(); - if (service == null) { - // Create token with "fake" binder if the service was not found. - return new ImeTracker.Token(new Binder(), tag); - } - try { - return service.onRequestShow(tag, uid, origin, reason, fromUser); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** @see com.android.server.inputmethod.ImeTrackerService#onRequestHide */ + /** @see com.android.server.inputmethod.ImeTrackerService#onStart */ @AnyThread @NonNull - static ImeTracker.Token onRequestHide(@NonNull String tag, int uid, + static ImeTracker.Token onStart(@NonNull String tag, int uid, @ImeTracker.Type int type, @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason, boolean fromUser) { - final IImeTracker service = getImeTrackerService(); + final var service = getImeTrackerService(); if (service == null) { - // Create token with "fake" binder if the service was not found. - return new ImeTracker.Token(new Binder(), tag); + // Create token with "empty" binder if the service was not found. + return ImeTracker.Token.empty(tag); } try { - return service.onRequestHide(tag, uid, origin, reason, fromUser); + return service.onStart(tag, uid, type, origin, reason, fromUser); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java index b1fdaa97ffe0..d992febc375e 100644 --- a/core/java/android/view/inputmethod/ImeTracker.java +++ b/core/java/android/view/inputmethod/ImeTracker.java @@ -28,17 +28,20 @@ import static com.android.internal.util.LatencyTracker.ACTION_REQUEST_IME_SHOWN; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.ActivityThread; import android.content.Context; +import android.os.Binder; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; +import android.os.Process; import android.os.SystemProperties; import android.util.Log; import android.view.InsetsController.AnimationType; import android.view.SurfaceControl; import android.view.View; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.annotations.VisibleForTesting.Visibility; import com.android.internal.inputmethod.InputMethodDebug; import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.internal.jank.InteractionJankMonitor; @@ -108,34 +111,32 @@ public interface ImeTracker { /** * The origin of the IME request * - * The name follows the format {@code PHASE_x_...} where {@code x} denotes - * where the origin is (i.e. {@code PHASE_SERVER_...} occurs in the server). + * <p> The name follows the format {@code ORIGIN_x_...} where {@code x} denotes + * where the origin is (i.e. {@code ORIGIN_SERVER} occurs in the server). */ @IntDef(prefix = { "ORIGIN_" }, value = { - ORIGIN_CLIENT_SHOW_SOFT_INPUT, - ORIGIN_CLIENT_HIDE_SOFT_INPUT, - ORIGIN_SERVER_START_INPUT, - ORIGIN_SERVER_HIDE_INPUT + ORIGIN_CLIENT, + ORIGIN_SERVER, + ORIGIN_IME }) @Retention(RetentionPolicy.SOURCE) @interface Origin {} - /** The IME show request originated in the client. */ - int ORIGIN_CLIENT_SHOW_SOFT_INPUT = ImeProtoEnums.ORIGIN_CLIENT_SHOW_SOFT_INPUT; + /** The IME request originated in the client. */ + int ORIGIN_CLIENT = ImeProtoEnums.ORIGIN_CLIENT; - /** The IME hide request originated in the client. */ - int ORIGIN_CLIENT_HIDE_SOFT_INPUT = ImeProtoEnums.ORIGIN_CLIENT_HIDE_SOFT_INPUT; + /** The IME request originated in the server. */ + int ORIGIN_SERVER = ImeProtoEnums.ORIGIN_SERVER; - /** The IME show request originated in the server. */ - int ORIGIN_SERVER_START_INPUT = ImeProtoEnums.ORIGIN_SERVER_START_INPUT; - - /** The IME hide request originated in the server. */ - int ORIGIN_SERVER_HIDE_INPUT = ImeProtoEnums.ORIGIN_SERVER_HIDE_INPUT; + /** The IME request originated in the IME. */ + int ORIGIN_IME = ImeProtoEnums.ORIGIN_IME; + /** The IME request originated in the WindowManager Shell. */ + int ORIGIN_WM_SHELL = ImeProtoEnums.ORIGIN_WM_SHELL; /** * The current phase of the IME request. * - * The name follows the format {@code PHASE_x_...} where {@code x} denotes + * <p> The name follows the format {@code PHASE_x_...} where {@code x} denotes * where the phase is (i.e. {@code PHASE_SERVER_...} occurs in the server). */ @IntDef(prefix = { "PHASE_" }, value = { @@ -155,7 +156,6 @@ public interface ImeTracker { PHASE_IME_SHOW_SOFT_INPUT, PHASE_IME_HIDE_SOFT_INPUT, PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE, - PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER, PHASE_SERVER_APPLY_IME_VISIBILITY, PHASE_WM_SHOW_IME_RUNNER, PHASE_WM_SHOW_IME_READY, @@ -182,6 +182,10 @@ public interface ImeTracker { PHASE_CLIENT_ANIMATION_FINISHED_SHOW, PHASE_CLIENT_ANIMATION_FINISHED_HIDE, PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT, + PHASE_IME_SHOW_WINDOW, + PHASE_IME_HIDE_WINDOW, + PHASE_IME_PRIVILEGED_OPERATIONS, + PHASE_SERVER_CURRENT_ACTIVE_IME, }) @Retention(RetentionPolicy.SOURCE) @interface Phase {} @@ -224,19 +228,15 @@ public interface ImeTracker { /** Dispatched from the IME wrapper to the IME. */ int PHASE_IME_WRAPPER_DISPATCH = ImeProtoEnums.PHASE_IME_WRAPPER_DISPATCH; - /** Reached the IME' showSoftInput method. */ + /** Reached the IME's showSoftInput method. */ int PHASE_IME_SHOW_SOFT_INPUT = ImeProtoEnums.PHASE_IME_SHOW_SOFT_INPUT; - /** Reached the IME' hideSoftInput method. */ + /** Reached the IME's hideSoftInput method. */ int PHASE_IME_HIDE_SOFT_INPUT = ImeProtoEnums.PHASE_IME_HIDE_SOFT_INPUT; /** The server decided the IME should be shown. */ int PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE = ImeProtoEnums.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE; - /** Requested applying the IME visibility in the insets source consumer. */ - int PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER = - ImeProtoEnums.PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER; - /** Applied the IME visibility. */ int PHASE_SERVER_APPLY_IME_VISIBILITY = ImeProtoEnums.PHASE_SERVER_APPLY_IME_VISIBILITY; @@ -323,37 +323,49 @@ public interface ImeTracker { int PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT = ImeProtoEnums.PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT; + /** Reached the IME's showWindow method. */ + int PHASE_IME_SHOW_WINDOW = ImeProtoEnums.PHASE_IME_SHOW_WINDOW; + + /** Reached the IME's hideWindow method. */ + int PHASE_IME_HIDE_WINDOW = ImeProtoEnums.PHASE_IME_HIDE_WINDOW; + + /** Reached the InputMethodPrivilegedOperations handler. */ + int PHASE_IME_PRIVILEGED_OPERATIONS = ImeProtoEnums.PHASE_IME_PRIVILEGED_OPERATIONS; + + /** Checked that the calling IME is the currently active IME. */ + int PHASE_SERVER_CURRENT_ACTIVE_IME = ImeProtoEnums.PHASE_SERVER_CURRENT_ACTIVE_IME; + /** - * Creates an IME show request tracking token. + * Called when an IME request is started. * - * @param component the name of the component that created the IME request, or {@code null} - * otherwise (defaulting to {@link ActivityThread#currentProcessName()}). - * @param uid the uid of the client that requested the IME. - * @param origin the origin of the IME show request. - * @param reason the reason why the IME show request was created. + * @param component the name of the component that started the request. + * @param uid the uid of the client that started the request. + * @param type the type of the request. + * @param origin the origin of the request. + * @param reason the reason for starting the request. * @param fromUser whether this request was created directly from user interaction. * - * @return An IME tracking token. + * @return An IME request tracking token. */ @NonNull - Token onRequestShow(@Nullable String component, int uid, @Origin int origin, + Token onStart(@NonNull String component, int uid, @Type int type, @Origin int origin, @SoftInputShowHideReason int reason, boolean fromUser); /** - * Creates an IME hide request tracking token. + * Called when an IME request is started for the current process. * - * @param component the name of the component that created the IME request, or {@code null} - * otherwise (defaulting to {@link ActivityThread#currentProcessName()}). - * @param uid the uid of the client that requested the IME. - * @param origin the origin of the IME hide request. - * @param reason the reason why the IME hide request was created. + * @param type the type of the request. + * @param origin the origin of the request. + * @param reason the reason for starting the request. * @param fromUser whether this request was created directly from user interaction. * - * @return An IME tracking token. + * @return An IME request tracking token. */ @NonNull - Token onRequestHide(@Nullable String component, int uid, @Origin int origin, - @SoftInputShowHideReason int reason, boolean fromUser); + default Token onStart(@Type int type, @Origin int origin, @SoftInputShowHideReason int reason, + boolean fromUser) { + return onStart(Process.myProcessName(), Process.myUid(), type, origin, reason, fromUser); + } /** * Called when an IME request progresses to a further phase. @@ -390,14 +402,14 @@ public interface ImeTracker { /** * Called when the IME show request is successful. * - * @param token the token tracking the current IME show request or {@code null} otherwise. + * @param token the token tracking the current IME request or {@code null} otherwise. */ void onShown(@Nullable Token token); /** * Called when the IME hide request is successful. * - * @param token the token tracking the current IME hide request or {@code null} otherwise. + * @param token the token tracking the current IME request or {@code null} otherwise. */ void onHidden(@Nullable Token token); @@ -479,33 +491,17 @@ public interface ImeTracker { @NonNull @Override - public Token onRequestShow(@Nullable String component, int uid, @Origin int origin, - @SoftInputShowHideReason int reason, boolean fromUser) { - final var tag = getTag(component); - final var token = IInputMethodManagerGlobalInvoker.onRequestShow(tag, uid, origin, - reason, fromUser); - - Log.i(TAG, token.mTag + ": onRequestShow at " + Debug.originToString(origin) - + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason) - + " fromUser " + fromUser, - mLogStackTrace ? new Throwable() : null); - - return token; - } - - @NonNull - @Override - public Token onRequestHide(@Nullable String component, int uid, @Origin int origin, + public Token onStart(@NonNull String component, int uid, @Type int type, @Origin int origin, @SoftInputShowHideReason int reason, boolean fromUser) { - final var tag = getTag(component); - final var token = IInputMethodManagerGlobalInvoker.onRequestHide(tag, uid, origin, - reason, fromUser); + final var tag = Token.createTag(component); + final var token = IInputMethodManagerGlobalInvoker.onStart(tag, uid, type, + origin, reason, fromUser); - Log.i(TAG, token.mTag + ": onRequestHide at " + Debug.originToString(origin) + Log.i(TAG, token.mTag + ": onRequest" + (type == TYPE_SHOW ? "Show" : "Hide") + + " at " + Debug.originToString(origin) + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason) + " fromUser " + fromUser, mLogStackTrace ? new Throwable() : null); - return token; } @@ -556,20 +552,6 @@ public interface ImeTracker { Log.i(TAG, token.mTag + ": onHidden"); } - - /** - * Returns a logging tag using the given component name. - * - * @param component the name of the component that created the IME request, or {@code null} - * otherwise (defaulting to {@link ActivityThread#currentProcessName()}). - */ - @NonNull - private String getTag(@Nullable String component) { - if (component == null) { - component = ActivityThread.currentProcessName(); - } - return component + ":" + Integer.toHexString(ThreadLocalRandom.current().nextInt()); - } }; /** The singleton IME tracker instance for instrumenting jank metrics. */ @@ -581,6 +563,10 @@ public interface ImeTracker { /** A token that tracks the progress of an IME request. */ final class Token implements Parcelable { + /** Empty binder, lazily initialized, used for empty token instantiation. */ + @Nullable + private static IBinder sEmptyBinder; + /** The binder used to identify this token. */ @NonNull private final IBinder mBinder; @@ -599,16 +585,56 @@ public interface ImeTracker { mTag = in.readString8(); } + /** Returns the binder used to identify this token. */ @NonNull public IBinder getBinder() { return mBinder; } + /** Returns the logging tag of this token. */ @NonNull public String getTag() { return mTag; } + /** + * Creates a logging tag. + * + * @param component the name of the component that created the IME request. + */ + @NonNull + private static String createTag(@NonNull String component) { + return component + ":" + Integer.toHexString(ThreadLocalRandom.current().nextInt()); + } + + /** Returns a new token with an empty binder. */ + @NonNull + @VisibleForTesting(visibility = Visibility.PACKAGE) + public static Token empty() { + final var tag = createTag(Process.myProcessName()); + return empty(tag); + } + + /** Returns a new token with an empty binder and the given logging tag. */ + @NonNull + static Token empty(@NonNull String tag) { + return new Token(getEmptyBinder(), tag); + } + + /** Returns the empty binder instance for empty token creation, lazily initializing it. */ + @NonNull + private static IBinder getEmptyBinder() { + if (sEmptyBinder == null) { + sEmptyBinder = new Binder(); + } + return sEmptyBinder; + } + + @Override + public String toString() { + return super.toString() + "(tag: " + mTag + ")"; + } + /** For Parcelable, no special marshalled objects. */ @Override public int describeContents() { diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java index 33f34c5697c4..88607fc80f69 100644 --- a/core/java/android/view/inputmethod/InputMethod.java +++ b/core/java/android/view/inputmethod/InputMethod.java @@ -281,7 +281,7 @@ public interface InputMethod { }) @Retention(RetentionPolicy.SOURCE) @interface ShowFlags {} - + /** * Flag for {@link #showSoftInput}: this show has been explicitly * requested by the user. If not set, the system has decided it may be @@ -314,18 +314,18 @@ public interface InputMethod { * @param showInputToken an opaque {@link android.os.Binder} token to identify which API call * of {@link InputMethodManager#showSoftInput(View, int)} is associated with * this callback. - * @param statsToken the token tracking the current IME show request or {@code null} otherwise. + * @param statsToken the token tracking the current IME request. * @hide */ @MainThread public default void showSoftInputWithToken(@ShowFlags int flags, ResultReceiver resultReceiver, - IBinder showInputToken, @Nullable ImeTracker.Token statsToken) { + IBinder showInputToken, @NonNull ImeTracker.Token statsToken) { showSoftInput(flags, resultReceiver); } /** * Request that any soft input part of the input method be shown to the user. - * + * * @param resultReceiver The client requesting the show may wish to * be told the impact of their request, which should be supplied here. * The result code should be @@ -352,12 +352,12 @@ public interface InputMethod { * @param hideInputToken an opaque {@link android.os.Binder} token to identify which API call * of {@link InputMethodManager#hideSoftInputFromWindow(IBinder, int)}} is associated * with this callback. - * @param statsToken the token tracking the current IME hide request or {@code null} otherwise. + * @param statsToken the token tracking the current IME request. * @hide */ @MainThread public default void hideSoftInputWithToken(int flags, ResultReceiver resultReceiver, - IBinder hideInputToken, @Nullable ImeTracker.Token statsToken) { + IBinder hideInputToken, @NonNull ImeTracker.Token statsToken) { hideSoftInput(flags, resultReceiver); } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 68940d699076..985f542c9982 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -1153,7 +1153,6 @@ public final class InputMethodManager { } final boolean startInput; synchronized (mH) { - mImeDispatcher.clear(); if (getBindSequenceLocked() != sequence) { return; } @@ -2263,21 +2262,22 @@ public final class InputMethodManager { * {@link #RESULT_HIDDEN}. */ public boolean showSoftInput(View view, @ShowFlags int flags, ResultReceiver resultReceiver) { - return showSoftInput(view, null /* statsToken */, flags, resultReceiver, - SoftInputShowHideReason.SHOW_SOFT_INPUT); + return showSoftInput(view, flags, resultReceiver, SoftInputShowHideReason.SHOW_SOFT_INPUT); } - private boolean showSoftInput(View view, @Nullable ImeTracker.Token statsToken, - @ShowFlags int flags, ResultReceiver resultReceiver, + private boolean showSoftInput(View view, @ShowFlags int flags, + @Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { + // TODO(b/303041796): handle tracking physical keyboard and DPAD as user interactions + final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_SHOW, + ImeTracker.ORIGIN_CLIENT, reason, ImeTracker.isFromUser(view)); + return showSoftInput(view, statsToken, flags, resultReceiver, reason); + } + + private boolean showSoftInput(View view, @NonNull ImeTracker.Token statsToken, + @ShowFlags int flags, @Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { - if (statsToken == null) { - // TODO(b/303041796): handle tracking physical keyboard and DPAD as user interactions - statsToken = ImeTracker.forLogging().onRequestShow(null /* component */, - Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT, reason, - ImeTracker.isFromUser(view)); - } - ImeTracker.forLatency().onRequestShow(statsToken, ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT, - reason, ActivityThread::currentApplication); + ImeTracker.forLatency().onRequestShow(statsToken, + ImeTracker.ORIGIN_CLIENT, reason, ActivityThread::currentApplication); ImeTracing.getInstance().triggerClientDump("InputMethodManager#showSoftInput", this, null /* icProto */); // Re-dispatch if there is a context mismatch. @@ -2290,9 +2290,8 @@ public final class InputMethodManager { synchronized (mH) { if (!hasServedByInputMethodLocked(view)) { ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED); - ImeTracker.forLatency().onShowFailed( - statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED, - ActivityThread::currentApplication); + ImeTracker.forLatency().onShowFailed(statsToken, + ImeTracker.PHASE_CLIENT_VIEW_SERVED, ActivityThread::currentApplication); Log.w(TAG, "Ignoring showSoftInput() as view=" + view + " is not served."); return false; } @@ -2327,9 +2326,9 @@ public final class InputMethodManager { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768499) public void showSoftInputUnchecked(@ShowFlags int flags, ResultReceiver resultReceiver) { synchronized (mH) { - final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestShow( - null /* component */, Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT, - SoftInputShowHideReason.SHOW_SOFT_INPUT, false /* fromUser */); + final int reason = SoftInputShowHideReason.SHOW_SOFT_INPUT; + final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_SHOW, + ImeTracker.ORIGIN_CLIENT, reason, false /* fromUser */); Log.w(TAG, "showSoftInputUnchecked() is a hidden method, which will be" + " removed soon. If you are using androidx.appcompat.widget.SearchView," @@ -2353,7 +2352,7 @@ public final class InputMethodManager { flags, mCurRootView.getLastClickToolType(), resultReceiver, - SoftInputShowHideReason.SHOW_SOFT_INPUT); + reason); } } @@ -2429,11 +2428,10 @@ public final class InputMethodManager { initialServedView = getServedViewLocked(); } - final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestHide( - null /* component */, Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, - reason, ImeTracker.isFromUser(initialServedView)); - ImeTracker.forLatency().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, - reason, ActivityThread::currentApplication); + final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE, + ImeTracker.ORIGIN_CLIENT, reason, ImeTracker.isFromUser(initialServedView)); + ImeTracker.forLatency().onRequestHide(statsToken, + ImeTracker.ORIGIN_CLIENT, reason, ActivityThread::currentApplication); ImeTracing.getInstance().triggerClientDump("InputMethodManager#hideSoftInputFromWindow", this, null /* icProto */); checkFocus(); @@ -2472,20 +2470,18 @@ public final class InputMethodManager { } } - final var reason = SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_VIEW; - final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestHide( - null /* component */, Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, - reason, ImeTracker.isFromUser(view)); - ImeTracker.forLatency().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, - reason, ActivityThread::currentApplication); + final int reason = SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_VIEW; + final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE, + ImeTracker.ORIGIN_CLIENT, reason, ImeTracker.isFromUser(view)); + ImeTracker.forLatency().onRequestHide(statsToken, + ImeTracker.ORIGIN_CLIENT, reason, ActivityThread::currentApplication); ImeTracing.getInstance().triggerClientDump("InputMethodManager#hideSoftInputFromView", this, null /* icProto */); synchronized (mH) { if (!hasServedByInputMethodLocked(view)) { ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED); - ImeTracker.forLatency().onShowFailed( - statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED, - ActivityThread::currentApplication); + ImeTracker.forLatency().onShowFailed(statsToken, + ImeTracker.PHASE_CLIENT_VIEW_SERVED, ActivityThread::currentApplication); Log.w(TAG, "Ignoring hideSoftInputFromView() as view=" + view + " is not served."); return false; } @@ -2498,6 +2494,19 @@ public final class InputMethodManager { } /** + * A test API for CTS to request hiding the current soft input window, with the request origin + * on the server side. + * + * @hide + */ + @SuppressLint("UnflaggedApi") // @TestApi without associated feature. + @TestApi + @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD) + public void hideSoftInputFromServerForTest() { + IInputMethodManagerGlobalInvoker.hideSoftInputFromServerForTest(); + } + + /** * Start stylus handwriting session. * * If supported by the current input method, a stylus handwriting session is started on the @@ -2899,8 +2908,6 @@ public final class InputMethodManager { * @param flags {@link #HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED} or {@code 0} * @param executor The executor to run the callback on. * @param callback {@code true>} would be received if delegation was accepted. - * @return {@code true} if view belongs to allowed delegate package declared in {@link - * #prepareStylusHandwritingDelegation(View, String)} and delegation is accepted * @see #prepareStylusHandwritingDelegation(View, String) * @see #acceptStylusHandwritingDelegation(View) */ @@ -2973,10 +2980,11 @@ public final class InputMethodManager { if (view != null) { final WindowInsets rootInsets = view.getRootWindowInsets(); if (rootInsets != null && rootInsets.isVisible(WindowInsets.Type.ime())) { - hideSoftInputFromWindow(view.getWindowToken(), hideFlags, null, + hideSoftInputFromWindow(view.getWindowToken(), hideFlags, + null /* resultReceiver */, SoftInputShowHideReason.HIDE_TOGGLE_SOFT_INPUT); } else { - showSoftInput(view, null /* statsToken */, showFlags, null /* resultReceiver */, + showSoftInput(view, showFlags, null /* resultReceiver */, SoftInputShowHideReason.SHOW_TOGGLE_SOFT_INPUT); } } @@ -3537,11 +3545,11 @@ public final class InputMethodManager { @UnsupportedAppUsage void closeCurrentInput() { - final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestHide( - null /* component */, Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, - SoftInputShowHideReason.HIDE_CLOSE_CURRENT_SESSION, false /* fromUser */); - ImeTracker.forLatency().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, - SoftInputShowHideReason.HIDE_CLOSE_CURRENT_SESSION, + final int reason = SoftInputShowHideReason.HIDE_CLOSE_CURRENT_SESSION; + final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE, + ImeTracker.ORIGIN_CLIENT, reason, false /* fromUser */); + ImeTracker.forLatency().onRequestHide(statsToken, + ImeTracker.ORIGIN_CLIENT, reason, ActivityThread::currentApplication); synchronized (mH) { @@ -3562,7 +3570,7 @@ public final class InputMethodManager { statsToken, HIDE_NOT_ALWAYS, null, - SoftInputShowHideReason.HIDE_CLOSE_CURRENT_SESSION); + reason); } } @@ -3603,12 +3611,12 @@ public final class InputMethodManager { * * @param windowToken the window from which this request originates. If this doesn't match the * currently served view, the request is ignored and returns {@code false}. - * @param statsToken the token tracking the current IME show request or {@code null} otherwise. + * @param statsToken the token tracking the current IME request. * * @return {@code true} if IME can (eventually) be shown, {@code false} otherwise. * @hide */ - public boolean requestImeShow(IBinder windowToken, @Nullable ImeTracker.Token statsToken) { + public boolean requestImeShow(IBinder windowToken, @NonNull ImeTracker.Token statsToken) { checkFocus(); synchronized (mH) { final View servedView = getServedViewLocked(); @@ -3632,16 +3640,11 @@ public final class InputMethodManager { * * @param windowToken the window from which this request originates. If this doesn't match the * currently served view, the request is ignored. - * @param statsToken the token tracking the current IME show request or {@code null} otherwise. + * @param statsToken the token tracking the current IME request. * @hide */ - public void notifyImeHidden(IBinder windowToken, @Nullable ImeTracker.Token statsToken) { - if (statsToken == null) { - statsToken = ImeTracker.forLogging().onRequestHide(null /* component */, - Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, - SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API, false /* fromUser */); - } - ImeTracker.forLatency().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, + public void notifyImeHidden(IBinder windowToken, @NonNull ImeTracker.Token statsToken) { + ImeTracker.forLatency().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT, SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API, ActivityThread::currentApplication); ImeTracing.getInstance().triggerClientDump("InputMethodManager#notifyImeHidden", this, @@ -4025,8 +4028,11 @@ public final class InputMethodManager { */ @Deprecated public void hideSoftInputFromInputMethod(IBinder token, @HideFlags int flags) { - InputMethodPrivilegedOperationsRegistry.get(token).hideMySoftInput( - flags, SoftInputShowHideReason.HIDE_SOFT_INPUT_IMM_DEPRECATION); + final int reason = SoftInputShowHideReason.HIDE_SOFT_INPUT_IMM_DEPRECATION; + final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE, + ImeTracker.ORIGIN_CLIENT, reason, false /* fromUser */); + InputMethodPrivilegedOperationsRegistry.get(token).hideMySoftInput(statsToken, flags, + reason); } /** @@ -4044,7 +4050,11 @@ public final class InputMethodManager { */ @Deprecated public void showSoftInputFromInputMethod(IBinder token, @ShowFlags int flags) { - InputMethodPrivilegedOperationsRegistry.get(token).showMySoftInput(flags); + final int reason = SoftInputShowHideReason.SHOW_SOFT_INPUT_IMM_DEPRECATION; + final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_SHOW, + ImeTracker.ORIGIN_CLIENT, reason, false /* fromUser */); + InputMethodPrivilegedOperationsRegistry.get(token).showMySoftInput(statsToken, flags, + reason); } /** diff --git a/core/java/android/webkit/WebViewDelegate.java b/core/java/android/webkit/WebViewDelegate.java index 3fc0a305c8e6..8501474b70a6 100644 --- a/core/java/android/webkit/WebViewDelegate.java +++ b/core/java/android/webkit/WebViewDelegate.java @@ -175,8 +175,16 @@ public final class WebViewDelegate { /** * Adds the WebView asset path to {@link android.content.res.AssetManager}. + * If {@link android.content.res.Flags#FLAG_REGISTER_RESOURCE_PATHS} is enabled, this function + * will be a no-op because the asset paths appending work will only be handled by + * {@link android.content.res.Resources#registerResourcePaths(String, ApplicationInfo)}, + * otherwise it behaves the old way. */ public void addWebViewAssetPath(Context context) { + if (android.content.res.Flags.registerResourcePaths()) { + return; + } + final String[] newAssetPaths = WebViewFactory.getLoadedPackageInfo().applicationInfo.getAllApkPaths(); final ApplicationInfo appInfo = context.getApplicationInfo(); diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java index c748a57dce74..8f1b72e90da1 100644 --- a/core/java/android/webkit/WebViewFactory.java +++ b/core/java/android/webkit/WebViewFactory.java @@ -30,6 +30,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.Signature; +import android.content.res.Resources; import android.os.Build; import android.os.RemoteException; import android.os.ServiceManager; @@ -93,6 +94,9 @@ public final class WebViewFactory { // error for namespace lookup public static final int LIBLOAD_FAILED_TO_FIND_NAMESPACE = 10; + // generic error for future use + static final int LIBLOAD_FAILED_OTHER = 11; + /** * Stores the timestamps at which various WebView startup events occurred in this process. */ @@ -544,8 +548,14 @@ public final class WebViewFactory { Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getChromiumProviderClass()"); try { sTimestamps.mAddAssetsStart = SystemClock.uptimeMillis(); - for (String newAssetPath : webViewContext.getApplicationInfo().getAllApkPaths()) { - initialApplication.getAssets().addAssetPathAsSharedLibrary(newAssetPath); + if (android.content.res.Flags.registerResourcePaths()) { + Resources.registerResourcePaths(webViewContext.getPackageName(), + webViewContext.getApplicationInfo()); + } else { + for (String newAssetPath : webViewContext.getApplicationInfo() + .getAllApkPaths()) { + initialApplication.getAssets().addAssetPathAsSharedLibrary(newAssetPath); + } } sTimestamps.mAddAssetsEnd = sTimestamps.mGetClassLoaderStart = SystemClock.uptimeMillis(); diff --git a/core/java/android/webkit/WebViewProviderResponse.java b/core/java/android/webkit/WebViewProviderResponse.java index 84e34a34f7f7..926aaff782f3 100644 --- a/core/java/android/webkit/WebViewProviderResponse.java +++ b/core/java/android/webkit/WebViewProviderResponse.java @@ -40,6 +40,7 @@ public final class WebViewProviderResponse implements Parcelable { STATUS_SUCCESS, STATUS_FAILED_WAITING_FOR_RELRO, STATUS_FAILED_LISTING_WEBVIEW_PACKAGES, + STATUS_FAILED_OTHER, }) @Retention(RetentionPolicy.SOURCE) private @interface WebViewProviderStatus {} @@ -49,6 +50,7 @@ public final class WebViewProviderResponse implements Parcelable { WebViewFactory.LIBLOAD_FAILED_WAITING_FOR_RELRO; public static final int STATUS_FAILED_LISTING_WEBVIEW_PACKAGES = WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES; + public static final int STATUS_FAILED_OTHER = WebViewFactory.LIBLOAD_FAILED_OTHER; public WebViewProviderResponse( @Nullable PackageInfo packageInfo, @WebViewProviderStatus int status) { diff --git a/core/java/android/webkit/WebViewUpdateManager.java b/core/java/android/webkit/WebViewUpdateManager.java index 8ada598d8a76..07576a2e89e4 100644 --- a/core/java/android/webkit/WebViewUpdateManager.java +++ b/core/java/android/webkit/WebViewUpdateManager.java @@ -127,7 +127,7 @@ public final class WebViewUpdateManager { * * This choice will be stored persistently. * - * @param newProvider the package name to use, or null to reset to default. + * @param newProvider the package name to use. * @return the package name which is now in use, which may not be the * requested one if it was not usable. */ @@ -155,7 +155,7 @@ public final class WebViewUpdateManager { /** * Get the WebView provider which will be used if no explicit choice has been made. * - * The default provider is not guaranteed to be currently valid/usable. + * The default provider is not guaranteed to be a valid/usable WebView implementation. * * @return the default WebView provider. */ diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java index 55b2251ac196..0b99df323b09 100644 --- a/core/java/android/widget/HorizontalScrollView.java +++ b/core/java/android/widget/HorizontalScrollView.java @@ -16,6 +16,8 @@ package android.widget; +import static android.view.flags.Flags.viewVelocityApi; + import android.annotation.ColorInt; import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; @@ -1488,6 +1490,11 @@ public class HorizontalScrollView extends FrameLayout { if (!awakenScrollBars()) { postInvalidateOnAnimation(); } + + // For variable refresh rate project to track the current velocity of this View + if (viewVelocityApi()) { + setFrameContentVelocity(Math.abs(mScroller.getCurrVelocity())); + } } } @@ -1810,6 +1817,11 @@ public class HorizontalScrollView extends FrameLayout { mScroller.fling(mScrollX, mScrollY, velocityX, 0, 0, maxScroll, 0, 0, width / 2, 0); + // For variable refresh rate project to track the current velocity of this View + if (viewVelocityApi()) { + setFrameContentVelocity(Math.abs(mScroller.getCurrVelocity())); + } + final boolean movingRight = velocityX > 0; View currentFocused = findFocus(); diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 0e5747d0e445..fe4ac4e2f0f1 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -1105,6 +1105,7 @@ public class RemoteViews implements Parcelable, Filter { SetRemoteCollectionItemListAdapterAction(Parcel parcel) { mViewId = parcel.readInt(); mIntentId = parcel.readInt(); + mIsReplacedIntoAction = parcel.readBoolean(); mServiceIntent = parcel.readTypedObject(Intent.CREATOR); mItems = mServiceIntent != null ? null @@ -1128,6 +1129,7 @@ public class RemoteViews implements Parcelable, Filter { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mViewId); dest.writeInt(mIntentId); + dest.writeBoolean(mIsReplacedIntoAction); dest.writeTypedObject(mServiceIntent, flags); if (mItems != null) { mItems.writeToParcel(dest, flags, /* attached= */ true); @@ -1209,6 +1211,19 @@ public class RemoteViews implements Parcelable, Filter { } /** + * The maximum size for RemoteViews with converted RemoteCollectionItemsAdapter. + * When converting RemoteViewsAdapter to RemoteCollectionItemsAdapter, we want to put size + * limits on each unique RemoteCollectionItems in order to not exceed the transaction size limit + * for each parcel (typically 1 MB). We leave a certain ratio of the maximum size as a buffer + * for missing calculations of certain parameters (e.g. writing a RemoteCollectionItems to the + * parcel will write its Id array as well, but that is missing when writing itschild RemoteViews + * directly to the parcel as we did in RemoteViewsService) + * + * @hide + */ + private static final int MAX_SINGLE_PARCEL_SIZE = (int) (1_000_000 * 0.8); + + /** * @hide */ public CompletableFuture<Void> collectAllIntents() { @@ -1260,17 +1275,47 @@ public class RemoteViews implements Parcelable, Filter { return mUriToCollectionMapping.get(uri); } - CompletableFuture<Void> collectAllIntentsNoComplete(@NonNull RemoteViews inViews) { - CompletableFuture<Void> collectionFuture = CompletableFuture.completedFuture(null); + public @NonNull CompletableFuture<Void> collectAllIntentsNoComplete( + @NonNull RemoteViews inViews) { + SparseArray<Intent> idToIntentMapping = new SparseArray<>(); + // Collect the number of uinque Intent (which is equal to the number of new connections + // to make) for size allocation and exclude certain collections from being written to + // the parcel to better estimate the space left for reallocation. + collectAllIntentsInternal(inViews, idToIntentMapping); + + // Calculate the individual size here + int numOfIntents = idToIntentMapping.size(); + if (numOfIntents == 0) { + Log.e(LOG_TAG, "Possibly notifying updates for nonexistent view Id"); + return CompletableFuture.completedFuture(null); + } + + Parcel sizeTestParcel = Parcel.obtain(); + // Write self RemoteViews to the parcel, which includes the actions/bitmaps/collection + // cache to see how much space is left for the RemoteCollectionItems that are to be + // updated. + RemoteViews.this.writeToParcel(sizeTestParcel, + /* flags= */ 0, + /* intentsToIgnore= */ idToIntentMapping); + int remainingSize = MAX_SINGLE_PARCEL_SIZE - sizeTestParcel.dataSize(); + sizeTestParcel.recycle(); + + int individualSize = remainingSize < 0 + ? 0 + : remainingSize / numOfIntents; + + return connectAllUniqueIntents(individualSize, idToIntentMapping); + } + + private void collectAllIntentsInternal(@NonNull RemoteViews inViews, + @NonNull SparseArray<Intent> idToIntentMapping) { if (inViews.hasSizedRemoteViews()) { for (RemoteViews remoteViews : inViews.mSizedRemoteViews) { - collectionFuture = CompletableFuture.allOf(collectionFuture, - collectAllIntentsNoComplete(remoteViews)); + collectAllIntentsInternal(remoteViews, idToIntentMapping); } } else if (inViews.hasLandscapeAndPortraitLayouts()) { - collectionFuture = CompletableFuture.allOf( - collectAllIntentsNoComplete(inViews.mLandscape), - collectAllIntentsNoComplete(inViews.mPortrait)); + collectAllIntentsInternal(inViews.mLandscape, idToIntentMapping); + collectAllIntentsInternal(inViews.mPortrait, idToIntentMapping); } else if (inViews.mActions != null) { for (Action action : inViews.mActions) { if (action instanceof SetRemoteCollectionItemListAdapterAction rca) { @@ -1280,13 +1325,16 @@ public class RemoteViews implements Parcelable, Filter { } if (rca.mIntentId != -1 && rca.mIsReplacedIntoAction) { - final String uri = mIdToUriMapping.get(rca.mIntentId); - collectionFuture = CompletableFuture.allOf(collectionFuture, - getItemsFutureFromIntentWithTimeout(rca.mServiceIntent) - .thenAccept(rc -> { - rc.setHierarchyRootData(getHierarchyRootData()); - mUriToCollectionMapping.put(uri, rc); - })); + rca.mIsReplacedIntoAction = false; + + // Avoid redundant connections for the same intent. Also making sure + // that the number of connections we are making is always equal to the + // nmuber of unique intents that are being used for the updates. + if (idToIntentMapping.contains(rca.mIntentId)) { + continue; + } + + idToIntentMapping.put(rca.mIntentId, rca.mServiceIntent); rca.mItems = null; continue; } @@ -1295,7 +1343,7 @@ public class RemoteViews implements Parcelable, Filter { // intents. if (rca.mServiceIntent != null) { final String uri = rca.mServiceIntent.toUri(0); - int index = mIdToUriMapping.indexOfValue(uri); + int index = mIdToUriMapping.indexOfValueByValue(uri); if (index == -1) { int newIntentId = mIdToUriMapping.size(); rca.mIntentId = newIntentId; @@ -1305,41 +1353,50 @@ public class RemoteViews implements Parcelable, Filter { rca.mItems = null; continue; } - collectionFuture = CompletableFuture.allOf(collectionFuture, - getItemsFutureFromIntentWithTimeout(rca.mServiceIntent) - .thenAccept(rc -> { - rc.setHierarchyRootData(getHierarchyRootData()); - mUriToCollectionMapping.put(uri, rc); - })); + + idToIntentMapping.put(rca.mIntentId, rca.mServiceIntent); rca.mItems = null; } else { for (RemoteViews views : rca.mItems.mViews) { - collectionFuture = CompletableFuture.allOf(collectionFuture, - collectAllIntentsNoComplete(views)); + collectAllIntentsInternal(views, idToIntentMapping); } } } else if (action instanceof ViewGroupActionAdd vgaa && vgaa.mNestedViews != null) { - collectionFuture = CompletableFuture.allOf(collectionFuture, - collectAllIntentsNoComplete(vgaa.mNestedViews)); + collectAllIntentsInternal(vgaa.mNestedViews, idToIntentMapping); } } } + } - return collectionFuture; + private @NonNull CompletableFuture<Void> connectAllUniqueIntents(int individualSize, + @NonNull SparseArray<Intent> idToIntentMapping) { + List<CompletableFuture<Void>> intentFutureList = new ArrayList<>(); + for (int i = 0; i < idToIntentMapping.size(); i++) { + String currentIntentUri = mIdToUriMapping.get(idToIntentMapping.keyAt(i)); + Intent currentIntent = idToIntentMapping.valueAt(i); + intentFutureList.add(getItemsFutureFromIntentWithTimeout(currentIntent, + individualSize) + .thenAccept(items -> { + items.setHierarchyRootData(getHierarchyRootData()); + mUriToCollectionMapping.put(currentIntentUri, items); + })); + } + + return CompletableFuture.allOf(intentFutureList.toArray(CompletableFuture[]::new)); } private static CompletableFuture<RemoteCollectionItems> getItemsFutureFromIntentWithTimeout( - Intent intent) { + Intent intent, int individualSize) { if (intent == null) { Log.e(LOG_TAG, "Null intent received when generating adapter future"); return CompletableFuture.completedFuture(new RemoteCollectionItems - .Builder().build()); + .Builder().build()); } final Context context = ActivityThread.currentApplication(); - final CompletableFuture<RemoteCollectionItems> result = new CompletableFuture<>(); + final CompletableFuture<RemoteCollectionItems> result = new CompletableFuture<>(); context.bindService(intent, Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE), result.defaultExecutor(), new ServiceConnection() { @Override @@ -1348,11 +1405,11 @@ public class RemoteViews implements Parcelable, Filter { RemoteCollectionItems items; try { items = IRemoteViewsFactory.Stub.asInterface(iBinder) - .getRemoteCollectionItems(); + .getRemoteCollectionItems(individualSize); } catch (RemoteException re) { items = new RemoteCollectionItems.Builder().build(); - Log.e(LOG_TAG, "Error getting collection items from the factory", - re); + Log.e(LOG_TAG, "Error getting collection items from the" + + " factory", re); } finally { context.unbindService(this); } @@ -1371,10 +1428,17 @@ public class RemoteViews implements Parcelable, Filter { return result; } - public void writeToParcel(Parcel out, int flags) { + public void writeToParcel(Parcel out, int flags, + @Nullable SparseArray<Intent> intentsToIgnore) { out.writeInt(mIdToUriMapping.size()); for (int i = 0; i < mIdToUriMapping.size(); i++) { - out.writeInt(mIdToUriMapping.keyAt(i)); + int currentIntentId = mIdToUriMapping.keyAt(i); + if (intentsToIgnore != null && intentsToIgnore.contains(currentIntentId)) { + // Skip writing collections that are to be updated in the following steps to + // better estimate the RemoteViews size. + continue; + } + out.writeInt(currentIntentId); String intentUri = mIdToUriMapping.valueAt(i); out.writeString8(intentUri); mUriToCollectionMapping.get(intentUri).writeToParcel(out, flags, true); @@ -6724,7 +6788,13 @@ public class RemoteViews implements Parcelable, Filter { return 0; } + @Override public void writeToParcel(Parcel dest, int flags) { + writeToParcel(dest, flags, /* intentsToIgnore= */ null); + } + + private void writeToParcel(Parcel dest, int flags, + @Nullable SparseArray<Intent> intentsToIgnore) { boolean prevSquashingAllowed = dest.allowSquashing(); if (!hasMultipleLayouts()) { @@ -6733,7 +6803,7 @@ public class RemoteViews implements Parcelable, Filter { // is shared by all children. if (mIsRoot) { mBitmapCache.writeBitmapsToParcel(dest, flags); - mCollectionCache.writeToParcel(dest, flags); + mCollectionCache.writeToParcel(dest, flags, intentsToIgnore); } mApplication.writeToParcel(dest, flags); if (mIsRoot || mIdealSize == null) { @@ -6750,7 +6820,7 @@ public class RemoteViews implements Parcelable, Filter { dest.writeInt(MODE_HAS_SIZED_REMOTEVIEWS); if (mIsRoot) { mBitmapCache.writeBitmapsToParcel(dest, flags); - mCollectionCache.writeToParcel(dest, flags); + mCollectionCache.writeToParcel(dest, flags, intentsToIgnore); } dest.writeInt(mSizedRemoteViews.size()); for (RemoteViews view : mSizedRemoteViews) { @@ -6762,7 +6832,7 @@ public class RemoteViews implements Parcelable, Filter { // is shared by all children. if (mIsRoot) { mBitmapCache.writeBitmapsToParcel(dest, flags); - mCollectionCache.writeToParcel(dest, flags); + mCollectionCache.writeToParcel(dest, flags, intentsToIgnore); } mLandscape.writeToParcel(dest, flags); // Both RemoteViews already share the same package and user diff --git a/core/java/android/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java index a250a867b9de..d4a5bbd79d31 100644 --- a/core/java/android/widget/RemoteViewsService.java +++ b/core/java/android/widget/RemoteViewsService.java @@ -19,6 +19,7 @@ package android.widget; import android.app.Service; import android.content.Intent; import android.os.IBinder; +import android.os.Parcel; import com.android.internal.widget.IRemoteViewsFactory; @@ -43,13 +44,6 @@ public abstract class RemoteViewsService extends Service { private static final Object sLock = new Object(); /** - * Used for determining the maximum number of entries to retrieve from RemoteViewsFactory - * - * @hide - */ - private static final int MAX_NUM_ENTRY = 10; - - /** * An interface for an adapter between a remote collection view (ListView, GridView, etc) and * the underlying data for that view. The implementor is responsible for making a RemoteView * for each item in the data set. This interface is a thin wrapper around {@link Adapter}. @@ -235,9 +229,10 @@ public abstract class RemoteViewsService extends Service { } @Override - public RemoteViews.RemoteCollectionItems getRemoteCollectionItems() { + public RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize) { RemoteViews.RemoteCollectionItems items = new RemoteViews.RemoteCollectionItems .Builder().build(); + Parcel capSizeTestParcel = Parcel.obtain(); try { RemoteViews.RemoteCollectionItems.Builder itemsBuilder = @@ -245,15 +240,25 @@ public abstract class RemoteViewsService extends Service { mFactory.onDataSetChanged(); itemsBuilder.setHasStableIds(mFactory.hasStableIds()); - final int numOfEntries = Math.min(mFactory.getCount(), MAX_NUM_ENTRY); + final int numOfEntries = mFactory.getCount(); + for (int i = 0; i < numOfEntries; i++) { - itemsBuilder.addItem(mFactory.getItemId(i), mFactory.getViewAt(i)); + final long currentItemId = mFactory.getItemId(i); + final RemoteViews currentView = mFactory.getViewAt(i); + currentView.writeToParcel(capSizeTestParcel, 0); + if (capSizeTestParcel.dataSize() > capSize) { + break; + } + itemsBuilder.addItem(currentItemId, currentView); } items = itemsBuilder.build(); } catch (Exception ex) { Thread t = Thread.currentThread(); Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex); + } finally { + // Recycle the parcel + capSizeTestParcel.recycle(); } return items; } diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index a1ebde76e98e..42c2d80ea322 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -16,6 +16,8 @@ package android.widget; +import static android.view.flags.Flags.viewVelocityApi; + import android.annotation.ColorInt; import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; @@ -726,6 +728,12 @@ public class ScrollView extends FrameLayout { * isFinished() is correct. */ mScroller.computeScrollOffset(); + + // For variable refresh rate project to track the current velocity of this View + if (viewVelocityApi()) { + setFrameContentVelocity(Math.abs(mScroller.getCurrVelocity())); + } + mIsBeingDragged = !mScroller.isFinished() || !mEdgeGlowBottom.isFinished() || !mEdgeGlowTop.isFinished(); // Catch the edge effect if it is active. @@ -1573,6 +1581,11 @@ public class ScrollView extends FrameLayout { // Keep on drawing until the animation has finished. postInvalidateOnAnimation(); } + + // For variable refresh rate project to track the current velocity of this View + if (viewVelocityApi()) { + setFrameContentVelocity(Math.abs(mScroller.getCurrVelocity())); + } } else { if (mFlingStrictSpan != null) { mFlingStrictSpan.finish(); @@ -1884,6 +1897,10 @@ public class ScrollView extends FrameLayout { mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0, Math.max(0, bottom - height), 0, height/2); + // For variable refresh rate project to track the current velocity of this View + if (viewVelocityApi()) { + setFrameContentVelocity(Math.abs(mScroller.getCurrVelocity())); + } if (mFlingStrictSpan == null) { mFlingStrictSpan = StrictMode.enterCriticalSpan("ScrollView-fling"); } diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java index 65b59790e327..c7695187e52d 100644 --- a/core/java/com/android/internal/app/IntentForwarderActivity.java +++ b/core/java/com/android/internal/app/IntentForwarderActivity.java @@ -172,6 +172,20 @@ public class IntentForwarderActivity extends Activity { newIntent.prepareToLeaveUser(callingUserId); final CompletableFuture<ResolveInfo> targetResolveInfoFuture = mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, targetUserId); + + if (isPrivateProfile(callingUserId)) { + buildAndExecuteForPrivateProfile(intentReceived, className, newIntent, callingUserId, + targetUserId); + } else { + buildAndExecute(targetResolveInfoFuture, intentReceived, className, newIntent, + callingUserId, + targetUserId, userMessage, managedProfile); + } + } + + private void buildAndExecute(CompletableFuture<ResolveInfo> targetResolveInfoFuture, + Intent intentReceived, String className, Intent newIntent, int callingUserId, + int targetUserId, String userMessage, UserInfo managedProfile) { targetResolveInfoFuture .thenApplyAsync(targetResolveInfo -> { if (isResolverActivityResolveInfo(targetResolveInfo)) { @@ -195,6 +209,23 @@ public class IntentForwarderActivity extends Activity { }, getApplicationContext().getMainExecutor()); } + private void buildAndExecuteForPrivateProfile( + Intent intentReceived, String className, Intent newIntent, int callingUserId, + int targetUserId) { + final CompletableFuture<ResolveInfo> targetResolveInfoFuture = + mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, targetUserId); + targetResolveInfoFuture + .thenAcceptAsync(targetResolveInfo -> { + if (isResolverActivityResolveInfo(targetResolveInfo)) { + launchResolverActivityWithCorrectTab(intentReceived, className, newIntent, + callingUserId, targetUserId); + } else { + maybeShowUserConsentMiniResolverPrivate(targetResolveInfo, newIntent, + targetUserId); + } + }, getApplicationContext().getMainExecutor()); + } + private void maybeShowUserConsentMiniResolver( ResolveInfo target, Intent launchIntent, UserInfo managedProfile) { if (target == null || isIntentForwarderResolveInfo(target) || !isDeviceProvisioned()) { @@ -233,24 +264,70 @@ public class IntentForwarderActivity extends Activity { "Showing user consent for redirection into the managed profile for intent [%s] and " + " calling package [%s]", launchIntent, callingPackage)); - int layoutId = R.layout.miniresolver; - setContentView(layoutId); + PackageManager packageManagerForTargetUser = + createContextAsUser(UserHandle.of(targetUserId), /* flags= */ 0) + .getPackageManager(); + buildMiniResolver(target, launchIntent, targetUserId, + getOpenInWorkMessage(launchIntent, target.loadLabel(packageManagerForTargetUser)), + packageManagerForTargetUser); - findViewById(R.id.title_container).setElevation(0); + View telephonyInfo = findViewById(R.id.miniresolver_info_section); + + // Additional information section is work telephony specific. Therefore, it is only shown + // for telephony related intents, when all sim subscriptions are in the work profile. + if ((isDialerIntent(launchIntent) || isTextMessageIntent(launchIntent)) + && devicePolicyManager.getManagedSubscriptionsPolicy().getPolicyType() + == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) { + telephonyInfo.setVisibility(View.VISIBLE); + ((TextView) findViewById(R.id.miniresolver_info_section_text)) + .setText(getWorkTelephonyInfoSectionMessage(launchIntent)); + } else { + telephonyInfo.setVisibility(View.GONE); + } + } + + private void maybeShowUserConsentMiniResolverPrivate( + ResolveInfo target, Intent launchIntent, int targetUserId) { + if (target == null || isIntentForwarderResolveInfo(target)) { + finish(); + return; + } + + String callingPackage = getCallingPackage(); + + Log.i("IntentForwarderActivity", String.format( + "Showing user consent for redirection into the main profile for intent [%s] and " + + " calling package [%s]", + launchIntent, callingPackage)); PackageManager packageManagerForTargetUser = createContextAsUser(UserHandle.of(targetUserId), /* flags= */ 0) .getPackageManager(); + buildMiniResolver(target, launchIntent, targetUserId, + getString(R.string.miniresolver_open_in_personal, + target.loadLabel(packageManagerForTargetUser)), + packageManagerForTargetUser); + + View telephonyInfo = findViewById(R.id.miniresolver_info_section); + telephonyInfo.setVisibility(View.GONE); + } + + private void buildMiniResolver(ResolveInfo target, Intent launchIntent, int targetUserId, + String resolverTitle, PackageManager pmForTargetUser) { + int layoutId = R.layout.miniresolver; + setContentView(layoutId); + + findViewById(R.id.title_container).setElevation(0); ImageView icon = findViewById(R.id.icon); icon.setImageDrawable( - getAppIcon(target, launchIntent, targetUserId, packageManagerForTargetUser)); + getAppIcon(target, launchIntent, targetUserId, pmForTargetUser)); View buttonContainer = findViewById(R.id.button_bar_container); buttonContainer.setPadding(0, 0, 0, buttonContainer.getPaddingBottom()); ((TextView) findViewById(R.id.open_cross_profile)).setText( - getOpenInWorkMessage(launchIntent, target.loadLabel(packageManagerForTargetUser))); + resolverTitle); // The mini-resolver's negative button is reused in this flow to cancel the intent ((Button) findViewById(R.id.use_same_profile_browser)).setText(R.string.cancel); @@ -269,21 +346,6 @@ public class IntentForwarderActivity extends Activity { targetUserId); finish(); }); - - - View telephonyInfo = findViewById(R.id.miniresolver_info_section); - - // Additional information section is work telephony specific. Therefore, it is only shown - // for telephony related intents, when all sim subscriptions are in the work profile. - if ((isDialerIntent(launchIntent) || isTextMessageIntent(launchIntent)) - && devicePolicyManager.getManagedSubscriptionsPolicy().getPolicyType() - == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) { - telephonyInfo.setVisibility(View.VISIBLE); - ((TextView) findViewById(R.id.miniresolver_info_section_text)) - .setText(getWorkTelephonyInfoSectionMessage(launchIntent)); - } else { - telephonyInfo.setVisibility(View.GONE); - } } private Drawable getAppIcon( @@ -548,6 +610,18 @@ public class IntentForwarderActivity extends Activity { } /** + * Returns the private profile for this device or null if there is no private profile. + */ + @Nullable + private UserInfo getPrivateProfile() { + List<UserInfo> relatedUsers = mInjector.getUserManager().getProfiles(UserHandle.myUserId()); + for (UserInfo userInfo : relatedUsers) { + if (userInfo.isPrivateProfile()) return userInfo; + } + return null; + } + + /** * Returns the userId of the profile parent or UserHandle.USER_NULL if there is * no parent. */ @@ -577,6 +651,17 @@ public class IntentForwarderActivity extends Activity { return mMetricsLogger; } + private boolean isPrivateProfile(int userId) { + UserInfo privateProfile = getPrivateProfile(); + return privateSpaceFlagsEnabled() && privateProfile != null + && privateProfile.id == userId; + } + + private boolean privateSpaceFlagsEnabled() { + return android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enablePrivateSpaceIntentRedirection(); + } + @VisibleForTesting protected Injector createInjector() { return new InjectorImpl(); diff --git a/core/java/com/android/internal/inputmethod/IImeTracker.aidl b/core/java/com/android/internal/inputmethod/IImeTracker.aidl index 275904347d2b..b45bc1c46967 100644 --- a/core/java/com/android/internal/inputmethod/IImeTracker.aidl +++ b/core/java/com/android/internal/inputmethod/IImeTracker.aidl @@ -25,28 +25,19 @@ import android.view.inputmethod.ImeTracker; interface IImeTracker { /** - * Called when an IME show request is created. + * Called when an IME request is started. * * @param tag the logging tag. - * @param uid the uid of the client that requested the IME. - * @param origin the origin of the IME show request. - * @param reason the reason why the IME show request was created. + * @param uid the uid of the client that started the request. + * @param type the type of the request. + * @param origin the origin of the request. * @param fromUser whether this request was created directly from user interaction. - * @return A new IME tracking token. - */ - ImeTracker.Token onRequestShow(String tag, int uid, int origin, int reason, boolean fromUser); - - /** - * Called when an IME hide request is created. + * @param reason the reason for starting the request. * - * @param tag the logging tag. - * @param uid the uid of the client that requested the IME. - * @param origin the origin of the IME hide request. - * @param reason the reason why the IME hide request was created. - * @param fromUser whether this request was created directly from user interaction. - * @return A new IME tracking token. + * @return An IME request tracking token. */ - ImeTracker.Token onRequestHide(String tag, int uid, int origin, int reason, boolean fromUser); + ImeTracker.Token onStart(String tag, int uid, int type, int origin, int reason, + boolean fromUser); /** * Called when the IME request progresses to a further phase. diff --git a/core/java/com/android/internal/inputmethod/IInputMethod.aidl b/core/java/com/android/internal/inputmethod/IInputMethod.aidl index 6abd9e8a8a17..2593b78f5182 100644 --- a/core/java/com/android/internal/inputmethod/IInputMethod.aidl +++ b/core/java/com/android/internal/inputmethod/IInputMethod.aidl @@ -71,11 +71,11 @@ oneway interface IInputMethod { void setSessionEnabled(IInputMethodSession session, boolean enabled); - void showSoftInput(in IBinder showInputToken, in @nullable ImeTracker.Token statsToken, - int flags, in ResultReceiver resultReceiver); + void showSoftInput(in IBinder showInputToken, in ImeTracker.Token statsToken, int flags, + in ResultReceiver resultReceiver); - void hideSoftInput(in IBinder hideInputToken, in @nullable ImeTracker.Token statsToken, - int flags, in ResultReceiver resultReceiver); + void hideSoftInput(in IBinder hideInputToken, in ImeTracker.Token statsToken, int flags, + in ResultReceiver resultReceiver); void updateEditorToolType(int toolType); diff --git a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl index 65a2f4be9901..457b9dd34644 100644 --- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl +++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl @@ -35,15 +35,17 @@ oneway interface IInputMethodPrivilegedOperations { void setInputMethod(String id, in AndroidFuture future /* T=Void */); void setInputMethodAndSubtype(String id, in InputMethodSubtype subtype, in AndroidFuture future /* T=Void */); - void hideMySoftInput(int flags, int reason, in AndroidFuture future /* T=Void */); - void showMySoftInput(int flags, in AndroidFuture future /* T=Void */); + void hideMySoftInput(in ImeTracker.Token statsToken, int flags, int reason, + in AndroidFuture future /* T=Void */); + void showMySoftInput(in ImeTracker.Token statsToken, int flags, int reason, + in AndroidFuture future /* T=Void */); void updateStatusIconAsync(String packageName, int iconId); void switchToPreviousInputMethod(in AndroidFuture future /* T=Boolean */); void switchToNextInputMethod(boolean onlyCurrentIme, in AndroidFuture future /* T=Boolean */); void shouldOfferSwitchingToNextInputMethod(in AndroidFuture future /* T=Boolean */); void notifyUserActionAsync(); void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible, - in @nullable ImeTracker.Token statsToken); + in ImeTracker.Token statsToken); void onStylusHandwritingReady(int requestId, int pid); void resetStylusHandwriting(int requestId); void switchKeyboardLayoutAsync(int direction); diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java index 9b7fa2f4f9e9..a0aad31d2e04 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java +++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java @@ -189,6 +189,8 @@ public final class InputMethodDebug { */ public static String softInputDisplayReasonToString(@SoftInputShowHideReason int reason) { switch (reason) { + case SoftInputShowHideReason.NOT_SET: + return "NOT_SET"; case SoftInputShowHideReason.SHOW_SOFT_INPUT: return "SHOW_SOFT_INPUT"; case SoftInputShowHideReason.ATTACH_NEW_INPUT: @@ -265,6 +267,36 @@ public final class InputMethodDebug { return "HIDE_SOFT_INPUT_CLOSE_CURRENT_SESSION"; case SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_VIEW: return "HIDE_SOFT_INPUT_FROM_VIEW"; + case SoftInputShowHideReason.SHOW_SOFT_INPUT_LEGACY_DIRECT: + return "SHOW_SOFT_INPUT_LEGACY_DIRECT"; + case SoftInputShowHideReason.HIDE_SOFT_INPUT_LEGACY_DIRECT: + return "HIDE_SOFT_INPUT_LEGACY_DIRECT"; + case SoftInputShowHideReason.SHOW_WINDOW_LEGACY_DIRECT: + return "SHOW_WINDOW_LEGACY_DIRECT"; + case SoftInputShowHideReason.HIDE_WINDOW_LEGACY_DIRECT: + return "HIDE_WINDOW_LEGACY_DIRECT"; + case SoftInputShowHideReason.RESET_NEW_CONFIGURATION: + return "RESET_NEW_CONFIGURATION"; + case SoftInputShowHideReason.UPDATE_CANDIDATES_VIEW_VISIBILITY: + return "UPDATE_CANDIDATES_VIEW_VISIBILITY"; + case SoftInputShowHideReason.CONTROLS_CHANGED: + return "CONTROLS_CHANGED"; + case SoftInputShowHideReason.DISPLAY_CONFIGURATION_CHANGED: + return "DISPLAY_CONFIGURATION_CHANGED"; + case SoftInputShowHideReason.DISPLAY_INSETS_CHANGED: + return "DISPLAY_INSETS_CHANGED"; + case SoftInputShowHideReason.DISPLAY_CONTROLS_CHANGED: + return "DISPLAY_CONTROLS_CHANGED"; + case SoftInputShowHideReason.UNBIND_CURRENT_METHOD: + return "UNBIND_CURRENT_METHOD"; + case SoftInputShowHideReason.HIDE_SOFT_INPUT_ON_ANIMATION_STATE_CHANGED: + return "HIDE_SOFT_INPUT_ON_ANIMATION_STATE_CHANGED"; + case SoftInputShowHideReason.HIDE_SOFT_INPUT_REQUEST_HIDE_WITH_CONTROL: + return "HIDE_SOFT_INPUT_REQUEST_HIDE_WITH_CONTROL"; + case SoftInputShowHideReason.SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT: + return "SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT"; + case SoftInputShowHideReason.SHOW_SOFT_INPUT_IMM_DEPRECATION: + return "SHOW_SOFT_INPUT_IMM_DEPRECATION"; default: return "Unknown=" + reason; } diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java index 792388dca339..635a227e5862 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java +++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java @@ -252,20 +252,21 @@ public final class InputMethodPrivilegedOperations { } /** - * Calls {@link IInputMethodPrivilegedOperations#hideMySoftInput(int, int, AndroidFuture)} - * - * @param reason the reason to hide soft input + * Calls {@link IInputMethodPrivilegedOperations#hideMySoftInput} */ @AnyThread - public void hideMySoftInput(@InputMethodManager.HideFlags int flags, - @SoftInputShowHideReason int reason) { + public void hideMySoftInput(@NonNull ImeTracker.Token statsToken, + @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason) { final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull(); if (ops == null) { + ImeTracker.forLogging().onFailed(statsToken, + ImeTracker.PHASE_IME_PRIVILEGED_OPERATIONS); return; } + ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_PRIVILEGED_OPERATIONS); try { final AndroidFuture<Void> future = new AndroidFuture<>(); - ops.hideMySoftInput(flags, reason, future); + ops.hideMySoftInput(statsToken, flags, reason, future); CompletableFutureUtil.getResult(future); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -273,17 +274,21 @@ public final class InputMethodPrivilegedOperations { } /** - * Calls {@link IInputMethodPrivilegedOperations#showMySoftInput(int, AndroidFuture)} + * Calls {@link IInputMethodPrivilegedOperations#showMySoftInput} */ @AnyThread - public void showMySoftInput(@InputMethodManager.ShowFlags int flags) { + public void showMySoftInput(@NonNull ImeTracker.Token statsToken, + @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason) { final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull(); if (ops == null) { + ImeTracker.forLogging().onFailed(statsToken, + ImeTracker.PHASE_IME_PRIVILEGED_OPERATIONS); return; } + ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_PRIVILEGED_OPERATIONS); try { final AndroidFuture<Void> future = new AndroidFuture<>(); - ops.showMySoftInput(flags, future); + ops.showMySoftInput(statsToken, flags, reason, future); CompletableFutureUtil.getResult(future); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -379,19 +384,19 @@ public final class InputMethodPrivilegedOperations { * {@link android.view.inputmethod.InputMethodManager#hideSoftInputFromWindow(IBinder, * int)} * @param setVisible {@code true} to set IME visible, else hidden. - * @param statsToken the token tracking the current IME request or {@code null} otherwise. + * @param statsToken the token tracking the current IME request. */ @AnyThread public void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible, - @Nullable ImeTracker.Token statsToken) { + @NonNull ImeTracker.Token statsToken) { final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull(); if (ops == null) { ImeTracker.forLogging().onFailed(statsToken, - ImeTracker.PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER); + ImeTracker.PHASE_IME_PRIVILEGED_OPERATIONS); return; } ImeTracker.forLogging().onProgress(statsToken, - ImeTracker.PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER); + ImeTracker.PHASE_IME_PRIVILEGED_OPERATIONS); try { ops.applyImeVisibilityAsync(showOrHideInputToken, setVisible, statsToken); } catch (RemoteException e) { diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java index 861b8a75f730..da738a01ec39 100644 --- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java +++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java @@ -34,6 +34,7 @@ import java.lang.annotation.Retention; */ @Retention(SOURCE) @IntDef(value = { + SoftInputShowHideReason.NOT_SET, SoftInputShowHideReason.SHOW_SOFT_INPUT, SoftInputShowHideReason.ATTACH_NEW_INPUT, SoftInputShowHideReason.SHOW_SOFT_INPUT_FROM_IME, @@ -72,8 +73,26 @@ import java.lang.annotation.Retention; SoftInputShowHideReason.HIDE_WHEN_INPUT_TARGET_INVISIBLE, SoftInputShowHideReason.HIDE_CLOSE_CURRENT_SESSION, SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_VIEW, + SoftInputShowHideReason.SHOW_SOFT_INPUT_LEGACY_DIRECT, + SoftInputShowHideReason.HIDE_SOFT_INPUT_LEGACY_DIRECT, + SoftInputShowHideReason.SHOW_WINDOW_LEGACY_DIRECT, + SoftInputShowHideReason.HIDE_WINDOW_LEGACY_DIRECT, + SoftInputShowHideReason.RESET_NEW_CONFIGURATION, + SoftInputShowHideReason.UPDATE_CANDIDATES_VIEW_VISIBILITY, + SoftInputShowHideReason.CONTROLS_CHANGED, + SoftInputShowHideReason.DISPLAY_CONFIGURATION_CHANGED, + SoftInputShowHideReason.DISPLAY_INSETS_CHANGED, + SoftInputShowHideReason.DISPLAY_CONTROLS_CHANGED, + SoftInputShowHideReason.UNBIND_CURRENT_METHOD, + SoftInputShowHideReason.HIDE_SOFT_INPUT_ON_ANIMATION_STATE_CHANGED, + SoftInputShowHideReason.HIDE_SOFT_INPUT_REQUEST_HIDE_WITH_CONTROL, + SoftInputShowHideReason.SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT, + SoftInputShowHideReason.SHOW_SOFT_INPUT_IMM_DEPRECATION, }) public @interface SoftInputShowHideReason { + /** Default, undefined reason. */ + int NOT_SET = ImeProtoEnums.REASON_NOT_SET; + /** Show soft input by {@link android.view.inputmethod.InputMethodManager#showSoftInput}. */ int SHOW_SOFT_INPUT = ImeProtoEnums.REASON_SHOW_SOFT_INPUT; @@ -291,4 +310,91 @@ public @interface SoftInputShowHideReason { * Hide soft input when {@link InputMethodManager#hideSoftInputFromView(View, int)} gets called. */ int HIDE_SOFT_INPUT_FROM_VIEW = ImeProtoEnums.REASON_HIDE_SOFT_INPUT_FROM_VIEW; + + /** + * Show soft input by legacy (discouraged) call to + * {@link android.inputmethodservice.InputMethodService.InputMethodImpl#showSoftInput}. + */ + int SHOW_SOFT_INPUT_LEGACY_DIRECT = ImeProtoEnums.REASON_SHOW_SOFT_INPUT_LEGACY_DIRECT; + + /** + * Hide soft input by legacy (discouraged) call to + * {@link android.inputmethodservice.InputMethodService.InputMethodImpl#hideSoftInput}. + */ + int HIDE_SOFT_INPUT_LEGACY_DIRECT = ImeProtoEnums.REASON_HIDE_SOFT_INPUT_LEGACY_DIRECT; + + /** + * Show soft input by legacy (discouraged) call to + * {@link android.inputmethodservice.InputMethodService#showWindow}. + */ + int SHOW_WINDOW_LEGACY_DIRECT = ImeProtoEnums.REASON_SHOW_WINDOW_LEGACY_DIRECT; + + /** + * Hide soft input by legacy (discouraged) call to + * {@link android.inputmethodservice.InputMethodService#hideWindow}. + */ + int HIDE_WINDOW_LEGACY_DIRECT = ImeProtoEnums.REASON_HIDE_WINDOW_LEGACY_DIRECT; + + /** + * Show / Hide soft input by + * {@link android.inputmethodservice.InputMethodService#resetStateForNewConfiguration}. + */ + int RESET_NEW_CONFIGURATION = ImeProtoEnums.REASON_RESET_NEW_CONFIGURATION; + + /** + * Show / Hide soft input by + * {@link android.inputmethodservice.InputMethodService#updateCandidatesVisibility}. + */ + int UPDATE_CANDIDATES_VIEW_VISIBILITY = ImeProtoEnums.REASON_UPDATE_CANDIDATES_VIEW_VISIBILITY; + + /** + * Show / Hide soft input by {@link android.view.InsetsController#onControlsChanged}. + */ + int CONTROLS_CHANGED = ImeProtoEnums.REASON_CONTROLS_CHANGED; + + /** + * Show soft input by + * {@link com.android.wm.shell.common.DisplayImeController#onDisplayConfigurationChanged}. + */ + int DISPLAY_CONFIGURATION_CHANGED = ImeProtoEnums.REASON_DISPLAY_CONFIGURATION_CHANGED; + + /** + * Show soft input by + * {@link com.android.wm.shell.common.DisplayImeController.PerDisplay#insetsChanged}. + */ + int DISPLAY_INSETS_CHANGED = ImeProtoEnums.REASON_DISPLAY_INSETS_CHANGED; + + /** + * Show / Hide soft input by + * {@link com.android.wm.shell.common.DisplayImeController.PerDisplay#insetsControlChanged}. + */ + int DISPLAY_CONTROLS_CHANGED = ImeProtoEnums.REASON_DISPLAY_CONTROLS_CHANGED; + + /** Hide soft input by + * {@link com.android.server.inputmethod.InputMethodManagerService#onUnbindCurrentMethodByReset}. + */ + int UNBIND_CURRENT_METHOD = ImeProtoEnums.REASON_UNBIND_CURRENT_METHOD; + + /** Hide soft input by {@link android.view.ImeInsetsSourceConsumer#onAnimationStateChanged}. */ + int HIDE_SOFT_INPUT_ON_ANIMATION_STATE_CHANGED = + ImeProtoEnums.REASON_HIDE_SOFT_INPUT_ON_ANIMATION_STATE_CHANGED; + + /** Hide soft input when we already have a {@link android.view.InsetsSourceControl} by + * {@link android.view.ImeInsetsSourceConsumer#requestHide}. + */ + int HIDE_SOFT_INPUT_REQUEST_HIDE_WITH_CONTROL = + ImeProtoEnums.REASON_HIDE_SOFT_INPUT_REQUEST_HIDE_WITH_CONTROL; + + /** + * Show soft input by + * {@link android.inputmethodservice.InputMethodService#onToggleSoftInput(int, int)}. + */ + int SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT = + ImeProtoEnums.REASON_SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT; + + /** + * Show soft input by the deprecated + * {@link InputMethodManager#showSoftInputFromInputMethod(IBinder, int)}. + */ + int SHOW_SOFT_INPUT_IMM_DEPRECATION = ImeProtoEnums.REASON_SHOW_SOFT_INPUT_IMM_DEPRECATION; } diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 1f4503a69428..dc3b5a8846cf 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -65,12 +65,21 @@ interface IInputMethodManager { InputMethodSubtype getLastInputMethodSubtype(int userId); boolean showSoftInput(in IInputMethodClient client, @nullable IBinder windowToken, - in @nullable ImeTracker.Token statsToken, int flags, int lastClickToolType, + in ImeTracker.Token statsToken, int flags, int lastClickToolType, in @nullable ResultReceiver resultReceiver, int reason); boolean hideSoftInput(in IInputMethodClient client, @nullable IBinder windowToken, - in @nullable ImeTracker.Token statsToken, int flags, + in ImeTracker.Token statsToken, int flags, in @nullable ResultReceiver resultReceiver, int reason); + /** + * A test API for CTS to request hiding the current soft input window, with the request origin + * on the server side. + */ + @EnforcePermission("TEST_INPUT_METHOD") + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " + + "android.Manifest.permission.TEST_INPUT_METHOD)") + void hideSoftInputFromServerForTest(); + // TODO(b/293640003): Remove method once Flags.useZeroJankProxy() is enabled. // If windowToken is null, this just does startInput(). Otherwise this reports that a window // has gained focus, and if 'editorInfo' is non-null then also does startInput. diff --git a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl index 918d9c029ef5..1d0e97295571 100644 --- a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl +++ b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl @@ -39,6 +39,6 @@ interface IRemoteViewsFactory { boolean hasStableIds(); @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) boolean isCreated(); - RemoteViews.RemoteCollectionItems getRemoteCollectionItems(); + RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java index b7cb3926d936..7c9fda5c6631 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java +++ b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java @@ -92,7 +92,7 @@ public class WireBuffer { mIndex = 0; mStartingIndex = 0; mSize = 0; - if (expectedSize > mMaxSize) { + if (expectedSize >= mMaxSize) { resize(expectedSize); } } diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 240028c191bc..76e71380017c 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -400,6 +400,7 @@ cc_library_shared_for_libandroid_runtime { "libbinary_parse", "libdng_sdk", "libft2", + "libhostgraphics", "libhwui", "libimage_type_recognition", "libjpeg", diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/LayoutlibLoader.cpp index d5f17da0a072..071f933db065 100644 --- a/core/jni/LayoutlibLoader.cpp +++ b/core/jni/LayoutlibLoader.cpp @@ -196,14 +196,6 @@ static vector<string> parseCsv(const string& csvString) { return result; } -static vector<string> parseCsv(JNIEnv* env, jstring csvJString) { - const char* charArray = env->GetStringUTFChars(csvJString, 0); - string csvString(charArray); - vector<string> result = parseCsv(csvString); - env->ReleaseStringUTFChars(csvJString, charArray); - return result; -} - void LayoutlibLogger(base::LogId, base::LogSeverity severity, const char* tag, const char* file, unsigned int line, const char* message) { JNIEnv* env = AndroidRuntime::getJNIEnv(); @@ -395,20 +387,28 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) { jmethodID getPropertyMethod = GetStaticMethodIDOrDie(env, system, "getProperty", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"); + // Java system properties that contain LayoutLib config. The initial values in the map + // are the default values if the property is not specified. + std::unordered_map<std::string, std::string> systemProperties = + {{"core_native_classes", ""}, + {"register_properties_during_load", ""}, + {"icu.data.path", ""}, + {"use_bridge_for_logging", ""}, + {"keyboard_paths", ""}}; + + for (auto& [name, defaultValue] : systemProperties) { + jstring propertyString = + (jstring)env->CallStaticObjectMethod(system, getPropertyMethod, + env->NewStringUTF(name.c_str()), + env->NewStringUTF(defaultValue.c_str())); + const char* propertyChars = env->GetStringUTFChars(propertyString, 0); + systemProperties[name] = string(propertyChars); + env->ReleaseStringUTFChars(propertyString, propertyChars); + } // Get the names of classes that need to register their native methods - auto nativesClassesJString = - (jstring)env->CallStaticObjectMethod(system, getPropertyMethod, - env->NewStringUTF("core_native_classes"), - env->NewStringUTF("")); - vector<string> classesToRegister = parseCsv(env, nativesClassesJString); - - jstring registerProperty = - (jstring)env->CallStaticObjectMethod(system, getPropertyMethod, - env->NewStringUTF( - "register_properties_during_load"), - env->NewStringUTF("")); - const char* registerPropertyString = env->GetStringUTFChars(registerProperty, 0); - if (strcmp(registerPropertyString, "true") == 0) { + vector<string> classesToRegister = parseCsv(systemProperties["core_native_classes"]); + + if (systemProperties["register_properties_during_load"] == "true") { // Set the system properties first as they could be used in the static initialization of // other classes if (register_android_os_SystemProperties(env) < 0) { @@ -423,35 +423,20 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) { env->CallStaticVoidMethod(bridge, setSystemPropertiesMethod); property_initialize_ro_cpu_abilist(); } - env->ReleaseStringUTFChars(registerProperty, registerPropertyString); if (register_jni_procs(gRegJNIMap, classesToRegister, env) < 0) { return JNI_ERR; } - // Set the location of ICU data - auto stringPath = (jstring)env->CallStaticObjectMethod(system, getPropertyMethod, - env->NewStringUTF("icu.data.path"), - env->NewStringUTF("")); - const char* path = env->GetStringUTFChars(stringPath, 0); - - if (strcmp(path, "**n/a**") != 0) { - bool icuInitialized = init_icu(path); + if (!systemProperties["register_properties_during_load"].empty()) { + // Set the location of ICU data + bool icuInitialized = init_icu(systemProperties["icu.data.path"].c_str()); if (!icuInitialized) { - fprintf(stderr, "Failed to initialize ICU\n"); return JNI_ERR; } - } else { - fprintf(stderr, "Skip initializing ICU\n"); } - env->ReleaseStringUTFChars(stringPath, path); - - jstring useJniProperty = - (jstring)env->CallStaticObjectMethod(system, getPropertyMethod, - env->NewStringUTF("use_bridge_for_logging"), - env->NewStringUTF("")); - const char* useJniString = env->GetStringUTFChars(useJniProperty, 0); - if (strcmp(useJniString, "true") == 0) { + + if (systemProperties["use_bridge_for_logging"] == "true") { layoutLog = FindClassOrDie(env, "com/android/ide/common/rendering/api/ILayoutLog"); layoutLog = MakeGlobalRefOrDie(env, layoutLog); logMethodId = GetMethodIDOrDie(env, layoutLog, "logAndroidFramework", @@ -468,23 +453,16 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) { // initialize logging, so ANDROD_LOG_TAGS env variable is respected android::base::InitLogging(nullptr, android::base::StderrLogger); } - env->ReleaseStringUTFChars(useJniProperty, useJniString); // Use English locale for number format to ensure correct parsing of floats when using strtof setlocale(LC_NUMERIC, "en_US.UTF-8"); - auto keyboardPathsJString = - (jstring)env->CallStaticObjectMethod(system, getPropertyMethod, - env->NewStringUTF("keyboard_paths"), - env->NewStringUTF("")); - const char* keyboardPathsString = env->GetStringUTFChars(keyboardPathsJString, 0); - if (strcmp(keyboardPathsString, "**n/a**") != 0) { - vector<string> keyboardPaths = parseCsv(env, keyboardPathsJString); + if (!systemProperties["keyboard_paths"].empty()) { + vector<string> keyboardPaths = parseCsv(systemProperties["keyboard_paths"]); init_keyboard(env, keyboardPaths); } else { fprintf(stderr, "Skip initializing keyboard\n"); } - env->ReleaseStringUTFChars(keyboardPathsJString, keyboardPathsString); return JNI_VERSION_1_6; } diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp index b39d5cf3842b..23adb8f7e2a3 100644 --- a/core/jni/android_view_MotionEvent.cpp +++ b/core/jni/android_view_MotionEvent.cpp @@ -622,6 +622,18 @@ static jlong android_view_MotionEvent_nativeCopy(jlong destNativePtr, jlong sour return reinterpret_cast<jlong>(destEvent); } +static jlong android_view_MotionEvent_nativeSplit(jlong destNativePtr, jlong sourceNativePtr, + jint idBits) { + MotionEvent* destEvent = reinterpret_cast<MotionEvent*>(destNativePtr); + if (!destEvent) { + destEvent = new MotionEvent(); + } + MotionEvent* sourceEvent = reinterpret_cast<MotionEvent*>(sourceNativePtr); + destEvent->splitFrom(*sourceEvent, static_cast<std::bitset<MAX_POINTER_ID + 1>>(idBits), + InputEvent::nextId()); + return reinterpret_cast<jlong>(destEvent); +} + static jint android_view_MotionEvent_nativeGetId(jlong nativePtr) { MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr); return event->getId(); @@ -837,6 +849,7 @@ static const JNINativeMethod gMotionEventMethods[] = { // --------------- @CriticalNative ------------------ {"nativeCopy", "(JJZ)J", (void*)android_view_MotionEvent_nativeCopy}, + {"nativeSplit", "(JJI)J", (void*)android_view_MotionEvent_nativeSplit}, {"nativeGetId", "(J)I", (void*)android_view_MotionEvent_nativeGetId}, {"nativeGetDeviceId", "(J)I", (void*)android_view_MotionEvent_nativeGetDeviceId}, {"nativeGetSource", "(J)I", (void*)android_view_MotionEvent_nativeGetSource}, diff --git a/core/proto/android/hardware/sensorprivacy.proto b/core/proto/android/hardware/sensorprivacy.proto index e368c6a98a75..53aa710d57b4 100644 --- a/core/proto/android/hardware/sensorprivacy.proto +++ b/core/proto/android/hardware/sensorprivacy.proto @@ -91,9 +91,7 @@ message SensorPrivacyIndividualEnabledSensorProto { enum StateType { ENABLED = 1; DISABLED = 2; - AUTO_DRIVER_ASSISTANCE_HELPFUL_APPS = 3; - AUTO_DRIVER_ASSISTANCE_REQUIRED_APPS = 4; - AUTO_DRIVER_ASSISTANCE_APPS = 5; + ENABLED_EXCEPT_ALLOWLISTED_APPS = 3; } // DEPRECATED diff --git a/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto b/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto index 5a18d9e627e8..b75d545b1305 100644 --- a/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto +++ b/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto @@ -39,7 +39,7 @@ message InputMethodManagerServiceProto { optional string cur_token = 14; optional int32 cur_token_display_id = 15; optional bool system_ready = 16; - optional int32 last_switch_user_id = 17; + reserved 17; // deprecated last_switch_user_id optional bool have_connection = 18; optional bool bound_to_method = 19; optional bool is_interactive = 20; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 487b5be9ef5c..52bad21e156d 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -8621,6 +8621,10 @@ android:permission="android.permission.BIND_JOB_SERVICE"> </service> + <service android:name="com.android.system.virtualmachine.SecretkeeperJobService" + android:permission="android.permission.BIND_JOB_SERVICE"> + </service> + <service android:name="com.android.server.PruneInstantAppsJobService" android:permission="android.permission.BIND_JOB_SERVICE" > </service> @@ -8675,7 +8679,7 @@ android:permission="android.permission.BIND_JOB_SERVICE"> </service> - <service android:name="com.android.server.companion.InactiveAssociationsRemovalService" + <service android:name="com.android.server.companion.association.InactiveAssociationsRemovalService" android:permission="android.permission.BIND_JOB_SERVICE"> </service> diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index d419cee4eef5..7155bf4241e6 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -633,8 +633,8 @@ <string name="permdesc_imagesWrite" msgid="5195054463269193317">"به برنامه اجازه میدهد مجموعه عکستان را تغییر دهد."</string> <string name="permlab_mediaLocation" msgid="7368098373378598066">"خواندن مکانها از مجموعه رسانه شما"</string> <string name="permdesc_mediaLocation" msgid="597912899423578138">"به برنامه اجازه میدهد مکانها را از مجموعه رسانهتان بخواند."</string> - <string name="biometric_app_setting_name" msgid="3339209978734534457">"استفاده از زیستسنجشی"</string> - <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"استفاده از زیستسنجشی یا قفل صفحه"</string> + <string name="biometric_app_setting_name" msgid="3339209978734534457">"استفاده از دادههای زیستسنجشی"</string> + <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"استفاده از دادههای زیستسنجشی یا قفل صفحه"</string> <string name="biometric_dialog_default_title" msgid="55026799173208210">"تأیید کنید این شمایید"</string> <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"برای ادامه، از زیستسنجشی استفاده کنید"</string> <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"برای ادامه، از زیستسنجشی یا قفل صفحه استفاده کنید"</string> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index c882938b63ce..d89f23614179 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -301,7 +301,9 @@ 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. --> + 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. + --> <flag name="retailDemo" value="0x1000000" /> <!-- Additional flag from base permission type: this permission will be granted to the recents app. --> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 967edde83cc9..4f20fceef8d1 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3348,26 +3348,23 @@ <string name="config_carrierAppInstallDialogComponent" translatable="false" >com.android.simappdialog/com.android.simappdialog.InstallCarrierAppActivity</string> - <!-- Name of the default framework dialog that is used to get or save an app credential. + <!-- Name of the fallback CredentialManager dialog that is used to get or save an app + credential. - This UI should be always launch-able and is used as a fallback when an oem replacement activity - (defined at config_oemCredentialManagerDialogComponent) is undefined / not found. --> - <string name="config_credentialManagerDialogComponent" translatable="false" - >com.android.credentialmanager/com.android.credentialmanager.CredentialSelectorActivity</string> - <!-- Whether to allow the credential selector activity to be replaced by an activity at - run-time (restricted to the privileged activity specified by - config_credentialSelectorActivityName). + If empty, no fallback will be used. IMPORTANT: In that case the OEM dialog value specified in + config_oemCredentialManagerDialogComponent must always launch-able. Otherwise, the + CredentialManager API contract is broken. - When disabled, the fallback activity defined at - config_credentialManagerDialogComponent will be used instead. --> - <bool name="config_enableOemCredentialManagerDialogComponent" translatable="false">true</bool> + If specified, this UI should be always launch-able. It will be used as a fallback when the OEM + dialog value specified in config_oemCredentialManagerDialogComponent) is undefined / not + found. --> + <string name="config_fallbackCredentialManagerDialogComponent" translatable="false" + >com.android.credentialmanager/com.android.credentialmanager.CredentialSelectorActivity</string> <!-- Fully qualified activity name providing the credential selector UI, that serves the - CredentialManager APIs. + CredentialManager APIs. Must be a system app component. - Used only when config_enableOemCredentialManagerDialogComponent is true. - - If the activity specified cannot be found or launched, then the fallback activity defined at - config_credentialManagerDialogComponent will be used instead. --> + If empty, or if this activity specified cannot be found or launched, then the fallback activity + defined at config_fallbackCredentialManagerDialogComponent will be used instead. --> <string name="config_oemCredentialManagerDialogComponent" translatable="false"></string> <!-- Name of the broadcast receiver that is used to receive provider change events --> @@ -3554,10 +3551,6 @@ <string name="config_keyguardComponent" translatable="false" >com.android.systemui/com.android.systemui.keyguard.KeyguardService</string> - <!-- Screen record dialog component --> - <string name="config_screenRecorderComponent" translatable="false" - >com.android.systemui/com.android.systemui.screenrecord.ScreenRecordDialog</string> - <!-- The component name of a special dock app that merely launches a dream. We don't want to launch this app when docked because it causes an unnecessary activity transition. We just want to start the dream. --> @@ -3614,8 +3607,7 @@ <!-- Whether this device prefers to show snapshot or splash screen on back predict target. When set true, there will create windowless starting surface for the preview target, so it won't affect activity's lifecycle. This should only be disabled on low-ram device. --> - <!-- TODO(b/268563842) enable once activity snapshot is ready --> - <bool name="config_predictShowStartingSurface">false</bool> + <bool name="config_predictShowStartingSurface">true</bool> <!-- default window ShowCircularMask property --> <bool name="config_windowShowCircularMask">false</bool> diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml index 5e3e1b0bbb43..6e56fe2abfdd 100644 --- a/core/res/res/values/config_telephony.xml +++ b/core/res/res/values/config_telephony.xml @@ -334,4 +334,9 @@ <bool name="config_enable_cellular_on_boot_default">true</bool> <java-symbol type="bool" name="config_enable_cellular_on_boot_default" /> + <!-- Defines metrics pull cooldown period. The default cooldown period is 23 hours, + some Telephony metrics need to be pulled more frequently --> + <integer name="config_metrics_pull_cooldown_millis">82800000</integer> + <java-symbol type="integer" name="config_metrics_pull_cooldown_millis" /> + </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index a1804672da73..150951fd57f8 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -375,7 +375,6 @@ <java-symbol type="string" name="config_recentsComponentName" /> <java-symbol type="string" name="config_systemUIServiceComponent" /> <java-symbol type="string" name="config_controlsPackage" /> - <java-symbol type="string" name="config_screenRecorderComponent" /> <java-symbol type="string" name="config_somnambulatorComponent" /> <java-symbol type="string" name="config_screenshotAppClipsServiceComponent" /> <java-symbol type="string" name="config_screenshotServiceComponent" /> @@ -2297,8 +2296,7 @@ <java-symbol type="string" name="config_customVpnAlwaysOnDisconnectedDialogComponent" /> <java-symbol type="string" name="config_platformVpnConfirmDialogComponent" /> <java-symbol type="string" name="config_carrierAppInstallDialogComponent" /> - <java-symbol type="string" name="config_credentialManagerDialogComponent" /> - <java-symbol type="bool" name="config_enableOemCredentialManagerDialogComponent" /> + <java-symbol type="string" name="config_fallbackCredentialManagerDialogComponent" /> <java-symbol type="string" name="config_oemCredentialManagerDialogComponent" /> <java-symbol type="string" name="config_credentialManagerReceiverComponent" /> <java-symbol type="string" name="config_defaultNetworkScorerPackageName" /> diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml index 61e6a36839ff..7d740ef76daf 100644 --- a/core/res/res/xml/sms_short_codes.xml +++ b/core/res/res/xml/sms_short_codes.xml @@ -88,6 +88,9 @@ <!-- Colombia: 1-6 digits (not confirmed) --> <shortcode country="co" pattern="\\d{1,6}" free="890350|908160|892255|898002|898880|899960|899948|87739|85517|491289" /> + <!-- Costa Rica --> + <shortcode country="cr" pattern="\\d{1,6}" free="466453" /> + <!-- Cyprus: 4-6 digits (not confirmed), known premium codes listed, plus EU --> <shortcode country="cy" pattern="\\d{4,6}" premium="7510" free="116\\d{3}" /> @@ -143,6 +146,9 @@ <!-- Greece: 5 digits (54xxx, 19yxx, x=0-9, y=0-5): http://www.cmtelecom.com/premium-sms/greece --> <shortcode country="gr" pattern="\\d{5}" premium="54\\d{3}|19[0-5]\\d{2}" free="116\\d{3}|12115" /> + <!-- Guatemala --> + <shortcode country="gt" pattern="\\d{1,6}" free="466453" /> + <!-- Croatia --> <shortcode country="hr" pattern="\\d{1,5}" free="13062" /> @@ -241,10 +247,10 @@ <shortcode country="ph" pattern="\\d{1,5}" free="2147|5495|5496" /> <!-- Pakistan --> - <shortcode country="pk" pattern="\\d{1,5}" free="2057" /> + <shortcode country="pk" pattern="\\d{1,5}" free="2057|9092" /> <!-- Palestine: 5 digits, known premium codes listed --> - <shortcode country="ps" pattern="\\d{1,5}" free="37477" /> + <shortcode country="ps" pattern="\\d{1,5}" free="37477|6681" /> <!-- Poland: 4-5 digits (not confirmed), known premium codes listed, plus EU --> <shortcode country="pl" pattern="\\d{4,5}" premium="74240|79(?:10|866)|92525" free="116\\d{3}|8012|80921" /> @@ -324,4 +330,7 @@ <!-- South Africa --> <shortcode country="za" pattern="\\d{1,5}" free="44136|30791|36056" /> + <!-- Zimbabwe --> + <shortcode country="zw" pattern="\\d{1,5}" free="33679" /> + </shortcodes> diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index e72beee71e50..24031cad0a3e 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -101,6 +101,7 @@ android_test { "flickerlib-trace_processor_shell", "mockito-target-extended-minus-junit4", "TestParameterInjector", + "android.content.res.flags-aconfig-java", ], libs: [ diff --git a/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml b/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml index 866e1a95c3f5..502921263462 100644 --- a/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml +++ b/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml @@ -14,105 +14,150 @@ ~ limitations under the License --> -<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android" +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" - android:id="@+id/horizontal_scroll_view"> + android:orientation="vertical"> - <LinearLayout + <HorizontalScrollView android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal"> - - <View - android:background="#F00" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#880" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#0F0" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#088" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#00F" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#808" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#F00" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#880" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#0F0" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#088" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#00F" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#808" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#F00" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#880" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#0F0" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#088" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#00F" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#808" - android:layout_width="100dp" - android:layout_height="100dp" /> - - </LinearLayout> -</HorizontalScrollView> + android:layout_height="match_parent" + android:id="@+id/horizontal_scroll_view"> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <View + android:background="#F00" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#880" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#0F0" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#088" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#00F" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#808" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#F00" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#880" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#0F0" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#088" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#00F" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#808" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#F00" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#880" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#0F0" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#088" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#00F" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#808" + android:layout_width="100dp" + android:layout_height="100dp" /> + + </LinearLayout> + </HorizontalScrollView> + + <view + class="android.widget.HorizontalScrollViewFunctionalTest$MyHorizontalScrollView" + android:id="@+id/my_horizontal_scroll_view" + android:layout_width="90dp" + android:layout_height="90dp" + android:background="#FFF" + android:defaultFocusHighlightEnabled="false"> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical"> + <View + android:background="#00F" + android:layout_width="90dp" + android:layout_height="50dp"/> + <View + android:background="#0FF" + android:layout_width="90dp" + android:layout_height="50dp"/> + <View + android:background="#0F0" + android:layout_width="90dp" + android:layout_height="50dp"/> + <View + android:background="#FF0" + android:layout_width="90dp" + android:layout_height="50dp"/> + <View + android:background="#F00" + android:layout_width="90dp" + android:layout_height="50dp"/> + <View + android:background="#F0F" + android:layout_width="90dp" + android:layout_height="50dp"/> + </LinearLayout> + </view> +</LinearLayout>
\ No newline at end of file diff --git a/core/tests/coretests/res/layout/activity_scroll_view.xml b/core/tests/coretests/res/layout/activity_scroll_view.xml index 61fabf8ee437..db8cd026e71a 100644 --- a/core/tests/coretests/res/layout/activity_scroll_view.xml +++ b/core/tests/coretests/res/layout/activity_scroll_view.xml @@ -14,105 +14,150 @@ ~ limitations under the License --> -<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" - android:id="@+id/scroll_view"> + android:orientation="vertical"> - <LinearLayout + <ScrollView android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical"> - - <View - android:background="#F00" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#880" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#0F0" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#088" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#00F" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#808" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#F00" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#880" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#0F0" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#088" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#00F" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#808" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#F00" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#880" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#0F0" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#088" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#00F" - android:layout_width="100dp" - android:layout_height="100dp" /> - - <View - android:background="#808" - android:layout_width="100dp" - android:layout_height="100dp" /> - - </LinearLayout> -</ScrollView> + android:layout_height="match_parent" + android:id="@+id/scroll_view"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <View + android:background="#F00" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#880" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#0F0" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#088" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#00F" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#808" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#F00" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#880" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#0F0" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#088" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#00F" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#808" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#F00" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#880" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#0F0" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#088" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#00F" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#808" + android:layout_width="100dp" + android:layout_height="100dp" /> + + </LinearLayout> + </ScrollView> + + <view + class="android.widget.ScrollViewFunctionalTest$MyScrollView" + android:id="@+id/my_scroll_view" + android:layout_width="90dp" + android:layout_height="90dp" + android:background="#FFF" + android:defaultFocusHighlightEnabled="false"> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical"> + <View + android:background="#00F" + android:layout_width="90dp" + android:layout_height="50dp"/> + <View + android:background="#0FF" + android:layout_width="90dp" + android:layout_height="50dp"/> + <View + android:background="#0F0" + android:layout_width="90dp" + android:layout_height="50dp"/> + <View + android:background="#FF0" + android:layout_width="90dp" + android:layout_height="50dp"/> + <View + android:background="#F00" + android:layout_width="90dp" + android:layout_height="50dp"/> + <View + android:background="#F0F" + android:layout_width="90dp" + android:layout_height="50dp"/> + </LinearLayout> + </view> +</LinearLayout> diff --git a/core/tests/coretests/res/layout/view_velocity_test.xml b/core/tests/coretests/res/layout/view_velocity_test.xml new file mode 100644 index 000000000000..98154a4b6b78 --- /dev/null +++ b/core/tests/coretests/res/layout/view_velocity_test.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/frameLayout" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <View + android:id="@+id/moving_view" + android:layout_width="50dp" + android:layout_height="50dp" /> +</FrameLayout> diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index d115bf306b45..927c67cb1d36 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -21,16 +21,22 @@ import static android.content.Intent.ACTION_EDIT; import static android.content.Intent.ACTION_VIEW; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; +import static com.android.window.flags.Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG; + import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.annotation.NonNull; @@ -46,6 +52,7 @@ import android.app.servertransaction.ActivityConfigurationChangeItem; import android.app.servertransaction.ActivityRelaunchItem; import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.ClientTransactionItem; +import android.app.servertransaction.ClientTransactionListenerController; import android.app.servertransaction.ConfigurationChangeItem; import android.app.servertransaction.NewIntentItem; import android.app.servertransaction.ResumeActivityItem; @@ -60,7 +67,9 @@ import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; import android.os.Bundle; import android.os.IBinder; +import android.os.RemoteException; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.DisplayMetrics; import android.util.MergedConfiguration; import android.view.Display; @@ -81,11 +90,14 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; import java.util.function.Consumer; /** @@ -103,11 +115,17 @@ public class ActivityThreadTest { // few sequence numbers the framework used to launch the test activity. private static final int BASE_SEQ = 10000000; - @Rule + @Rule(order = 0) public final ActivityTestRule<TestActivity> mActivityTestRule = new ActivityTestRule<>(TestActivity.class, true /* initialTouchMode */, false /* launchActivity */); + @Rule(order = 1) + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); + + @Mock + private BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener; + private WindowTokenClientController mOriginalWindowTokenClientController; private Configuration mOriginalAppConfig; @@ -115,6 +133,8 @@ public class ActivityThreadTest { @Before public void setup() { + MockitoAnnotations.initMocks(this); + // Keep track of the original controller, so that it can be used to restore in tearDown() // when there is override in some test cases. mOriginalWindowTokenClientController = WindowTokenClientController.getInstance(); @@ -129,6 +149,8 @@ public class ActivityThreadTest { mCreatedVirtualDisplays = null; } WindowTokenClientController.overrideForTesting(mOriginalWindowTokenClientController); + ClientTransactionListenerController.getInstance() + .unregisterActivityWindowInfoChangedListener(mActivityWindowInfoListener); InstrumentationRegistry.getInstrumentation().runOnMainSync( () -> restoreConfig(ActivityThread.currentActivityThread(), mOriginalAppConfig)); } @@ -783,6 +805,101 @@ public class ActivityThreadTest { verify(windowTokenClientController).onWindowContextWindowRemoved(clientToken); } + @Test + public void testActivityWindowInfoChanged_activityLaunch() { + mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG); + + ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener( + mActivityWindowInfoListener); + + final Activity activity = mActivityTestRule.launchActivity(new Intent()); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + final ActivityClientRecord activityClientRecord = getActivityClientRecord(activity); + + verify(mActivityWindowInfoListener).accept(activityClientRecord.token, + activityClientRecord.getActivityWindowInfo()); + } + + @Test + public void testActivityWindowInfoChanged_activityRelaunch() throws RemoteException { + mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG); + + ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener( + mActivityWindowInfoListener); + + final Activity activity = mActivityTestRule.launchActivity(new Intent()); + final IApplicationThread appThread = activity.getActivityThread().getApplicationThread(); + appThread.scheduleTransaction(newRelaunchResumeTransaction(activity)); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + final ActivityClientRecord activityClientRecord = getActivityClientRecord(activity); + + // The same ActivityWindowInfo won't trigger duplicated callback. + verify(mActivityWindowInfoListener).accept(activityClientRecord.token, + activityClientRecord.getActivityWindowInfo()); + + final Configuration currentConfig = activity.getResources().getConfiguration(); + final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo(); + activityWindowInfo.set(true /* isEmbedded */, new Rect(0, 0, 1000, 2000), + new Rect(0, 0, 1000, 1000)); + final ActivityRelaunchItem relaunchItem = ActivityRelaunchItem.obtain( + activity.getActivityToken(), null, null, 0, + new MergedConfiguration(currentConfig, currentConfig), + false /* preserveWindow */, activityWindowInfo); + final ClientTransaction transaction = newTransaction(activity); + transaction.addTransactionItem(relaunchItem); + appThread.scheduleTransaction(transaction); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + + verify(mActivityWindowInfoListener).accept(activityClientRecord.token, + activityWindowInfo); + } + + @Test + public void testActivityWindowInfoChanged_activityConfigurationChanged() + throws RemoteException { + mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG); + + ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener( + mActivityWindowInfoListener); + + final Activity activity = mActivityTestRule.launchActivity(new Intent()); + final IApplicationThread appThread = activity.getActivityThread().getApplicationThread(); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + + clearInvocations(mActivityWindowInfoListener); + final Configuration config = new Configuration(activity.getResources().getConfiguration()); + config.seq++; + final Rect taskBounds = new Rect(0, 0, 1000, 2000); + final Rect taskFragmentBounds = new Rect(0, 0, 1000, 1000); + final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo(); + activityWindowInfo.set(true /* isEmbedded */, taskBounds, taskFragmentBounds); + final ActivityConfigurationChangeItem activityConfigurationChangeItem = + ActivityConfigurationChangeItem.obtain( + activity.getActivityToken(), config, activityWindowInfo); + final ClientTransaction transaction = newTransaction(activity); + transaction.addTransactionItem(activityConfigurationChangeItem); + appThread.scheduleTransaction(transaction); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + + verify(mActivityWindowInfoListener).accept(activity.getActivityToken(), + activityWindowInfo); + + clearInvocations(mActivityWindowInfoListener); + final ActivityWindowInfo activityWindowInfo2 = new ActivityWindowInfo(); + activityWindowInfo2.set(true /* isEmbedded */, taskBounds, taskFragmentBounds); + config.seq++; + final ActivityConfigurationChangeItem activityConfigurationChangeItem2 = + ActivityConfigurationChangeItem.obtain( + activity.getActivityToken(), config, activityWindowInfo2); + final ClientTransaction transaction2 = newTransaction(activity); + transaction2.addTransactionItem(activityConfigurationChangeItem2); + appThread.scheduleTransaction(transaction); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + + // The same ActivityWindowInfo won't trigger duplicated callback. + verify(mActivityWindowInfoListener, never()).accept(any(), any()); + } + /** * Calls {@link ActivityThread#handleActivityConfigurationChanged(ActivityClientRecord, * Configuration, int, ActivityWindowInfo)} to try to push activity configuration to the @@ -871,7 +988,7 @@ public class ActivityThreadTest { @NonNull private static ClientTransaction newStopTransaction(@NonNull Activity activity) { final StopActivityItem stopStateRequest = StopActivityItem.obtain( - activity.getActivityToken(), 0 /* configChanges */); + activity.getActivityToken()); final ClientTransaction transaction = newTransaction(activity); transaction.addTransactionItem(stopStateRequest); diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java index 4db5d1bf4f67..990739745f24 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java +++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java @@ -136,7 +136,7 @@ public class ClientTransactionItemTest { @Test public void testDestroyActivityItem_preExecute() { final DestroyActivityItem item = DestroyActivityItem - .obtain(mActivityToken, false /* finished */, 123 /* configChanges */); + .obtain(mActivityToken, false /* finished */); item.preExecute(mHandler); assertEquals(1, mActivitiesToBeDestroyed.size()); @@ -146,7 +146,7 @@ public class ClientTransactionItemTest { @Test public void testDestroyActivityItem_postExecute() { final DestroyActivityItem item = DestroyActivityItem - .obtain(mActivityToken, false /* finished */, 123 /* configChanges */); + .obtain(mActivityToken, false /* finished */); item.preExecute(mHandler); item.postExecute(mHandler, mPendingActions); @@ -156,11 +156,11 @@ public class ClientTransactionItemTest { @Test public void testDestroyActivityItem_execute() { final DestroyActivityItem item = DestroyActivityItem - .obtain(mActivityToken, false /* finished */, 123 /* configChanges */); + .obtain(mActivityToken, false /* finished */); item.execute(mHandler, mActivityClientRecord, mPendingActions); verify(mHandler).handleDestroyActivity(eq(mActivityClientRecord), eq(false) /* finishing */, - eq(123) /* configChanges */, eq(false) /* getNonConfigInstance */, any()); + eq(false) /* getNonConfigInstance */, any()); } @Test diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java index 213fd7bd494d..77d31a5f27e7 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java +++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java @@ -22,21 +22,29 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static com.android.window.flags.Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerGlobal; import android.hardware.display.IDisplayManager; import android.os.Handler; +import android.os.IBinder; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import android.view.DisplayInfo; +import android.window.ActivityWindowInfo; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.window.flags.Flags; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -44,6 +52,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.function.BiConsumer; + /** * Tests for {@link ClientTransactionListenerController}. * @@ -62,6 +72,10 @@ public class ClientTransactionListenerControllerTest { private IDisplayManager mIDisplayManager; @Mock private DisplayManager.DisplayListener mListener; + @Mock + private BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener; + @Mock + private IBinder mActivityToken; private DisplayManagerGlobal mDisplayManager; private Handler mHandler; @@ -91,4 +105,24 @@ public class ClientTransactionListenerControllerTest { verify(mListener).onDisplayChanged(123); } + + @Test + public void testActivityWindowInfoChangedListener() { + mSetFlagsRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG); + + mController.registerActivityWindowInfoChangedListener(mActivityWindowInfoListener); + final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo(); + activityWindowInfo.set(true /* isEmbedded */, new Rect(0, 0, 1000, 2000), + new Rect(0, 0, 1000, 1000)); + mController.onActivityWindowInfoChanged(mActivityToken, activityWindowInfo); + + verify(mActivityWindowInfoListener).accept(mActivityToken, activityWindowInfo); + + clearInvocations(mActivityWindowInfoListener); + mController.unregisterActivityWindowInfoChangedListener(mActivityWindowInfoListener); + + mController.onActivityWindowInfoChanged(mActivityToken, activityWindowInfo); + + verify(mActivityWindowInfoListener, never()).accept(any(), any()); + } } diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java index 31ea6759c710..584fe16d00ac 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java @@ -98,7 +98,7 @@ public class ObjectPoolTests { @Test public void testRecycleDestroyActivityItem() { - testRecycle(() -> DestroyActivityItem.obtain(mActivityToken, true, 117)); + testRecycle(() -> DestroyActivityItem.obtain(mActivityToken, true)); } @Test @@ -169,7 +169,7 @@ public class ObjectPoolTests { @Test public void testRecyclePauseActivityItemItem() { - testRecycle(() -> PauseActivityItem.obtain(mActivityToken, true, true, 5, true, true)); + testRecycle(() -> PauseActivityItem.obtain(mActivityToken, true, true, true, true)); } @Test @@ -185,7 +185,7 @@ public class ObjectPoolTests { @Test public void testRecycleStopItem() { - testRecycle(() -> StopActivityItem.obtain(mActivityToken, 4)); + testRecycle(() -> StopActivityItem.obtain(mActivityToken)); } @Test diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java index adb6f2a23847..935bc7565986 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java @@ -306,7 +306,7 @@ public class TransactionExecutorTests { final IBinder token = mock(IBinder.class); final ClientTransaction destroyTransaction = ClientTransaction.obtain(null /* client */); destroyTransaction.addTransactionItem( - DestroyActivityItem.obtain(token, false /* finished */, 0 /* configChanges */)); + DestroyActivityItem.obtain(token, false /* finished */)); destroyTransaction.preExecute(mTransactionHandler); // The activity should be added to to-be-destroyed container. assertEquals(1, activitiesToBeDestroyed.size()); diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java index 75347bf2c8de..d451fe58e8e4 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java @@ -155,8 +155,7 @@ public class TransactionParcelTests { @Test public void testDestroy() { - DestroyActivityItem item = DestroyActivityItem.obtain(mActivityToken, true /* finished */, - 135 /* configChanges */); + DestroyActivityItem item = DestroyActivityItem.obtain(mActivityToken, true /* finished */); writeAndPrepareForReading(item); // Read from parcel and assert @@ -244,8 +243,7 @@ public class TransactionParcelTests { public void testPause() { // Write to parcel PauseActivityItem item = PauseActivityItem.obtain(mActivityToken, true /* finished */, - true /* userLeaving */, 135 /* configChanges */, true /* dontReport */, - true /* autoEnteringPip */); + true /* userLeaving */, true /* dontReport */, true /* autoEnteringPip */); writeAndPrepareForReading(item); // Read from parcel and assert @@ -272,7 +270,7 @@ public class TransactionParcelTests { @Test public void testStop() { // Write to parcel - StopActivityItem item = StopActivityItem.obtain(mActivityToken, 14 /* configChanges */); + StopActivityItem item = StopActivityItem.obtain(mActivityToken); writeAndPrepareForReading(item); // Read from parcel and assert @@ -305,8 +303,7 @@ public class TransactionParcelTests { ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain( mActivityToken, config(), new ActivityWindowInfo()); - StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken, - 78 /* configChanges */); + StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken); ClientTransaction transaction = ClientTransaction.obtain(null /* client */); transaction.addTransactionItem(callback1); @@ -351,8 +348,7 @@ public class TransactionParcelTests { mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG); // Write to parcel - StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken, - 78 /* configChanges */); + StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken); ClientTransaction transaction = ClientTransaction.obtain(null /* client */); transaction.addTransactionItem(lifecycleRequest); diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java index 4a9cb7180a3f..0c1e8793bfc9 100644 --- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java +++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java @@ -18,34 +18,52 @@ package android.content.res; import android.annotation.NonNull; import android.app.ResourcesManager; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.os.Binder; import android.os.LocaleList; import android.platform.test.annotations.Postsubmit; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.Display; import android.view.DisplayAdjustments; +import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import junit.framework.TestCase; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; @Postsubmit +@RunWith(AndroidJUnit4.class) public class ResourcesManagerTest extends TestCase { private static final int SECONDARY_DISPLAY_ID = 1; private static final String APP_ONE_RES_DIR = "app_one.apk"; private static final String APP_ONE_RES_SPLIT_DIR = "app_one_split.apk"; private static final String APP_TWO_RES_DIR = "app_two.apk"; private static final String LIB_RES_DIR = "lib.apk"; + private static final String TEST_LIB = "com.android.frameworks.coretests.bdr_helper_app1"; private ResourcesManager mResourcesManager; private Map<Integer, DisplayMetrics> mDisplayMetricsMap; + private PackageManager mPackageManager; - @Override - protected void setUp() throws Exception { + @Before + public void setUp() throws Exception { super.setUp(); mDisplayMetricsMap = new HashMap<>(); @@ -93,8 +111,14 @@ public class ResourcesManagerTest extends TestCase { return mDisplayMetricsMap.get(displayId); } }; + + mPackageManager = InstrumentationRegistry.getContext().getPackageManager(); } + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + @Test @SmallTest public void testMultipleCallsWithIdenticalParametersCacheReference() { Resources resources = mResourcesManager.getResources( @@ -109,6 +133,7 @@ public class ResourcesManagerTest extends TestCase { assertSame(resources.getImpl(), newResources.getImpl()); } + @Test @SmallTest public void testMultipleCallsWithDifferentParametersReturnDifferentReferences() { Resources resources = mResourcesManager.getResources( @@ -125,6 +150,7 @@ public class ResourcesManagerTest extends TestCase { assertNotSame(resources, newResources); } + @Test @SmallTest public void testAddingASplitCreatesANewImpl() { Resources resources1 = mResourcesManager.getResources( @@ -142,6 +168,7 @@ public class ResourcesManagerTest extends TestCase { assertNotSame(resources1.getImpl(), resources2.getImpl()); } + @Test @SmallTest public void testUpdateConfigurationUpdatesAllAssetManagers() { Resources resources1 = mResourcesManager.getResources( @@ -187,6 +214,7 @@ public class ResourcesManagerTest extends TestCase { assertEquals(expectedConfig, resources3.getConfiguration()); } + @Test @SmallTest public void testTwoActivitiesWithIdenticalParametersShareImpl() { Binder activity1 = new Binder(); @@ -208,6 +236,7 @@ public class ResourcesManagerTest extends TestCase { assertSame(resources1.getImpl(), resources2.getImpl()); } + @Test @SmallTest public void testThemesGetUpdatedWithNewImpl() { Binder activity1 = new Binder(); @@ -237,6 +266,7 @@ public class ResourcesManagerTest extends TestCase { assertTrue(value.data != 0); } + @Test @SmallTest public void testMultipleResourcesForOneActivityGetUpdatedWhenActivityBaseUpdates() { Binder activity1 = new Binder(); @@ -286,6 +316,7 @@ public class ResourcesManagerTest extends TestCase { assertEquals(expectedConfig2, resources2.getConfiguration()); } + @Test @SmallTest public void testChangingActivityDisplayDoesntOverrideDisplayRequestedByResources() { Binder activity = new Binder(); @@ -322,4 +353,101 @@ public class ResourcesManagerTest extends TestCase { assertEquals(mDisplayMetricsMap.get(Display.DEFAULT_DISPLAY).widthPixels, defaultDisplayResources.getDisplayMetrics().widthPixels); } + + @Test + @SmallTest + @RequiresFlagsEnabled(Flags.FLAG_REGISTER_RESOURCE_PATHS) + public void testExistingResourcesAfterResourcePathsRegistration() + throws PackageManager.NameNotFoundException { + // Inject ResourcesManager instance from this test to the ResourcesManager class so that all + // the static method can interact with this test smoothly. + ResourcesManager oriResourcesManager = ResourcesManager.getInstance(); + ResourcesManager.setInstance(mResourcesManager); + + // Create a Resources before register resources' paths for a package. + Resources resources = mResourcesManager.getResources( + null, APP_ONE_RES_DIR, null, null, null, null, null, null, + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); + assertNotNull(resources); + ResourcesImpl oriResImpl = resources.getImpl(); + + ApplicationInfo appInfo = mPackageManager.getApplicationInfo(TEST_LIB, 0); + Resources.registerResourcePaths(TEST_LIB, appInfo); + + assertNotSame(oriResImpl, resources.getImpl()); + + String[] resourcePaths = appInfo.getAllApkPaths(); + resourcePaths = removeDuplicates(resourcePaths); + ApkAssets[] loadedAssets = resources.getAssets().getApkAssets(); + assertTrue(allResourcePathsLoaded(resourcePaths, loadedAssets)); + + // Package resources' paths should be cached in ResourcesManager. + assertEquals(Arrays.toString(resourcePaths), Arrays.toString(ResourcesManager.getInstance() + .getSharedLibAssetsMap().get(TEST_LIB).getAllAssetPaths())); + + // Revert the ResourcesManager instance back. + ResourcesManager.setInstance(oriResourcesManager); + } + + @Test + @SmallTest + @RequiresFlagsEnabled(Flags.FLAG_REGISTER_RESOURCE_PATHS) + public void testNewResourcesAfterResourcePathsRegistration() + throws PackageManager.NameNotFoundException { + // Inject ResourcesManager instance from this test to the ResourcesManager class so that all + // the static method can interact with this test smoothly. + ResourcesManager oriResourcesManager = ResourcesManager.getInstance(); + ResourcesManager.setInstance(mResourcesManager); + + ApplicationInfo appInfo = mPackageManager.getApplicationInfo(TEST_LIB, 0); + Resources.registerResourcePaths(TEST_LIB, appInfo); + + // Create a Resources after register resources' paths for a package. + Resources resources = mResourcesManager.getResources( + null, APP_ONE_RES_DIR, null, null, null, null, null, null, + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); + assertNotNull(resources); + + String[] resourcePaths = appInfo.getAllApkPaths(); + resourcePaths = removeDuplicates(resourcePaths); + ApkAssets[] loadedAssets = resources.getAssets().getApkAssets(); + assertTrue(allResourcePathsLoaded(resourcePaths, loadedAssets)); + + // Package resources' paths should be cached in ResourcesManager. + assertEquals(Arrays.toString(resourcePaths), Arrays.toString(ResourcesManager.getInstance() + .getSharedLibAssetsMap().get(TEST_LIB).getAllAssetPaths())); + + // Revert the ResourcesManager instance back. + ResourcesManager.setInstance(oriResourcesManager); + } + + private static boolean allResourcePathsLoaded(String[] resourcePaths, ApkAssets[] loadedAsset) { + for (int i = 0; i < resourcePaths.length; i++) { + if (!resourcePaths[i].endsWith(".apk")) { + continue; + } + boolean found = false; + for (int j = 0; j < loadedAsset.length; j++) { + if (loadedAsset[j].getAssetPath().equals(resourcePaths[i])) { + found = true; + } + } + if (!found) { + return false; + } + } + return true; + } + + private static String[] removeDuplicates(String[] paths) { + var pathList = new ArrayList<String>(); + var pathSet = new ArraySet<String>(); + final int pathsLen = paths.length; + for (int i = 0; i < pathsLen; i++) { + if (pathSet.add(paths[i])) { + pathList.add(paths[i]); + } + } + return pathList.toArray(new String[0]); + } } diff --git a/core/tests/coretests/src/android/os/PowerManagerTest.java b/core/tests/coretests/src/android/os/PowerManagerTest.java index cb281ff8a6a7..b9a12ad57c33 100644 --- a/core/tests/coretests/src/android/os/PowerManagerTest.java +++ b/core/tests/coretests/src/android/os/PowerManagerTest.java @@ -32,6 +32,7 @@ import android.platform.test.annotations.IgnoreUnderRavenwood; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.flag.junit.RavenwoodFlagsValueProvider; import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.InstrumentationRegistry; @@ -86,7 +87,8 @@ public class PowerManagerTest { // Required for RequiresFlagsEnabled and RequiresFlagsDisabled annotations to take effect. @Rule - public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isUnderRavenwood() ? null + public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isOnRavenwood() + ? RavenwoodFlagsValueProvider.createAllOnCheckFlagsRule() : DeviceFlagsValueProvider.createCheckFlagsRule(); /** diff --git a/core/tests/coretests/src/android/os/WorkDurationUnitTest.java b/core/tests/coretests/src/android/os/WorkDurationUnitTest.java index c70da6e94385..fcdc5905ef88 100644 --- a/core/tests/coretests/src/android/os/WorkDurationUnitTest.java +++ b/core/tests/coretests/src/android/os/WorkDurationUnitTest.java @@ -22,6 +22,7 @@ import android.platform.test.annotations.IgnoreUnderRavenwood; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.flag.junit.RavenwoodFlagsValueProvider; import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.runner.AndroidJUnit4; @@ -40,7 +41,8 @@ public class WorkDurationUnitTest { // Required for RequiresFlagsEnabled and RequiresFlagsDisabled annotations to take effect. @Rule - public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isUnderRavenwood() ? null + public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isOnRavenwood() + ? RavenwoodFlagsValueProvider.createAllOnCheckFlagsRule() : DeviceFlagsValueProvider.createCheckFlagsRule(); @Before diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java index 8c93fbbc6b47..48ba52621f64 100644 --- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java +++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java @@ -24,8 +24,10 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; +import static org.mockito.AdditionalMatchers.and; +import static org.mockito.AdditionalMatchers.not; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -36,6 +38,7 @@ import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.view.WindowManager.BadTokenException; import android.view.WindowManager.LayoutParams; +import android.view.inputmethod.ImeTracker; import android.widget.TextView; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -96,12 +99,12 @@ public class ImeInsetsSourceConsumerTest { InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { // test if setVisibility can show IME mImeConsumer.onWindowFocusGained(true); - mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */); + mController.show(WindowInsets.Type.ime(), true /* fromIme */, ImeTracker.Token.empty()); mController.cancelExistingAnimations(); assertTrue((mController.getRequestedVisibleTypes() & WindowInsets.Type.ime()) != 0); // test if setVisibility can hide IME - mController.hide(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */); + mController.hide(WindowInsets.Type.ime(), true /* fromIme */, ImeTracker.Token.empty()); mController.cancelExistingAnimations(); assertFalse((mController.getRequestedVisibleTypes() & WindowInsets.Type.ime()) != 0); }); @@ -114,8 +117,9 @@ public class ImeInsetsSourceConsumerTest { InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { // Request IME visible before control is available. + final var statsToken = ImeTracker.Token.empty(); mImeConsumer.onWindowFocusGained(true); - mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */); + mController.show(WindowInsets.Type.ime(), true /* fromIme */, statsToken); // set control and verify visibility is applied. InsetsSourceControl control = new InsetsSourceControl(ID_IME, @@ -124,10 +128,10 @@ public class ImeInsetsSourceConsumerTest { // IME show animation should be triggered when control becomes available. verify(mController).applyAnimation( eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */, - any() /* statsToken */); + eq(statsToken)); verify(mController, never()).applyAnimation( eq(WindowInsets.Type.ime()), eq(false) /* show */, eq(true) /* fromIme */, - any() /* statsToken */); + eq(statsToken)); }); } @@ -152,9 +156,9 @@ public class ImeInsetsSourceConsumerTest { // Request IME visible before control is available. mImeConsumer.onWindowFocusGained(hasWindowFocus); final boolean imeVisible = hasWindowFocus && hasViewFocus; + final var statsToken = ImeTracker.Token.empty(); if (imeVisible) { - mController.show(WindowInsets.Type.ime(), true /* fromIme */, - null /* statsToken */); + mController.show(WindowInsets.Type.ime(), true /* fromIme */, statsToken); } // set control and verify visibility is applied. @@ -168,23 +172,25 @@ public class ImeInsetsSourceConsumerTest { // and expect skip animation state after getAndClearSkipAnimationOnce invoked. mController.onControlsChanged(new InsetsSourceControl[]{ control }); verify(control).getAndClearSkipAnimationOnce(); + // This ends up creating a new request when we gain control, + // so the statsToken won't match. verify(mController).applyAnimation(eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(false) /* fromIme */, - eq(expectSkipAnim) /* skipAnim */, eq(null) /* statsToken */); + eq(expectSkipAnim) /* skipAnim */, and(not(eq(statsToken)), notNull())); } // If previously hasViewFocus is false, verify when requesting the IME visible next // time will not skip animation. if (!hasViewFocus) { - mController.show(WindowInsets.Type.ime(), true /* fromIme */, - null /* statsToken */); + final var statsTokenNext = ImeTracker.Token.empty(); + mController.show(WindowInsets.Type.ime(), true /* fromIme */, statsTokenNext); mController.onControlsChanged(new InsetsSourceControl[]{ control }); // Verify IME show animation should be triggered when control becomes available and // the animation will be skipped by getAndClearSkipAnimationOnce invoked. verify(control).getAndClearSkipAnimationOnce(); verify(mController).applyAnimation(eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */, - eq(false) /* skipAnim */, eq(null) /* statsToken */); + eq(false) /* skipAnim */, eq(statsTokenNext)); } }); } diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index 1568174e1955..316e191eecbd 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -256,7 +256,7 @@ public class InsetsControllerTest { mController.setSystemDrivenInsetsAnimationLoggingListener(loggingListener); mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true); // since there is no focused view, forcefully make IME visible. - mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */); + mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty()); // When using the animation thread, this must not invoke onReady() mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw(); }); @@ -273,14 +273,14 @@ public class InsetsControllerTest { InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true); // since there is no focused view, forcefully make IME visible. - mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */); + mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty()); mController.show(all()); // quickly jump to final state by cancelling it. mController.cancelExistingAnimations(); - final @InsetsType int types = navigationBars() | statusBars() | ime(); + @InsetsType final int types = navigationBars() | statusBars() | ime(); assertEquals(types, mController.getRequestedVisibleTypes() & types); - mController.hide(ime(), true /* fromIme */, null /* statsToken */); + mController.hide(ime(), true /* fromIme */, ImeTracker.Token.empty()); mController.hide(all()); mController.cancelExistingAnimations(); assertEquals(0, mController.getRequestedVisibleTypes() & types); @@ -295,10 +295,10 @@ public class InsetsControllerTest { mController.onControlsChanged(new InsetsSourceControl[] { ime }); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true); - mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */); + mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty()); mController.cancelExistingAnimations(); assertTrue(isRequestedVisible(mController, ime())); - mController.hide(ime(), true /* fromIme */, null /* statsToken */); + mController.hide(ime(), true /* fromIme */, ImeTracker.Token.empty()); mController.cancelExistingAnimations(); assertFalse(isRequestedVisible(mController, ime())); mController.getSourceConsumer(ID_IME, ime()).onWindowFocusLost(); @@ -465,7 +465,7 @@ public class InsetsControllerTest { assertFalse(mController.getState().peekSource(ID_IME).isVisible()); // Pretend IME is calling - mController.show(ime(), true /* fromIme */, null /* statsToken */); + mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty()); // Gaining control shortly after mController.onControlsChanged(createSingletonControl(ID_IME, ime())); @@ -489,7 +489,7 @@ public class InsetsControllerTest { mController.onControlsChanged(createSingletonControl(ID_IME, ime())); // Pretend IME is calling - mController.show(ime(), true /* fromIme */, null /* statsToken */); + mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty()); assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ime())); mController.cancelExistingAnimations(); @@ -567,7 +567,7 @@ public class InsetsControllerTest { verify(listener, never()).onReady(any(), anyInt()); // Pretend that IME is calling. - mController.show(ime(), true /* fromIme */, null /* statsToken */); + mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty()); // Ready gets deferred until next predraw mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw(); @@ -651,7 +651,7 @@ public class InsetsControllerTest { mController.onControlsChanged(createSingletonControl(ID_IME, ime())); // Pretend IME is calling - mController.show(ime(), true /* fromIme */, null /* statsToken */); + mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty()); InsetsState copy = new InsetsState(mController.getState(), true /* copySources */); copy.peekSource(ID_IME).setFrame(0, 1, 2, 3); @@ -851,7 +851,7 @@ public class InsetsControllerTest { // Showing invisible ime should only causes insets change once. clearInvocations(mTestHost); - mController.show(ime(), true /* fromIme */, null /* statsToken */); + mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty()); verify(mTestHost, times(1)).notifyInsetsChanged(); // Sending the same insets state should not cause insets change. @@ -918,7 +918,7 @@ public class InsetsControllerTest { assertNull(imeInsetsConsumer.getControl()); // Verify IME requested visibility should be updated to IME consumer from controller. - mController.show(ime(), true /* fromIme */, null /* statsToken */); + mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty()); assertTrue(isRequestedVisible(mController, ime())); mController.hide(ime()); diff --git a/core/tests/coretests/src/android/view/ViewVelocityTest.java b/core/tests/coretests/src/android/view/ViewVelocityTest.java new file mode 100644 index 000000000000..128c54b77e57 --- /dev/null +++ b/core/tests/coretests/src/android/view/ViewVelocityTest.java @@ -0,0 +1,102 @@ +/* + * 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.view; + +import static junit.framework.Assert.assertEquals; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.app.Activity; +import android.os.SystemClock; + +import androidx.test.annotation.UiThreadTest; +import androidx.test.filters.SmallTest; +import androidx.test.rule.ActivityTestRule; +import androidx.test.runner.AndroidJUnit4; + +import com.android.frameworks.coretests.R; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ViewVelocityTest { + + @Rule + public ActivityTestRule<ViewCaptureTestActivity> mActivityRule = new ActivityTestRule<>( + ViewCaptureTestActivity.class); + + private Activity mActivity; + private View mMovingView; + private ViewRootImpl mViewRoot; + + @Before + public void setUp() throws Throwable { + mActivity = mActivityRule.getActivity(); + mActivityRule.runOnUiThread(() -> { + mActivity.setContentView(R.layout.view_velocity_test); + mMovingView = mActivity.findViewById(R.id.moving_view); + }); + ViewParent parent = mActivity.getWindow().getDecorView().getParent(); + while (parent instanceof View) { + parent = parent.getParent(); + } + mViewRoot = (ViewRootImpl) parent; + } + + @UiThreadTest + @Test + public void frameRateChangesWhenContentMoves() { + mMovingView.offsetLeftAndRight(100); + float frameRate = mViewRoot.getPreferredFrameRate(); + assertTrue(frameRate > 0); + } + + @UiThreadTest + @Test + public void firstFrameNoMovement() { + assertEquals(0f, mViewRoot.getPreferredFrameRate(), 0f); + } + + @Test + public void touchBoostDisable() throws Throwable { + mActivityRule.runOnUiThread(() -> { + long now = SystemClock.uptimeMillis(); + MotionEvent down = MotionEvent.obtain( + /* downTime */ now, + /* eventTime */ now, + /* action */ MotionEvent.ACTION_DOWN, + /* x */ 0f, + /* y */ 0f, + /* metaState */ 0 + ); + mActivity.dispatchTouchEvent(down); + mMovingView.offsetLeftAndRight(10); + }); + mActivityRule.runOnUiThread(() -> { + mMovingView.invalidate(); + }); + + mActivityRule.runOnUiThread(() -> { + assertFalse(mViewRoot.getIsTouchBoosting()); + }); + } +} diff --git a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java index df212ebe1744..cd38bd68a26b 100644 --- a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java +++ b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java @@ -16,11 +16,17 @@ package android.widget; +import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import android.content.Context; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.util.AttributeSet; import android.util.PollingCheck; import androidx.test.filters.MediumTest; @@ -43,14 +49,21 @@ import java.util.concurrent.TimeUnit; public class HorizontalScrollViewFunctionalTest { private HorizontalScrollViewActivity mActivity; private HorizontalScrollView mHorizontalScrollView; + private MyHorizontalScrollView mMyHorizontalScrollView; @Rule public ActivityTestRule<HorizontalScrollViewActivity> mActivityRule = new ActivityTestRule<>( HorizontalScrollViewActivity.class); + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + @Before public void setUp() throws Exception { mActivity = mActivityRule.getActivity(); mHorizontalScrollView = mActivity.findViewById(R.id.horizontal_scroll_view); + mMyHorizontalScrollView = + (MyHorizontalScrollView) mActivity.findViewById(R.id.my_horizontal_scroll_view); } @Test @@ -79,6 +92,22 @@ public class HorizontalScrollViewFunctionalTest { assertEquals(maxScroll, mHorizontalScrollView.getScrollX()); } + @Test + @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API) + public void testSetVelocity() throws Throwable { + mActivityRule.runOnUiThread(() -> { + mMyHorizontalScrollView.setFrameContentVelocity(0); + }); + // set setFrameContentVelocity shouldn't do anything. + assertEquals(mMyHorizontalScrollView.isSetVelocityCalled, false); + + mActivityRule.runOnUiThread(() -> { + mMyHorizontalScrollView.fling(100); + }); + // set setFrameContentVelocity should be called when fling. + assertEquals(mMyHorizontalScrollView.isSetVelocityCalled, true); + } + static class WatchedEdgeEffect extends EdgeEffect { public CountDownLatch onAbsorbLatch = new CountDownLatch(1); @@ -92,5 +121,29 @@ public class HorizontalScrollViewFunctionalTest { onAbsorbLatch.countDown(); } } + + public static class MyHorizontalScrollView extends ScrollView { + + public boolean isSetVelocityCalled; + + public MyHorizontalScrollView(Context context) { + super(context); + } + + public MyHorizontalScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public MyHorizontalScrollView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + public void setFrameContentVelocity(float pixelsPerSecond) { + if (pixelsPerSecond != 0) { + isSetVelocityCalled = true; + } + } + } } diff --git a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java index 4f722cefcf9f..6ab77dc9d535 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java @@ -355,7 +355,7 @@ public class RemoteViewsAdapterTest { } @Override - public RemoteViews.RemoteCollectionItems getRemoteCollectionItems() { + public RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize) { RemoteViews.RemoteCollectionItems.Builder itemsBuilder = new RemoteViews.RemoteCollectionItems.Builder(); itemsBuilder.setHasStableIds(hasStableIds()) diff --git a/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java index 109c8080de94..a60b2a13e2eb 100644 --- a/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java +++ b/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java @@ -16,11 +16,17 @@ package android.widget; +import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import android.content.Context; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.util.AttributeSet; import android.util.PollingCheck; import androidx.test.filters.MediumTest; @@ -43,14 +49,20 @@ import java.util.concurrent.TimeUnit; public class ScrollViewFunctionalTest { private ScrollViewActivity mActivity; private ScrollView mScrollView; + private MyScrollView mMyScrollView; @Rule public ActivityTestRule<ScrollViewActivity> mActivityRule = new ActivityTestRule<>( ScrollViewActivity.class); + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + @Before public void setUp() throws Exception { mActivity = mActivityRule.getActivity(); mScrollView = mActivity.findViewById(R.id.scroll_view); + mMyScrollView = (MyScrollView) mActivity.findViewById(R.id.my_scroll_view); } @Test @@ -79,6 +91,22 @@ public class ScrollViewFunctionalTest { assertEquals(maxScroll, mScrollView.getScrollY()); } + @Test + @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API) + public void testSetVelocity() throws Throwable { + mActivityRule.runOnUiThread(() -> { + mMyScrollView.setFrameContentVelocity(0); + }); + // set setFrameContentVelocity shouldn't do anything. + assertEquals(mMyScrollView.isSetVelocityCalled, false); + + mActivityRule.runOnUiThread(() -> { + mMyScrollView.fling(100); + }); + // set setFrameContentVelocity should be called when fling. + assertEquals(mMyScrollView.isSetVelocityCalled, true); + } + static class WatchedEdgeEffect extends EdgeEffect { public CountDownLatch onAbsorbLatch = new CountDownLatch(1); @@ -92,5 +120,29 @@ public class ScrollViewFunctionalTest { onAbsorbLatch.countDown(); } } + + public static class MyScrollView extends ScrollView { + + public boolean isSetVelocityCalled; + + public MyScrollView(Context context) { + super(context); + } + + public MyScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public MyScrollView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + public void setFrameContentVelocity(float pixelsPerSecond) { + if (pixelsPerSecond != 0) { + isSetVelocityCalled = true; + } + } + } } diff --git a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java index 58cfc6625051..43e62275152e 100644 --- a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java @@ -53,6 +53,7 @@ import android.os.Bundle; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import androidx.test.InstrumentationRegistry; @@ -93,6 +94,9 @@ public class IntentForwarderActivityTest { private static final String TYPE_PLAIN_TEXT = "text/plain"; private static UserInfo MANAGED_PROFILE_INFO = new UserInfo(); + private static UserInfo PRIVATE_PROFILE_INFO = new UserInfo(12, "Private", null, + UserInfo.FLAG_PROFILE, UserManager.USER_TYPE_PROFILE_PRIVATE); + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); static { MANAGED_PROFILE_INFO.id = 10; @@ -131,6 +135,7 @@ public class IntentForwarderActivityTest { @Before public void setup() { + MockitoAnnotations.initMocks(this); mContext = InstrumentationRegistry.getTargetContext(); sInjector = spy(new TestInjector()); @@ -632,6 +637,54 @@ public class IntentForwarderActivityTest { logMakerCaptor.getValue().getSubtype()); } + @Test + public void shouldForwardToParent_telephony_privateProfile() throws Exception { + mSetFlagsRule.enableFlags( + android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_INTENT_REDIRECTION); + + sComponentName = FORWARD_TO_PARENT_COMPONENT_NAME; + when(mIPm.canForwardTo( + any(Intent.class), nullable(String.class), anyInt(), anyInt())).thenReturn(true); + + List<UserInfo> profiles = new ArrayList<>(); + profiles.add(CURRENT_USER_INFO); + profiles.add(PRIVATE_PROFILE_INFO); + when(mUserManager.getProfiles(anyInt())).thenReturn(profiles); + when(mUserManager.getProfileParent(anyInt())).thenReturn(CURRENT_USER_INFO); + Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class); + intent.setAction(Intent.ACTION_DIAL); + IntentForwarderWrapperActivity activity = mActivityRule.launchActivity(intent); + verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); + assertEquals(activity.getStartActivityIntent().getAction(), intent.getAction()); + assertEquals(activity.getUserIdActivityLaunchedIn(), CURRENT_USER_INFO.id); + } + + @Test + public void shouldForwardToParent_mms_privateProfile() throws Exception { + mSetFlagsRule.enableFlags( + android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_INTENT_REDIRECTION); + + sComponentName = FORWARD_TO_PARENT_COMPONENT_NAME; + when(mIPm.canForwardTo( + any(Intent.class), nullable(String.class), anyInt(), anyInt())).thenReturn(true); + + List<UserInfo> profiles = new ArrayList<>(); + profiles.add(CURRENT_USER_INFO); + profiles.add(PRIVATE_PROFILE_INFO); + when(mUserManager.getProfiles(anyInt())).thenReturn(profiles); + when(mUserManager.getProfileParent(anyInt())).thenReturn(CURRENT_USER_INFO); + Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class); + intent.setAction(Intent.ACTION_SEND); + intent.setType(TYPE_PLAIN_TEXT); + IntentForwarderWrapperActivity activity = mActivityRule.launchActivity(intent); + verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); + assertEquals(activity.getStartActivityIntent().getAction(), intent.getAction()); + assertEquals(activity.getStartActivityIntent().getType(), intent.getType()); + assertEquals(activity.getUserIdActivityLaunchedIn(), CURRENT_USER_INFO.id); + } + private void setupShouldSkipDisclosureTest() throws RemoteException { sComponentName = FORWARD_TO_PARENT_COMPONENT_NAME; sActivityName = "MyTestActivity"; @@ -688,6 +741,14 @@ public class IntentForwarderActivityTest { protected MetricsLogger getMetricsLogger() { return mMetricsLogger; } + + Intent getStartActivityIntent() { + return mStartActivityIntent; + } + + int getUserIdActivityLaunchedIn() { + return mUserIdActivityLaunchedIn; + } } public class TestInjector implements IntentForwarderActivity.Injector { diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java index 66be05ff233c..ed641e048a81 100644 --- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java +++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java @@ -309,17 +309,17 @@ public class ActivityThreadClientTest { private void pauseActivity(ActivityClientRecord r) { mThread.handlePauseActivity(r, false /* finished */, - false /* userLeaving */, 0 /* configChanges */, false /* autoEnteringPip */, + false /* userLeaving */, false /* autoEnteringPip */, null /* pendingActions */, "test"); } private void stopActivity(ActivityClientRecord r) { - mThread.handleStopActivity(r, 0 /* configChanges */, + mThread.handleStopActivity(r, new PendingTransactionActions(), false /* finalStateRequest */, "test"); } private void destroyActivity(ActivityClientRecord r) { - mThread.handleDestroyActivity(r, true /* finishing */, 0 /* configChanges */, + mThread.handleDestroyActivity(r, true /* finishing */, false /* getNonConfigInstance */, "test"); } diff --git a/data/etc/Android.bp b/data/etc/Android.bp index 238a3e10f058..1410950966e9 100644 --- a/data/etc/Android.bp +++ b/data/etc/Android.bp @@ -72,6 +72,12 @@ prebuilt_etc { src: "enhanced-confirmation.xml", } +prebuilt_etc { + name: "package-shareduid-allowlist.xml", + sub_dir: "sysconfig", + src: "package-shareduid-allowlist.xml", +} + // Privapp permission whitelist files prebuilt_etc { diff --git a/data/etc/CleanSpec.mk b/data/etc/CleanSpec.mk index 783a7edadeb7..fd38d2782cb2 100644 --- a/data/etc/CleanSpec.mk +++ b/data/etc/CleanSpec.mk @@ -43,6 +43,8 @@ #$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates) #$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f) #$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/etc/sysconfig/package-shareduid-allowlist.xml) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/etc/sysconfig/package-shareduid-allowlist.xml) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/etc/permissions/com.android.carrierconfig.xml) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/etc/permissions/com.android.carrierconfig.xml) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/etc/permissions/com.android.emergency.xml) diff --git a/data/etc/package-shareduid-allowlist.xml b/data/etc/package-shareduid-allowlist.xml new file mode 100644 index 000000000000..2401d4a26e68 --- /dev/null +++ b/data/etc/package-shareduid-allowlist.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<!-- +This XML defines an allowlist for packages that want to join a particular shared-uid. +If a non-system package that is signed with platform signature, is trying to join a particular +shared-uid, and not in this list, the installation will fail. + +- The "package" XML attribute refers to the app's package name. +- The "shareduid" XML attribute refers to the shared uid name. + +Example usage + 1. <allow-package-shareduid package="com.example.app" shareduid="android.uid.system"/> + Indicates that a package - com.example.app, will be able to join android.uid.system. + 2. <allow-package-shareduid package="oem.example.app" shareduid="oem.uid.custom"/> + Indicates that a package - oem.example.app, will be able to join oem.uid.custom. +--> + +<config> + <allow-package-shareduid package="android.test.settings" shareduid="android.uid.system" /> +</config> diff --git a/data/fonts/font_fallback.xml b/data/fonts/font_fallback.xml index 15ea15a9b4c1..53024ab858c4 100644 --- a/data/fonts/font_fallback.xml +++ b/data/fonts/font_fallback.xml @@ -792,14 +792,10 @@ </font> </family> <family lang="ja"> - <font weight="400" style="normal"> - NotoSerifHentaigana-EL.ttf + <font postScriptName="NotoSerifHentaigana-ExtraLight" supportedAxes="wght"> + NotoSerifHentaigana.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="700" style="normal"> - NotoSerifHentaigana-EL.ttf - <axis tag="wght" stylevalue="700"/> - </font> </family> <family lang="ko"> <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Regular"> diff --git a/data/fonts/font_fallback_cjkvf.xml b/data/fonts/font_fallback_cjkvf.xml index c1ca67ef456a..ac1b06495832 100644 --- a/data/fonts/font_fallback_cjkvf.xml +++ b/data/fonts/font_fallback_cjkvf.xml @@ -804,14 +804,10 @@ </font> </family> <family lang="ja"> - <font weight="400" style="normal"> - NotoSerifHentaigana-EL.ttf + <font postScriptName="NotoSerifHentaigana-ExtraLight" supportedAxes="wght"> + NotoSerifHentaigana.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="700" style="normal"> - NotoSerifHentaigana-EL.ttf - <axis tag="wght" stylevalue="700"/> - </font> </family> <family lang="ko"> <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin" diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml index b23f00554bdb..d1aa8e9734c2 100644 --- a/data/fonts/fonts.xml +++ b/data/fonts/fonts.xml @@ -1433,12 +1433,12 @@ </font> </family> <family lang="ja"> - <font weight="400" style="normal"> - NotoSerifHentaigana-EL.ttf + <font weight="400" style="normal" postScriptName="NotoSerifHentaigana-ExtraLight"> + NotoSerifHentaigana.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="700" style="normal"> - NotoSerifHentaigana-EL.ttf + <font weight="700" style="normal" postScriptName="NotoSerifHentaigana-ExtraLight"> + NotoSerifHentaigana.ttf <axis tag="wght" stylevalue="700"/> </font> </family> diff --git a/data/fonts/fonts_cjkvf.xml b/data/fonts/fonts_cjkvf.xml index 1ab71ae270eb..9545ae718574 100644 --- a/data/fonts/fonts_cjkvf.xml +++ b/data/fonts/fonts_cjkvf.xml @@ -1532,12 +1532,12 @@ </font> </family> <family lang="ja"> - <font weight="400" style="normal"> - NotoSerifHentaigana-EL.ttf + <font weight="400" style="normal" postScriptName="NotoSerifHentaigana-ExtraLight"> + NotoSerifHentaigana.ttf <axis tag="wght" stylevalue="400"/> </font> - <font weight="700" style="normal"> - NotoSerifHentaigana-EL.ttf + <font weight="700" style="normal" postScriptName="NotoSerifHentaigana-ExtraLight"> + NotoSerifHentaigana.ttf <axis tag="wght" stylevalue="700"/> </font> </family> diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java index 6da07198c3ad..884268a4b85c 100644 --- a/graphics/java/android/graphics/text/MeasuredText.java +++ b/graphics/java/android/graphics/text/MeasuredText.java @@ -29,11 +29,13 @@ import android.util.Log; import com.android.internal.util.Preconditions; import dalvik.annotation.optimization.CriticalNative; +import dalvik.annotation.optimization.NeverInline; import libcore.util.NativeAllocationRegistry; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Locale; import java.util.Objects; /** @@ -85,6 +87,30 @@ public class MeasuredText { return mChars; } + private void rangeCheck(int start, int end) { + if (start < 0 || start > end || end > mChars.length) { + throwRangeError(start, end); + } + } + + @NeverInline + private void throwRangeError(int start, int end) { + throw new IllegalArgumentException(String.format(Locale.US, + "start(%d) end(%d) length(%d) out of bounds", start, end, mChars.length)); + } + + private void offsetCheck(int offset) { + if (offset < 0 || offset >= mChars.length) { + throwOffsetError(offset); + } + } + + @NeverInline + private void throwOffsetError(int offset) { + throw new IllegalArgumentException(String.format(Locale.US, + "offset (%d) length(%d) out of bounds", offset, mChars.length)); + } + /** * Returns the width of a given range. * @@ -93,12 +119,7 @@ public class MeasuredText { */ public @FloatRange(from = 0.0) @Px float getWidth( @IntRange(from = 0) int start, @IntRange(from = 0) int end) { - Preconditions.checkArgument(0 <= start && start <= mChars.length, - "start(%d) must be 0 <= start <= %d", start, mChars.length); - Preconditions.checkArgument(0 <= end && end <= mChars.length, - "end(%d) must be 0 <= end <= %d", end, mChars.length); - Preconditions.checkArgument(start <= end, - "start(%d) is larger than end(%d)", start, end); + rangeCheck(start, end); return nGetWidth(mNativePtr, start, end); } @@ -120,12 +141,7 @@ public class MeasuredText { */ public void getBounds(@IntRange(from = 0) int start, @IntRange(from = 0) int end, @NonNull Rect rect) { - Preconditions.checkArgument(0 <= start && start <= mChars.length, - "start(%d) must be 0 <= start <= %d", start, mChars.length); - Preconditions.checkArgument(0 <= end && end <= mChars.length, - "end(%d) must be 0 <= end <= %d", end, mChars.length); - Preconditions.checkArgument(start <= end, - "start(%d) is larger than end(%d)", start, end); + rangeCheck(start, end); Preconditions.checkNotNull(rect); nGetBounds(mNativePtr, mChars, start, end, rect); } @@ -139,12 +155,7 @@ public class MeasuredText { */ public void getFontMetricsInt(@IntRange(from = 0) int start, @IntRange(from = 0) int end, @NonNull Paint.FontMetricsInt outMetrics) { - Preconditions.checkArgument(0 <= start && start <= mChars.length, - "start(%d) must be 0 <= start <= %d", start, mChars.length); - Preconditions.checkArgument(0 <= end && end <= mChars.length, - "end(%d) must be 0 <= end <= %d", end, mChars.length); - Preconditions.checkArgument(start <= end, - "start(%d) is larger than end(%d)", start, end); + rangeCheck(start, end); Objects.requireNonNull(outMetrics); long packed = nGetExtent(mNativePtr, mChars, start, end); @@ -160,8 +171,7 @@ public class MeasuredText { * @param offset an offset of the character. */ public @FloatRange(from = 0.0f) @Px float getCharWidthAt(@IntRange(from = 0) int offset) { - Preconditions.checkArgument(0 <= offset && offset < mChars.length, - "offset(%d) is larger than text length %d" + offset, mChars.length); + offsetCheck(offset); return nGetCharWidthAt(mNativePtr, offset); } 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 038d0081ead8..1abda4287800 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -56,6 +56,7 @@ import android.app.ActivityOptions; import android.app.ActivityThread; import android.app.Application; import android.app.Instrumentation; +import android.app.servertransaction.ClientTransactionListenerController; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -103,6 +104,7 @@ import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; +import java.util.function.BiConsumer; /** * Main controller class that manages split states and presentation. @@ -178,6 +180,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen private final List<ActivityStack> mLastReportedActivityStacks = new ArrayList<>(); + /** WM Jetpack set callback for {@link EmbeddedActivityWindowInfo}. */ + @GuardedBy("mLock") + @Nullable + private Pair<Executor, Consumer<EmbeddedActivityWindowInfo>> + mEmbeddedActivityWindowInfoCallback; + + /** Listener registered to {@link ClientTransactionListenerController}. */ + @GuardedBy("mLock") + @Nullable + private final BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener = + Flags.activityWindowInfoFlag() + ? this::onActivityWindowInfoChanged + : null; + private final Handler mHandler; final Object mLock = new Object(); private final ActivityStartMonitor mActivityStartMonitor; @@ -2456,6 +2472,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } @VisibleForTesting + @Nullable + ActivityThread.ActivityClientRecord getActivityClientRecord(@NonNull Activity activity) { + return ActivityThread.currentActivityThread() + .getActivityClient(activity.getActivityToken()); + } + + @VisibleForTesting ActivityStartMonitor getActivityStartMonitor() { return mActivityStartMonitor; } @@ -2468,8 +2491,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @VisibleForTesting @Nullable IBinder getTaskFragmentTokenFromActivityClientRecord(@NonNull Activity activity) { - final ActivityThread.ActivityClientRecord record = ActivityThread.currentActivityThread() - .getActivityClient(activity.getActivityToken()); + final ActivityThread.ActivityClientRecord record = getActivityClientRecord(activity); return record != null ? record.mTaskFragmentToken : null; } @@ -2876,17 +2898,102 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } + @Override + public void setEmbeddedActivityWindowInfoCallback(@NonNull Executor executor, + @NonNull Consumer<EmbeddedActivityWindowInfo> callback) { + if (!Flags.activityWindowInfoFlag()) { + return; + } + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + synchronized (mLock) { + if (mEmbeddedActivityWindowInfoCallback == null) { + ClientTransactionListenerController.getInstance() + .registerActivityWindowInfoChangedListener(getActivityWindowInfoListener()); + } + mEmbeddedActivityWindowInfoCallback = new Pair<>(executor, callback); + } + } + + @Override + public void clearEmbeddedActivityWindowInfoCallback() { + if (!Flags.activityWindowInfoFlag()) { + return; + } + synchronized (mLock) { + if (mEmbeddedActivityWindowInfoCallback == null) { + return; + } + mEmbeddedActivityWindowInfoCallback = null; + ClientTransactionListenerController.getInstance() + .unregisterActivityWindowInfoChangedListener(getActivityWindowInfoListener()); + } + } + + @VisibleForTesting + @GuardedBy("mLock") + @Nullable + BiConsumer<IBinder, ActivityWindowInfo> getActivityWindowInfoListener() { + return mActivityWindowInfoListener; + } + + @Nullable + @Override + public EmbeddedActivityWindowInfo getEmbeddedActivityWindowInfo(@NonNull Activity activity) { + if (!Flags.activityWindowInfoFlag()) { + return null; + } + synchronized (mLock) { + final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity); + return activityWindowInfo != null + ? translateActivityWindowInfo(activity, activityWindowInfo) + : null; + } + } + + @VisibleForTesting + void onActivityWindowInfoChanged(@NonNull IBinder activityToken, + @NonNull ActivityWindowInfo activityWindowInfo) { + synchronized (mLock) { + if (mEmbeddedActivityWindowInfoCallback == null) { + return; + } + final Executor executor = mEmbeddedActivityWindowInfoCallback.first; + final Consumer<EmbeddedActivityWindowInfo> callback = + mEmbeddedActivityWindowInfoCallback.second; + + final Activity activity = getActivity(activityToken); + if (activity == null) { + return; + } + final EmbeddedActivityWindowInfo info = translateActivityWindowInfo( + activity, activityWindowInfo); + + executor.execute(() -> callback.accept(info)); + } + } + @Nullable - private static ActivityWindowInfo getActivityWindowInfo(@NonNull Activity activity) { + private ActivityWindowInfo getActivityWindowInfo(@NonNull Activity activity) { if (activity.isFinishing()) { return null; } - final ActivityThread.ActivityClientRecord record = - ActivityThread.currentActivityThread() - .getActivityClient(activity.getActivityToken()); + final ActivityThread.ActivityClientRecord record = getActivityClientRecord(activity); return record != null ? record.getActivityWindowInfo() : null; } + @NonNull + private static EmbeddedActivityWindowInfo translateActivityWindowInfo( + @NonNull Activity activity, @NonNull ActivityWindowInfo activityWindowInfo) { + final boolean isEmbedded = activityWindowInfo.isEmbedded(); + final Rect activityBounds = new Rect(activity.getResources().getConfiguration() + .windowConfiguration.getBounds()); + final Rect taskBounds = new Rect(activityWindowInfo.getTaskBounds()); + final Rect activityStackBounds = new Rect(activityWindowInfo.getTaskFragmentBounds()); + return new EmbeddedActivityWindowInfo(activity, isEmbedded, activityBounds, taskBounds, + activityStackBounds); + } + /** * If the two rules have the same presentation, and the calculated {@link SplitAttributes} * matches the {@link SplitAttributes} of {@link SplitContainer}, we can reuse the same 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 2f2da8c53db0..b53b9c519cb6 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -387,7 +387,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { // Sets the dim area when the two TaskFragments are adjacent. final boolean dimOnTask = !isStacked - && splitAttributes.getWindowAttributes().getDimArea() == DIM_AREA_ON_TASK + && splitAttributes.getWindowAttributes().getDimAreaBehavior() == DIM_AREA_ON_TASK && Flags.fullscreenDimFlag(); setTaskFragmentDimOnTask(wct, primaryContainer.getTaskFragmentToken(), dimOnTask); setTaskFragmentDimOnTask(wct, secondaryContainer.getTaskFragmentToken(), dimOnTask); @@ -590,7 +590,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { final boolean isFillParent = relativeBounds.isEmpty(); final boolean isIsolatedNavigated = !isFillParent && container.isOverlay(); final boolean dimOnTask = !isFillParent - && attributes.getWindowAttributes().getDimArea() == DIM_AREA_ON_TASK + && attributes.getWindowAttributes().getDimAreaBehavior() == DIM_AREA_ON_TASK && Flags.fullscreenDimFlag(); final IBinder fragmentToken = container.getTaskFragmentToken(); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index 00f8b5925d66..bdeeb7304b12 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -72,6 +72,8 @@ import static org.mockito.Mockito.times; import android.annotation.NonNull; import android.app.Activity; import android.app.ActivityOptions; +import android.app.ActivityThread; +import android.app.servertransaction.ClientTransactionListenerController; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -83,9 +85,11 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.ArraySet; import android.view.WindowInsets; import android.view.WindowMetrics; +import android.window.ActivityWindowInfo; import android.window.TaskFragmentInfo; import android.window.TaskFragmentOrganizer; import android.window.TaskFragmentParentInfo; @@ -99,7 +103,10 @@ import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; import androidx.window.extensions.layout.WindowLayoutComponentImpl; import androidx.window.extensions.layout.WindowLayoutInfo; +import com.android.window.flags.Flags; + import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -110,6 +117,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.concurrent.Executor; +import java.util.function.BiConsumer; import java.util.function.Consumer; /** @@ -127,6 +136,9 @@ public class SplitControllerTest { private static final Intent PLACEHOLDER_INTENT = new Intent().setComponent( new ComponentName("test", "placeholder")); + @Rule + public final SetFlagsRule mSetFlagRule = new SetFlagsRule(); + private Activity mActivity; @Mock private Resources mActivityResources; @@ -138,6 +150,13 @@ public class SplitControllerTest { private Handler mHandler; @Mock private WindowLayoutComponentImpl mWindowLayoutComponent; + @Mock + private ActivityWindowInfo mActivityWindowInfo; + @Mock + private BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener; + @Mock + private androidx.window.extensions.core.util.function.Consumer<EmbeddedActivityWindowInfo> + mEmbeddedActivityWindowInfoCallback; private SplitController mSplitController; private SplitPresenter mSplitPresenter; @@ -1529,6 +1548,73 @@ public class SplitControllerTest { .getTopNonFinishingActivity(), secondaryActivity); } + @Test + public void testIsActivityEmbedded() { + mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG); + + assertFalse(mSplitController.isActivityEmbedded(mActivity)); + + doReturn(true).when(mActivityWindowInfo).isEmbedded(); + + assertTrue(mSplitController.isActivityEmbedded(mActivity)); + } + + @Test + public void testGetEmbeddedActivityWindowInfo() { + mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG); + + final boolean isEmbedded = true; + final Rect activityBounds = mActivity.getResources().getConfiguration().windowConfiguration + .getBounds(); + final Rect taskBounds = new Rect(0, 0, 1000, 2000); + final Rect activityStackBounds = new Rect(0, 0, 500, 2000); + doReturn(isEmbedded).when(mActivityWindowInfo).isEmbedded(); + doReturn(taskBounds).when(mActivityWindowInfo).getTaskBounds(); + doReturn(activityStackBounds).when(mActivityWindowInfo).getTaskFragmentBounds(); + + final EmbeddedActivityWindowInfo expected = new EmbeddedActivityWindowInfo(mActivity, + isEmbedded, activityBounds, taskBounds, activityStackBounds); + assertEquals(expected, mSplitController.getEmbeddedActivityWindowInfo(mActivity)); + } + + @Test + public void testSetEmbeddedActivityWindowInfoCallback() { + mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG); + + final ClientTransactionListenerController controller = ClientTransactionListenerController + .getInstance(); + spyOn(controller); + doNothing().when(controller).registerActivityWindowInfoChangedListener(any()); + doReturn(mActivityWindowInfoListener).when(mSplitController) + .getActivityWindowInfoListener(); + final Executor executor = Runnable::run; + + // Register to ClientTransactionListenerController + mSplitController.setEmbeddedActivityWindowInfoCallback(executor, + mEmbeddedActivityWindowInfoCallback); + + verify(controller).registerActivityWindowInfoChangedListener(mActivityWindowInfoListener); + verify(mEmbeddedActivityWindowInfoCallback, never()).accept(any()); + + // Test onActivityWindowInfoChanged triggered. + mSplitController.onActivityWindowInfoChanged(mActivity.getActivityToken(), + mActivityWindowInfo); + + verify(mEmbeddedActivityWindowInfoCallback).accept(any()); + + // Unregister to ClientTransactionListenerController + mSplitController.clearEmbeddedActivityWindowInfoCallback(); + + verify(controller).unregisterActivityWindowInfoChangedListener(mActivityWindowInfoListener); + + // Test onActivityWindowInfoChanged triggered as no-op after clear callback. + clearInvocations(mEmbeddedActivityWindowInfoCallback); + mSplitController.onActivityWindowInfoChanged(mActivity.getActivityToken(), + mActivityWindowInfo); + + verify(mEmbeddedActivityWindowInfoCallback, never()).accept(any()); + } + /** Creates a mock activity in the organizer process. */ private Activity createMockActivity() { return createMockActivity(TASK_ID); @@ -1537,13 +1623,17 @@ public class SplitControllerTest { /** Creates a mock activity in the organizer process. */ private Activity createMockActivity(int taskId) { final Activity activity = mock(Activity.class); + final ActivityThread.ActivityClientRecord activityClientRecord = + mock(ActivityThread.ActivityClientRecord.class); doReturn(mActivityResources).when(activity).getResources(); final IBinder activityToken = new Binder(); doReturn(activityToken).when(activity).getActivityToken(); doReturn(activity).when(mSplitController).getActivity(activityToken); + doReturn(activityClientRecord).when(mSplitController).getActivityClientRecord(activity); doReturn(taskId).when(activity).getTaskId(); doReturn(new ActivityInfo()).when(activity).getActivityInfo(); doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId(); + doReturn(mActivityWindowInfo).when(activityClientRecord).getActivityWindowInfo(); return activity; } diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_header_ic_close.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_header_ic_close.xml new file mode 100644 index 000000000000..ff49edb7a699 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_header_ic_close.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<vector + xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="#FF000000" + android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/> +</vector> diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml index 490f0883fbfb..a5605a7ff50a 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml @@ -33,14 +33,15 @@ android:orientation="horizontal" android:clickable="true" android:focusable="true" - android:paddingStart="6dp" - android:paddingEnd="8dp"> + android:paddingStart="12dp"> <ImageView android:id="@+id/application_icon" android:layout_width="@dimen/desktop_mode_caption_icon_radius" android:layout_height="@dimen/desktop_mode_caption_icon_radius" android:layout_gravity="center_vertical" - android:contentDescription="@string/app_icon_text" /> + android:contentDescription="@string/app_icon_text" + android:layout_marginStart="6dp" + android:scaleType="centerCrop"/> <TextView android:id="@+id/application_name" @@ -53,8 +54,7 @@ android:lineHeight="20dp" android:layout_gravity="center_vertical" android:layout_weight="1" - android:paddingStart="8dp" - android:paddingEnd="8dp" + android:layout_marginStart="8dp" tools:text="Gmail"/> <ImageButton @@ -67,6 +67,7 @@ android:scaleType="fitCenter" android:clickable="false" android:focusable="false" + android:layout_marginHorizontal="8dp" android:layout_gravity="center_vertical"/> </LinearLayout> @@ -87,14 +88,15 @@ <ImageButton android:id="@+id/close_window" - android:layout_width="40dp" + android:layout_width="44dp" android:layout_height="40dp" - android:padding="4dp" + android:paddingHorizontal="10dp" + android:paddingVertical="8dp" android:layout_marginEnd="8dp" android:tint="?androidprv:attr/materialColorOnSurface" android:background="?android:selectableItemBackgroundBorderless" android:contentDescription="@string/close_button_text" - android:src="@drawable/decor_close_button_dark" - android:scaleType="fitCenter" + android:src="@drawable/desktop_mode_header_ic_close" + android:scaleType="centerCrop" android:gravity="end"/> </com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml index 8baaf2f155af..a541c590575f 100644 --- a/libs/WindowManager/Shell/res/values/config.xml +++ b/libs/WindowManager/Shell/res/values/config.xml @@ -145,4 +145,7 @@ <!-- Whether CompatUIController is enabled --> <bool name="config_enableCompatUIController">true</bool> + + <!-- Whether pointer pilfer is required to start back animation. --> + <bool name="config_backAnimationRequiresPointerPilfer">true</bool> </resources> diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 48e6428524ae..7dd39613b438 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -434,15 +434,22 @@ <!-- (32 dp buttons + 10dp margins) * 3 buttons--> <dimen name="caption_right_buttons_width">126dp</dimen> - <!-- 2 buttons * 48dp button size. --> - <dimen name="desktop_mode_right_edge_buttons_width">96dp</dimen> + <!-- 2 buttons * 44dp button size + 16dp total margins. --> + <dimen name="desktop_mode_right_edge_buttons_width">104dp</dimen> <!-- 22dp padding + 24dp app icon + 16dp expand button. Text varies in size, we will calculate that width separately. --> <dimen name="desktop_mode_app_details_width_minus_text">62dp</dimen> - <!-- 22dp padding + 24dp app icon + 16dp expand button + 86dp text (max) --> - <dimen name="desktop_mode_app_details_max_width">148dp</dimen> + <!-- When custom headers are requested, this is the width of the left-aligned region that is + taken up by caption elements and extra margins. The customizable region starts at the + end of this area. --> + <dimen name="desktop_mode_customizable_caption_margin_start">84dp</dimen> + + <!-- When custom headers are requested, this is the width of the right-aligned region that is + taken up by caption elements and extra margins. The customizable region ends at the + start of this area. --> + <dimen name="desktop_mode_customizable_caption_margin_end">152dp</dimen> <!-- The width of the maximize menu in desktop mode. --> <dimen name="desktop_mode_maximize_menu_width">287dp</dimen> @@ -490,7 +497,7 @@ <dimen name="desktop_mode_handle_menu_corner_radius">26dp</dimen> <!-- The radius of the caption menu icon. --> - <dimen name="desktop_mode_caption_icon_radius">28dp</dimen> + <dimen name="desktop_mode_caption_icon_radius">24dp</dimen> <!-- The radius of the caption menu shadow. --> <dimen name="desktop_mode_handle_menu_shadow_radius">2dp</dimen> @@ -503,10 +510,6 @@ split select if dragged until the touch input is within the range. --> <dimen name="desktop_mode_transition_area_width">32dp</dimen> - <!-- The height of the area at the top of the screen where a freeform task will transition to - fullscreen if dragged until the top bound of the task is within the area. --> - <dimen name="desktop_mode_transition_area_height">16dp</dimen> - <!-- The width of the area where a desktop task will transition to fullscreen. --> <dimen name="desktop_mode_fullscreen_from_desktop_width">80dp</dimen> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index 2606fb661e80..9bd8531d33dc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -64,6 +64,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.LatencyTracker; import com.android.internal.view.AppearanceRegion; +import com.android.wm.shell.R; import com.android.wm.shell.animation.FlingAnimationUtils; import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.RemoteCallable; @@ -115,6 +116,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private boolean mShouldStartOnNextMoveEvent = false; private boolean mOnBackStartDispatched = false; private boolean mPointerPilfered = false; + private final boolean mRequirePointerPilfer; private final FlingAnimationUtils mFlingAnimationUtils; @@ -220,6 +222,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mActivityTaskManager = activityTaskManager; mContext = context; mContentResolver = contentResolver; + mRequirePointerPilfer = + context.getResources().getBoolean(R.bool.config_backAnimationRequiresPointerPilfer); mBgHandler = bgHandler; shellInit.addInitCallback(this::onInit, this); mAnimationBackground = backAnimationBackground; @@ -560,7 +564,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private void tryDispatchOnBackStarted( IOnBackInvokedCallback callback, BackMotionEvent backEvent) { - if (mOnBackStartDispatched || callback == null || !mPointerPilfered) { + if (mOnBackStartDispatched + || callback == null + || (!mPointerPilfered && mRequirePointerPilfer)) { return; } dispatchOnBackStarted(callback, backEvent); @@ -1006,6 +1012,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont pw.println(prefix + " mBackGestureStarted=" + mBackGestureStarted); pw.println(prefix + " mPostCommitAnimationInProgress=" + mPostCommitAnimationInProgress); pw.println(prefix + " mShouldStartOnNextMoveEvent=" + mShouldStartOnNextMoveEvent); + pw.println(prefix + " mPointerPilfered=" + mPointerPilfered); + pw.println(prefix + " mRequirePointerPilfer=" + mRequirePointerPilfer); pw.println(prefix + " mCurrentTracker state:"); mCurrentTracker.dump(pw, prefix + " "); pw.println(prefix + " mQueuedTracker state:"); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 8fd6ffe15cfe..474430eb44ab 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -717,11 +717,6 @@ public class BubbleStackView extends FrameLayout // Hide the stack after a delay, if needed. updateTemporarilyInvisibleAnimation(false /* hideImmediately */); - - if (mShouldReorderBubblesAfterGestureCompletes) { - mShouldReorderBubblesAfterGestureCompletes = false; - updateBubbleOrderInternal(mBubbleData.getBubbles(), true); - } } }; @@ -2732,6 +2727,12 @@ public class BubbleStackView extends FrameLayout ev.getAction() != MotionEvent.ACTION_UP && ev.getAction() != MotionEvent.ACTION_CANCEL; + // If there is a deferred reorder action, and the gesture is over, run it now. + if (mShouldReorderBubblesAfterGestureCompletes && !mIsGestureInProgress) { + mShouldReorderBubblesAfterGestureCompletes = false; + updateBubbleOrderInternal(mBubbleData.getBubbles(), false); + } + return dispatched; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java index 2ea43162d225..ad01d0fa311a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java @@ -20,12 +20,12 @@ import static android.view.EventLogTags.IMF_IME_REMOTE_ANIM_CANCEL; import static android.view.EventLogTags.IMF_IME_REMOTE_ANIM_END; import static android.view.EventLogTags.IMF_IME_REMOTE_ANIM_START; import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY; -import static android.view.inputmethod.ImeTracker.TOKEN_NONE; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.res.Configuration; @@ -51,6 +51,7 @@ import android.view.inputmethod.InputMethodManagerGlobal; import androidx.annotation.VisibleForTesting; +import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.wm.shell.sysui.ShellInit; import java.util.ArrayList; @@ -122,7 +123,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } if (mDisplayController.getDisplayLayout(displayId).rotation() != pd.mRotation && isImeShowing(displayId)) { - pd.startAnimation(true, false /* forceRestart */, null /* statsToken */); + pd.startAnimation(true, false /* forceRestart */, + SoftInputShowHideReason.DISPLAY_CONFIGURATION_CHANGED); } } @@ -257,7 +259,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged mInsetsState.set(insetsState, true /* copySources */); if (mImeShowing && !Objects.equals(oldFrame, newFrame) && newSourceVisible) { if (DEBUG) Slog.d(TAG, "insetsChanged when IME showing, restart animation"); - startAnimation(mImeShowing, true /* forceRestart */, null /* statsToken */); + startAnimation(mImeShowing, true /* forceRestart */, + SoftInputShowHideReason.DISPLAY_INSETS_CHANGED); } } @@ -291,7 +294,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged final boolean positionChanged = !imeSourceControl.getSurfacePosition().equals(lastSurfacePosition); if (positionChanged) { - startAnimation(mImeShowing, true /* forceRestart */, null /* statsToken */); + startAnimation(mImeShowing, true /* forceRestart */, + SoftInputShowHideReason.DISPLAY_CONTROLS_CHANGED); } } else { if (!haveSameLeash(mImeSourceControl, imeSourceControl)) { @@ -384,7 +388,20 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } private void startAnimation(final boolean show, final boolean forceRestart, - @Nullable ImeTracker.Token statsToken) { + @SoftInputShowHideReason int reason) { + final var imeSource = mInsetsState.peekSource(InsetsSource.ID_IME); + if (imeSource == null || mImeSourceControl == null) { + return; + } + final var statsToken = ImeTracker.forLogging().onStart( + show ? ImeTracker.TYPE_SHOW : ImeTracker.TYPE_HIDE, ImeTracker.ORIGIN_WM_SHELL, + reason, false /* fromUser */); + + startAnimation(show, forceRestart, statsToken); + } + + private void startAnimation(final boolean show, final boolean forceRestart, + @NonNull final ImeTracker.Token statsToken) { final InsetsSource imeSource = mInsetsState.peekSource(InsetsSource.ID_IME); if (imeSource == null || mImeSourceControl == null) { ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE); @@ -458,7 +475,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE); mAnimation.addListener(new AnimatorListenerAdapter() { private boolean mCancelled = false; - @Nullable + @NonNull private final ImeTracker.Token mStatsToken = statsToken; @Override @@ -484,7 +501,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } if (DEBUG_IME_VISIBILITY) { EventLog.writeEvent(IMF_IME_REMOTE_ANIM_START, - statsToken != null ? statsToken.getTag() : TOKEN_NONE, + mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE, mDisplayId, mAnimationDirection, alpha, startY , endY, Objects.toString(mImeSourceControl.getLeash()), Objects.toString(mImeSourceControl.getInsetsHint()), @@ -500,7 +517,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged mCancelled = true; if (DEBUG_IME_VISIBILITY) { EventLog.writeEvent(IMF_IME_REMOTE_ANIM_CANCEL, - statsToken != null ? statsToken.getTag() : TOKEN_NONE, mDisplayId, + mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE, + mDisplayId, Objects.toString(mImeSourceControl.getInsetsHint())); } } @@ -528,7 +546,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } if (DEBUG_IME_VISIBILITY) { EventLog.writeEvent(IMF_IME_REMOTE_ANIM_END, - statsToken != null ? statsToken.getTag() : TOKEN_NONE, + mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE, mDisplayId, mAnimationDirection, endY, Objects.toString(mImeSourceControl.getLeash()), Objects.toString(mImeSourceControl.getInsetsHint()), diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java index 9bdda14cf00b..ca06024a9adb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java @@ -277,8 +277,7 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan * * @param types {@link InsetsType} to show * @param fromIme true if this request originated from IME (InputMethodService). - * @param statsToken the token tracking the current IME show request - * or {@code null} otherwise. + * @param statsToken the token tracking the current IME request or {@code null} otherwise. */ default void showInsets(@InsetsType int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) {} @@ -288,8 +287,7 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan * * @param types {@link InsetsType} to hide * @param fromIme true if this request originated from IME (InputMethodService). - * @param statsToken the token tracking the current IME hide request - * or {@code null} otherwise. + * @param statsToken the token tracking the current IME request or {@code null} otherwise. */ default void hideInsets(@InsetsType int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) {} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS index 7237d2bde39f..37ccd15587e1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS @@ -1,2 +1,4 @@ # WM shell sub-modules splitscreen owner chenghsiuchang@google.com +jeremysim@google.com +peanutbutter@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java index 7091c4b7210a..fb0ed1587055 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java @@ -98,6 +98,7 @@ public class DesktopModeVisualIndicator { * Based on the coordinates of the current drag event, determine which indicator type we should * display, including no visible indicator. */ + @NonNull IndicatorType updateIndicatorType(PointF inputCoordinates, int windowingMode) { final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId); // If we are in freeform, we don't want a visible indicator in the "freeform" drag zone. @@ -136,18 +137,18 @@ public class DesktopModeVisualIndicator { Region calculateFullscreenRegion(DisplayLayout layout, @WindowConfiguration.WindowingMode int windowingMode, int captionHeight) { final Region region = new Region(); - int edgeTransitionHeight = mContext.getResources().getDimensionPixelSize( - com.android.wm.shell.R.dimen.desktop_mode_transition_area_height); + int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM + ? 2 * layout.stableInsets().top + : mContext.getResources().getDimensionPixelSize( + com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height); // A thin, short Rect at the top of the screen. if (windowingMode == WINDOWING_MODE_FREEFORM) { int fromFreeformWidth = mContext.getResources().getDimensionPixelSize( com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_width); - int fromFreeformHeight = mContext.getResources().getDimensionPixelSize( - com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height); region.union(new Rect((layout.width() / 2) - (fromFreeformWidth / 2), -captionHeight, (layout.width() / 2) + (fromFreeformWidth / 2), - fromFreeformHeight)); + transitionHeight)); } // A screen-wide, shorter Rect if the task is in fullscreen or split. if (windowingMode == WINDOWING_MODE_FULLSCREEN @@ -155,7 +156,7 @@ public class DesktopModeVisualIndicator { region.union(new Rect(0, -captionHeight, layout.width(), - edgeTransitionHeight)); + transitionHeight)); } return region; } 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 b9d0342137c5..654409f4a637 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 @@ -62,7 +62,6 @@ import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.common.annotations.ExternalThread import com.android.wm.shell.common.annotations.ShellMainThread import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT -import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener import com.android.wm.shell.draganddrop.DragAndDropController @@ -141,7 +140,7 @@ class DesktopTasksController( private val transitionAreaHeight get() = context.resources.getDimensionPixelSize( - com.android.wm.shell.R.dimen.desktop_mode_transition_area_height + com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height ) private val transitionAreaWidth @@ -417,23 +416,9 @@ class DesktopTasksController( splitScreenController.getStageOfTask(taskInfo.taskId), EXIT_REASON_DESKTOP_MODE ) - getOtherSplitTask(taskInfo.taskId)?.let { otherTaskInfo -> - wct.removeTask(otherTaskInfo.token) - } } } - private fun getOtherSplitTask(taskId: Int): RunningTaskInfo? { - val remainingTaskPosition: Int = - if (splitScreenController.getSplitPosition(taskId) - == SPLIT_POSITION_BOTTOM_OR_RIGHT) { - SPLIT_POSITION_TOP_OR_LEFT - } else { - SPLIT_POSITION_BOTTOM_OR_RIGHT - } - return splitScreenController.getTaskInfo(remainingTaskPosition) - } - /** * The second part of the animated drag to desktop transition, called after * [startDragToDesktop]. @@ -580,30 +565,7 @@ class DesktopTasksController( * @param position the portion of the screen (RIGHT or LEFT) we want to snap the task to. */ fun snapToHalfScreen(taskInfo: RunningTaskInfo, position: SnapPosition) { - val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return - - val stableBounds = Rect() - displayLayout.getStableBounds(stableBounds) - - val destinationWidth = stableBounds.width() / 2 - val destinationBounds = when (position) { - SnapPosition.LEFT -> { - Rect( - stableBounds.left, - stableBounds.top, - stableBounds.left + destinationWidth, - stableBounds.bottom - ) - } - SnapPosition.RIGHT -> { - Rect( - stableBounds.right - destinationWidth, - stableBounds.top, - stableBounds.right, - stableBounds.bottom - ) - } - } + val destinationBounds = getSnapBounds(taskInfo, position) if (destinationBounds == taskInfo.configuration.windowConfiguration.bounds) return @@ -624,8 +586,35 @@ class DesktopTasksController( outBounds.set(0, 0, desiredWidth, desiredHeight) // Center the task in screen bounds outBounds.offset( - screenBounds.centerX() - outBounds.centerX(), - screenBounds.centerY() - outBounds.centerY()) + screenBounds.centerX() - outBounds.centerX(), + screenBounds.centerY() - outBounds.centerY()) + } + + private fun getSnapBounds(taskInfo: RunningTaskInfo, position: SnapPosition): Rect { + val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return Rect() + + val stableBounds = Rect() + displayLayout.getStableBounds(stableBounds) + + val destinationWidth = stableBounds.width() / 2 + return when (position) { + SnapPosition.LEFT -> { + Rect( + stableBounds.left, + stableBounds.top, + stableBounds.left + destinationWidth, + stableBounds.bottom + ) + } + SnapPosition.RIGHT -> { + Rect( + stableBounds.right - destinationWidth, + stableBounds.top, + stableBounds.right, + stableBounds.bottom + ) + } + } } /** @@ -661,7 +650,7 @@ class DesktopTasksController( ?.let { homeTask -> wct.reorder(homeTask.getToken(), true /* onTop */) } } - private fun releaseVisualIndicator() { + fun releaseVisualIndicator() { val t = SurfaceControl.Transaction() visualIndicator?.releaseVisualIndicator(t) visualIndicator = null @@ -942,16 +931,13 @@ class DesktopTasksController( taskSurface: SurfaceControl, inputX: Float, taskTop: Float - ) { + ): DesktopModeVisualIndicator.IndicatorType { // If the visual indicator does not exist, create it. - if (visualIndicator == null) { - visualIndicator = DesktopModeVisualIndicator( - syncQueue, taskInfo, displayController, context, taskSurface, - rootTaskDisplayAreaOrganizer) - } - // Then, update the indicator type. - val indicator = visualIndicator ?: return - indicator.updateIndicatorType(PointF(inputX, taskTop), taskInfo.windowingMode) + val indicator = visualIndicator ?: DesktopModeVisualIndicator( + syncQueue, taskInfo, displayController, context, taskSurface, + rootTaskDisplayAreaOrganizer) + if (visualIndicator == null) visualIndicator = indicator + return indicator.updateIndicatorType(PointF(inputX, taskTop), taskInfo.windowingMode) } /** @@ -971,20 +957,28 @@ class DesktopTasksController( if (taskInfo.configuration.windowConfiguration.windowingMode != WINDOWING_MODE_FREEFORM) { return } - if (taskBounds.top <= transitionAreaHeight) { - moveToFullscreenWithAnimation(taskInfo, position) - return - } - if (inputCoordinate.x <= transitionAreaWidth) { - releaseVisualIndicator() - snapToHalfScreen(taskInfo, SnapPosition.LEFT) - return - } - if (inputCoordinate.x >= (displayController.getDisplayLayout(taskInfo.displayId)?.width() - ?.minus(transitionAreaWidth) ?: return)) { - releaseVisualIndicator() - snapToHalfScreen(taskInfo, SnapPosition.RIGHT) - return + + val indicator = visualIndicator ?: return + val indicatorType = indicator.updateIndicatorType( + PointF(inputCoordinate.x, taskBounds.top.toFloat()), + taskInfo.windowingMode + ) + when (indicatorType) { + DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR -> { + moveToFullscreenWithAnimation(taskInfo, position) + } + DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR -> { + releaseVisualIndicator() + snapToHalfScreen(taskInfo, SnapPosition.LEFT) + } + DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> { + releaseVisualIndicator() + snapToHalfScreen(taskInfo, SnapPosition.RIGHT) + } + DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR, + DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR -> { + releaseVisualIndicator() + } } // A freeform drag-move ended, remove the indicator immediately. releaseVisualIndicator() @@ -997,14 +991,28 @@ class DesktopTasksController( * @param y height of drag, to be checked against status bar height. */ fun onDragPositioningEndThroughStatusBar( + inputCoordinates: PointF, taskInfo: RunningTaskInfo, freeformBounds: Rect ) { - finalizeDragToDesktop(taskInfo, freeformBounds) - } - - private fun getStatusBarHeight(taskInfo: RunningTaskInfo): Int { - return displayController.getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0 + val indicator = visualIndicator ?: return + val indicatorType = indicator + .updateIndicatorType(inputCoordinates, taskInfo.windowingMode) + when (indicatorType) { + DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR -> { + finalizeDragToDesktop(taskInfo, freeformBounds) + } + DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR, + DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR -> { + cancelDragToDesktop(taskInfo) + } + DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR -> { + finalizeDragToDesktop(taskInfo, getSnapBounds(taskInfo, SnapPosition.LEFT)) + } + DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> { + finalizeDragToDesktop(taskInfo, getSnapBounds(taskInfo, SnapPosition.RIGHT)) + } + } } /** 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 2cdec81d77ac..4d47ca998d8b 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 @@ -122,6 +122,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb private static final long PIP_KEEP_CLEAR_AREAS_DELAY = SystemProperties.getLong("persist.wm.debug.pip_keep_clear_areas_delay", 200); + private static final long ENABLE_TOUCH_DELAY_MS = 200L; + private Context mContext; protected ShellExecutor mMainExecutor; private DisplayController mDisplayController; @@ -152,6 +154,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb private final Runnable mMovePipInResponseToKeepClearAreasChangeCallback = this::onKeepClearAreasChangedCallback; + private final Runnable mEnableTouchCallback = () -> mTouchHandler.setTouchEnabled(true); + private void onKeepClearAreasChangedCallback() { if (mIsKeyguardShowingOrAnimating) { // early bail out if the change was caused by keyguard showing up @@ -1037,6 +1041,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb saveReentryState(pipBounds); } // Disable touches while the animation is running + mMainExecutor.removeCallbacks(mEnableTouchCallback); mTouchHandler.setTouchEnabled(false); if (mPinnedStackAnimationRecentsCallback != null) { mPinnedStackAnimationRecentsCallback.onPipAnimationStarted(); @@ -1067,7 +1072,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb InteractionJankMonitor.getInstance().end(CUJ_PIP_TRANSITION); // Re-enable touches after the animation completes - mTouchHandler.setTouchEnabled(true); + mMainExecutor.executeDelayed(mEnableTouchCallback, ENABLE_TOUCH_DELAY_MS); mTouchHandler.onPinnedStackAnimationEnded(direction); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OWNERS index 7237d2bde39f..37ccd15587e1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OWNERS +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OWNERS @@ -1,2 +1,4 @@ # WM shell sub-modules splitscreen owner chenghsiuchang@google.com +jeremysim@google.com +peanutbutter@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 2321869d2f4a..76504447339f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -55,6 +55,7 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASO import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP; +import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DESKTOP_MODE; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT; @@ -1607,6 +1608,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // The device is folded case EXIT_REASON_FULLSCREEN_SHORTCUT: // User has used a keyboard shortcut to go back to fullscreen from split + case EXIT_REASON_DESKTOP_MODE: + // One of the children enters desktop mode return true; default: 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 bfb60c0f4fc8..da2965c05ee4 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 @@ -417,7 +417,7 @@ public class SplashscreenContentDrawer { final SplashViewBuilder builder = new SplashViewBuilder(context, ai); final SplashScreenView view = builder .setWindowBGColor(themeBGColor) - .chooseStyle(STARTING_WINDOW_TYPE_SPLASH_SCREEN) + .chooseStyle(STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN) .build(); view.setNotCopyable(); return view; 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 8d798a3035f6..f4ccd689f938 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 @@ -30,6 +30,10 @@ import static android.view.WindowInsets.Type.statusBars; 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.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR; +import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR; +import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR; +import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR; import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION; import static com.android.wm.shell.windowdecor.MoveToDesktopAnimator.DRAG_FREEFORM_SCALE; @@ -81,6 +85,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import com.android.wm.shell.desktopmode.DesktopModeStatus; +import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition; import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; @@ -119,9 +124,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private final Choreographer mMainChoreographer; private final DisplayController mDisplayController; private final SyncTransactionQueue mSyncQueue; - private final Optional<DesktopTasksController> mDesktopTasksController; + private final DesktopTasksController mDesktopTasksController; private final InputManager mInputManager; - private boolean mTransitionDragActive; private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>(); @@ -129,8 +133,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private final ExclusionRegionListener mExclusionRegionListener = new ExclusionRegionListenerImpl(); - private final SparseArray<DesktopModeWindowDecoration> mWindowDecorByTaskId = - new SparseArray<>(); + private final SparseArray<DesktopModeWindowDecoration> mWindowDecorByTaskId; private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl(); private final InputMonitorFactory mInputMonitorFactory; private TaskOperations mTaskOperations; @@ -197,7 +200,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { new DesktopModeWindowDecoration.Factory(), new InputMonitorFactory(), SurfaceControl.Transaction::new, - rootTaskDisplayAreaOrganizer); + rootTaskDisplayAreaOrganizer, + new SparseArray<>()); } @VisibleForTesting @@ -219,7 +223,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory, InputMonitorFactory inputMonitorFactory, Supplier<SurfaceControl.Transaction> transactionFactory, - RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) { + RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, + SparseArray<DesktopModeWindowDecoration> windowDecorByTaskId) { mContext = context; mMainExecutor = shellExecutor; mMainHandler = mainHandler; @@ -231,7 +236,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mDisplayInsetsController = displayInsetsController; mSyncQueue = syncQueue; mTransitions = transitions; - mDesktopTasksController = desktopTasksController; + mDesktopTasksController = desktopTasksController.get(); mShellCommandHandler = shellCommandHandler; mWindowManager = windowManager; mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory; @@ -239,6 +244,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mTransactionFactory = transactionFactory; mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer; mInputManager = mContext.getSystemService(InputManager.class); + mWindowDecorByTaskId = windowDecorByTaskId; shellInit.addInitCallback(this::onInit, this); } @@ -248,8 +254,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mShellCommandHandler.addDumpCallback(this::dump, this); mDisplayInsetsController.addInsetsChangedListener(mContext.getDisplayId(), new DesktopModeOnInsetsChangedListener()); - mDesktopTasksController.ifPresent(c -> c.setOnTaskResizeAnimationListener( - new DeskopModeOnTaskResizeAnimationListener())); + mDesktopTasksController.setOnTaskResizeAnimationListener( + new DeskopModeOnTaskResizeAnimationListener()); try { mWindowManager.registerSystemGestureExclusionListener(mGestureExclusionListener, mContext.getDisplayId()); @@ -273,7 +279,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId); if (decor != null && DesktopModeStatus.isEnabled() && decor.mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { - mDesktopTasksController.ifPresent(c -> c.moveToSplit(decor.mTaskInfo)); + mDesktopTasksController.moveToSplit(decor.mTaskInfo); } } } @@ -340,8 +346,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { @Override public void destroyWindowDecoration(RunningTaskInfo taskInfo) { - final DesktopModeWindowDecoration decoration = - mWindowDecorByTaskId.removeReturnOld(taskInfo.taskId); + final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId); if (decoration == null) return; decoration.close(); @@ -349,6 +354,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { if (mEventReceiversByDisplay.contains(displayId)) { removeTaskFromEventReceiver(displayId); } + // Remove the decoration from the cache last because WindowDecoration#close could still + // issue CANCEL MotionEvents to touch listeners before its view host is released. + // See b/327664694. + mWindowDecorByTaskId.remove(taskInfo.taskId); } private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener @@ -414,14 +423,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { decoration.closeHandleMenu(); } } else if (id == R.id.desktop_button) { - if (mDesktopTasksController.isPresent()) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - // App sometimes draws before the insets from WindowDecoration#relayout have - // been added, so they must be added here - mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct); - mDesktopTasksController.get().moveToDesktop(mTaskId, wct); - closeOtherSplitTask(mTaskId); - } + final WindowContainerTransaction wct = new WindowContainerTransaction(); + // App sometimes draws before the insets from WindowDecoration#relayout have + // been added, so they must be added here + mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct); + mDesktopTasksController.moveToDesktop(mTaskId, wct); decoration.closeHandleMenu(); } else if (id == R.id.fullscreen_button) { decoration.closeHandleMenu(); @@ -429,42 +435,37 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mSplitScreenController.moveTaskToFullscreen(mTaskId, SplitScreenController.EXIT_REASON_DESKTOP_MODE); } else { - mDesktopTasksController.ifPresent(c -> - c.moveToFullscreen(mTaskId)); + mDesktopTasksController.moveToFullscreen(mTaskId); } } else if (id == R.id.split_screen_button) { decoration.closeHandleMenu(); - mDesktopTasksController.ifPresent(c -> { - c.requestSplit(decoration.mTaskInfo); - }); + mDesktopTasksController.requestSplit(decoration.mTaskInfo); } else if (id == R.id.collapse_menu_button) { decoration.closeHandleMenu(); } else if (id == R.id.select_button) { if (DesktopModeStatus.IS_DISPLAY_CHANGE_ENABLED) { // TODO(b/278084491): dev option to enable display switching // remove when select is implemented - mDesktopTasksController.ifPresent(c -> c.moveToNextDisplay(mTaskId)); + mDesktopTasksController.moveToNextDisplay(mTaskId); } } else if (id == R.id.maximize_window) { final RunningTaskInfo taskInfo = decoration.mTaskInfo; decoration.closeHandleMenu(); decoration.closeMaximizeMenu(); - mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(taskInfo)); + mDesktopTasksController.toggleDesktopTaskSize(taskInfo); } else if (id == R.id.maximize_menu_maximize_button) { final RunningTaskInfo taskInfo = decoration.mTaskInfo; - mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(taskInfo)); + mDesktopTasksController.toggleDesktopTaskSize(taskInfo); decoration.closeHandleMenu(); decoration.closeMaximizeMenu(); } else if (id == R.id.maximize_menu_snap_left_button) { final RunningTaskInfo taskInfo = decoration.mTaskInfo; - mDesktopTasksController.ifPresent(c -> c.snapToHalfScreen( - taskInfo, SnapPosition.LEFT)); + mDesktopTasksController.snapToHalfScreen(taskInfo, SnapPosition.LEFT); decoration.closeHandleMenu(); decoration.closeMaximizeMenu(); } else if (id == R.id.maximize_menu_snap_right_button) { final RunningTaskInfo taskInfo = decoration.mTaskInfo; - mDesktopTasksController.ifPresent(c -> c.snapToHalfScreen( - taskInfo, SnapPosition.RIGHT)); + mDesktopTasksController.snapToHalfScreen(taskInfo, SnapPosition.RIGHT); decoration.closeHandleMenu(); decoration.closeMaximizeMenu(); } @@ -559,8 +560,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } else if (ev.getAction() == ACTION_HOVER_EXIT) { if (!decoration.isMaximizeMenuActive() && id == R.id.maximize_window) { decoration.onMaximizeWindowHoverExit(); - } else if (id == R.id.maximize_window - || MaximizeMenu.Companion.isMaximizeMenuView(id)) { + } else if (id == R.id.maximize_window || id == R.id.maximize_menu) { // Close menu if not hovering over maximize menu or maximize button after a // delay to give user a chance to re-enter view or to move from one maximize // menu view to another. @@ -574,7 +574,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private void moveTaskToFront(RunningTaskInfo taskInfo) { if (!taskInfo.isFocused) { - mDesktopTasksController.ifPresent(c -> c.moveTaskToFront(taskInfo)); + mDesktopTasksController.moveTaskToFront(taskInfo); } } @@ -618,10 +618,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { final int dragPointerIdx = e.findPointerIndex(mDragPointerId); final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningMove( e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); - mDesktopTasksController.ifPresent(c -> c.onDragPositioningMove(taskInfo, + mDesktopTasksController.onDragPositioningMove(taskInfo, decoration.mTaskSurface, e.getRawX(dragPointerIdx), - newTaskBounds)); + newTaskBounds); mIsDragging = true; return true; } @@ -643,10 +643,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { (int) (e.getRawY(dragPointerIdx) - e.getY(dragPointerIdx))); final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningEnd( e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); - mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo, - position, + mDesktopTasksController.onDragPositioningEnd(taskInfo, position, new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)), - newTaskBounds)); + newTaskBounds); if (touchingButton && !mHasLongClicked) { // We need the input event to not be consumed here to end the ripple // effect on the touched button. We will reset drag state in the ensuing @@ -674,10 +673,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { && action != MotionEvent.ACTION_CANCEL)) { return false; } - mDesktopTasksController.ifPresent(c -> { - final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId); - c.toggleDesktopTaskSize(decoration.mTaskInfo); - }); + final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId); + mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo); return true; } } @@ -841,20 +838,29 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return; } if (mTransitionDragActive) { + final DesktopModeVisualIndicator.IndicatorType indicatorType = + mDesktopTasksController.updateVisualIndicator(relevantDecor.mTaskInfo, + relevantDecor.mTaskSurface, ev.getRawX(), ev.getRawY()); mTransitionDragActive = false; - final int statusBarHeight = getStatusBarHeight( - relevantDecor.mTaskInfo.displayId); - if (ev.getRawY() > 2 * statusBarHeight) { + if (indicatorType == TO_DESKTOP_INDICATOR + || indicatorType == TO_SPLIT_LEFT_INDICATOR + || indicatorType == TO_SPLIT_RIGHT_INDICATOR) { if (DesktopModeStatus.isEnabled()) { animateToDesktop(relevantDecor, ev); } mMoveToDesktopAnimator = null; return; } else if (mMoveToDesktopAnimator != null) { - mDesktopTasksController.ifPresent( - c -> c.cancelDragToDesktop(relevantDecor.mTaskInfo)); + mDesktopTasksController.onDragPositioningEndThroughStatusBar( + new PointF(ev.getRawX(), ev.getRawY()), + relevantDecor.mTaskInfo, + calculateFreeformBounds(ev.getDisplayId(), DRAG_FREEFORM_SCALE)); mMoveToDesktopAnimator = null; return; + } else { + // In cases where we create an indicator but do not start the + // move-to-desktop animation, we need to dismiss it. + mDesktopTasksController.releaseVisualIndicator(); } } relevantDecor.checkClickEvent(ev); @@ -866,20 +872,17 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return; } if (mTransitionDragActive) { - mDesktopTasksController.ifPresent( - c -> c.updateVisualIndicator( + final DesktopModeVisualIndicator.IndicatorType indicatorType = + mDesktopTasksController.updateVisualIndicator( relevantDecor.mTaskInfo, - relevantDecor.mTaskSurface, ev.getRawX(), ev.getRawY())); - final int statusBarHeight = getStatusBarHeight( - relevantDecor.mTaskInfo.displayId); - if (ev.getRawY() > statusBarHeight) { + relevantDecor.mTaskSurface, ev.getRawX(), ev.getRawY()); + if (indicatorType != TO_FULLSCREEN_INDICATOR) { if (mMoveToDesktopAnimator == null) { mMoveToDesktopAnimator = new MoveToDesktopAnimator( mContext, mDragToDesktopAnimationStartBounds, relevantDecor.mTaskInfo, relevantDecor.mTaskSurface); - mDesktopTasksController.ifPresent( - c -> c.startDragToDesktop(relevantDecor.mTaskInfo, - mMoveToDesktopAnimator)); + mDesktopTasksController.startDragToDesktop(relevantDecor.mTaskInfo, + mMoveToDesktopAnimator); } } if (mMoveToDesktopAnimator != null) { @@ -925,6 +928,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { * Animates a window to the center, grows to freeform size, and transitions to Desktop Mode. * @param relevantDecor the window decor of the task to be animated * @param ev the motion event that triggers the animation + * TODO(b/315527000): This animation needs to be adjusted to allow snap left/right cases. + * Currently fullscreen -> split snap still animates to center screen before readjusting. */ private void centerAndMoveToDesktopWithAnimation(DesktopModeWindowDecoration relevantDecor, MotionEvent ev) { @@ -948,13 +953,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - mDesktopTasksController.ifPresent( - c -> { - c.onDragPositioningEndThroughStatusBar(relevantDecor.mTaskInfo, - calculateFreeformBounds(ev.getDisplayId(), - DesktopTasksController - .DESKTOP_MODE_INITIAL_BOUNDS_SCALE)); - }); + mDesktopTasksController.onDragPositioningEndThroughStatusBar( + new PointF(ev.getRawX(), ev.getRawY()), + relevantDecor.mTaskInfo, + calculateFreeformBounds(ev.getDisplayId(), + DesktopTasksController + .DESKTOP_MODE_INITIAL_BOUNDS_SCALE)); } }); animator.start(); @@ -1084,7 +1088,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { final DragPositioningCallback dragPositioningCallback; final int transitionAreaHeight = mContext.getResources().getDimensionPixelSize( - R.dimen.desktop_mode_transition_area_height); + R.dimen.desktop_mode_fullscreen_from_desktop_height); if (!DesktopModeStatus.isVeiledResizeEnabled()) { dragPositioningCallback = new FluidResizeTaskPositioner( mTaskOrganizer, mTransitions, windowDecoration, mDisplayController, @@ -1119,12 +1123,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return mSplitScreenController.getTaskInfo(remainingTaskPosition); } - private void closeOtherSplitTask(int taskId) { - if (isTaskInSplitScreen(taskId)) { - mTaskOperations.closeTask(getOtherSplitTask(taskId).token); - } - } - private boolean isTaskInSplitScreen(int taskId) { return mSplitScreenController != null && mSplitScreenController.isTaskInSplitScreen(taskId); @@ -1189,12 +1187,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { @Override public void onExclusionRegionChanged(int taskId, Region region) { - mDesktopTasksController.ifPresent(d -> d.onExclusionRegionChanged(taskId, region)); + mDesktopTasksController.onExclusionRegionChanged(taskId, region); } @Override public void onExclusionRegionDismissed(int taskId) { - mDesktopTasksController.ifPresent(d -> d.removeExclusionRegionForTask(taskId)); + mDesktopTasksController.removeExclusionRegionForTask(taskId); } } 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 9e999ae52835..39803e2afd34 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 @@ -318,28 +318,25 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin relayoutParams.mCaptionHeightId = getCaptionHeightIdStatic(taskInfo.getWindowingMode()); relayoutParams.mCaptionWidthId = getCaptionWidthId(relayoutParams.mLayoutResId); - // The "app controls" type caption bar should report the occluding elements as bounding - // rects to the insets system so that apps can draw in the empty space left in the center. - if (captionLayoutId == R.layout.desktop_mode_app_controls_window_decor) { - // The "app chip" section of the caption bar, it's aligned to the left and its width - // varies depending on the length of the app name, but we'll report its max width for - // now. - // TODO(b/316387515): consider reporting the true width after it's been laid out. + if (captionLayoutId == R.layout.desktop_mode_app_controls_window_decor + && TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) { + // App is requesting to customize the caption bar. Allow input to fall through to the + // windows below so that the app can respond to input events on their custom content. + relayoutParams.mAllowCaptionInputFallthrough = true; + // Report occluding elements as bounding rects to the insets system so that apps can + // draw in the empty space in the center: + // First, the "app chip" section of the caption bar (+ some extra margins). final RelayoutParams.OccludingCaptionElement appChipElement = new RelayoutParams.OccludingCaptionElement(); - appChipElement.mWidthResId = R.dimen.desktop_mode_app_details_max_width; + appChipElement.mWidthResId = R.dimen.desktop_mode_customizable_caption_margin_start; appChipElement.mAlignment = RelayoutParams.OccludingCaptionElement.Alignment.START; relayoutParams.mOccludingCaptionElements.add(appChipElement); - // The "controls" section of the caption bar (maximize, close btns). These are aligned - // to the right of the caption bar and have a fixed width. - // TODO(b/316387515): add additional padding for an exclusive drag-move region. + // Then, the right-aligned section (drag space, maximize and close buttons). final RelayoutParams.OccludingCaptionElement controlsElement = new RelayoutParams.OccludingCaptionElement(); - controlsElement.mWidthResId = R.dimen.desktop_mode_right_edge_buttons_width; + controlsElement.mWidthResId = R.dimen.desktop_mode_customizable_caption_margin_end; controlsElement.mAlignment = RelayoutParams.OccludingCaptionElement.Alignment.END; relayoutParams.mOccludingCaptionElements.add(controlsElement); - relayoutParams.mAllowCaptionInputFallthrough = - TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo); } if (DesktopModeStatus.useWindowShadow(/* isFocusedWindow= */ taskInfo.isFocused)) { relayoutParams.mShadowRadiusId = taskInfo.isFocused diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt index b7dd01faa543..58bbb030da01 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt @@ -12,6 +12,7 @@ import android.widget.ImageButton import android.widget.ImageView import android.widget.TextView import androidx.core.content.withStyledAttributes +import androidx.core.view.isVisible import com.android.internal.R.attr.materialColorOnSecondaryContainer import com.android.internal.R.attr.materialColorOnSurface import com.android.internal.R.attr.materialColorSecondaryContainer @@ -76,6 +77,7 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder( closeWindowButton.imageTintList = ColorStateList.valueOf(color) maximizeWindowButton.imageTintList = ColorStateList.valueOf(color) expandMenuButton.imageTintList = ColorStateList.valueOf(color) + appNameTextView.isVisible = !taskInfo.isTransparentCaptionBarAppearance appNameTextView.setTextColor(color) appIconImageView.imageAlpha = alpha maximizeWindowButton.imageAlpha = alpha diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java index 01e2f988fbfc..2c0aa12f22d2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java @@ -38,6 +38,7 @@ import android.view.InsetsSource; import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.SurfaceControl; +import android.view.inputmethod.ImeTracker; import androidx.test.filters.SmallTest; @@ -51,6 +52,12 @@ import org.mockito.MockitoAnnotations; import java.util.concurrent.Executor; +/** + * Tests for the display IME controller. + * + * <p> Build/Install/Run: + * atest WMShellUnitTests:DisplayImeControllerTest + */ @SmallTest public class DisplayImeControllerTest extends ShellTestCase { @@ -99,13 +106,13 @@ public class DisplayImeControllerTest extends ShellTestCase { @Test public void showInsets_schedulesNoWorkOnExecutor() { - mPerDisplay.showInsets(ime(), true /* fromIme */, null /* statsToken */); + mPerDisplay.showInsets(ime(), true /* fromIme */, ImeTracker.Token.empty()); verifyZeroInteractions(mExecutor); } @Test public void hideInsets_schedulesNoWorkOnExecutor() { - mPerDisplay.hideInsets(ime(), true /* fromIme */, null /* statsToken */); + mPerDisplay.hideInsets(ime(), true /* fromIme */, ImeTracker.Token.empty()); verifyZeroInteractions(mExecutor); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java index 956f1cd419c2..669e433ba386 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java @@ -50,6 +50,12 @@ import org.mockito.MockitoAnnotations; import java.util.List; +/** + * Tests for the display insets controller. + * + * <p> Build/Install/Run: + * atest WMShellUnitTests:DisplayInsetsControllerTest + */ @SmallTest public class DisplayInsetsControllerTest extends ShellTestCase { @@ -114,9 +120,9 @@ public class DisplayInsetsControllerTest extends ShellTestCase { mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsChanged(null); mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsControlChanged(null, null); mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).showInsets(0, false, - null /* statsToken */); + ImeTracker.Token.empty()); mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).hideInsets(0, false, - null /* statsToken */); + ImeTracker.Token.empty()); mExecutor.flushAll(); assertTrue(defaultListener.topFocusedWindowChangedCount == 1); @@ -136,9 +142,9 @@ public class DisplayInsetsControllerTest extends ShellTestCase { mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsChanged(null); mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsControlChanged(null, null); mInsetsControllersByDisplayId.get(SECOND_DISPLAY).showInsets(0, false, - null /* statsToken */); + ImeTracker.Token.empty()); mInsetsControllersByDisplayId.get(SECOND_DISPLAY).hideInsets(0, false, - null /* statsToken */); + ImeTracker.Token.empty()); mExecutor.flushAll(); assertTrue(defaultListener.topFocusedWindowChangedCount == 1); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt index f8ce4ee8e1ce..9703dce8bf53 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt @@ -56,18 +56,16 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() { context, taskSurface, taskDisplayAreaOrganizer) whenever(displayLayout.width()).thenReturn(DISPLAY_BOUNDS.width()) whenever(displayLayout.height()).thenReturn(DISPLAY_BOUNDS.height()) + whenever(displayLayout.stableInsets()).thenReturn(STABLE_INSETS) } @Test fun testFullscreenRegionCalculation() { val transitionHeight = context.resources.getDimensionPixelSize( - R.dimen.desktop_mode_transition_area_height) + R.dimen.desktop_mode_fullscreen_from_desktop_height) val fromFreeformWidth = mContext.resources.getDimensionPixelSize( R.dimen.desktop_mode_fullscreen_from_desktop_width ) - val fromFreeformHeight = mContext.resources.getDimensionPixelSize( - R.dimen.desktop_mode_fullscreen_from_desktop_height - ) var testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, WINDOWING_MODE_FULLSCREEN, CAPTION_HEIGHT) assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight)) @@ -77,7 +75,7 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() { DISPLAY_BOUNDS.width() / 2 - fromFreeformWidth / 2, -50, DISPLAY_BOUNDS.width() / 2 + fromFreeformWidth / 2, - fromFreeformHeight)) + 2 * STABLE_INSETS.top)) testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, WINDOWING_MODE_MULTI_WINDOW, CAPTION_HEIGHT) assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight)) @@ -135,5 +133,12 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() { private const val TRANSITION_AREA_WIDTH = 32 private const val CAPTION_HEIGHT = 50 private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600) + private const val NAVBAR_HEIGHT = 50 + private val STABLE_INSETS = Rect( + DISPLAY_BOUNDS.left, + DISPLAY_BOUNDS.top + CAPTION_HEIGHT, + DISPLAY_BOUNDS.right, + DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT + ) } }
\ No newline at end of file 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 917fd715f71f..9bb5482de715 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 @@ -29,6 +29,7 @@ import android.hardware.display.VirtualDisplay import android.os.Handler import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import android.util.SparseArray import android.view.Choreographer import android.view.Display.DEFAULT_DISPLAY import android.view.IWindowManager @@ -72,6 +73,8 @@ import org.mockito.kotlin.eq import org.mockito.kotlin.whenever import java.util.Optional import java.util.function.Supplier +import org.mockito.Mockito +import org.mockito.kotlin.spy /** Tests of [DesktopModeWindowDecorViewModel] */ @@ -102,6 +105,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { private val transactionFactory = Supplier<SurfaceControl.Transaction> { SurfaceControl.Transaction() } + private val windowDecorByTaskIdSpy = spy(SparseArray<DesktopModeWindowDecoration>()) private lateinit var shellInit: ShellInit private lateinit var desktopModeOnInsetsChangedListener: DesktopModeOnInsetsChangedListener @@ -110,6 +114,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Before fun setUp() { shellInit = ShellInit(mockShellExecutor) + windowDecorByTaskIdSpy.clear() desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel( mContext, mockShellExecutor, @@ -128,7 +133,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { mockDesktopModeWindowDecorFactory, mockInputMonitorFactory, transactionFactory, - mockRootTaskDisplayAreaOrganizer + mockRootTaskDisplayAreaOrganizer, + windowDecorByTaskIdSpy ) whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout) @@ -332,6 +338,19 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { verify(decoration, times(1)).relayout(task) } + @Test + fun testDestroyWindowDecoration_closesBeforeCleanup() { + val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM) + val decoration = setUpMockDecorationForTask(task) + val inOrder = Mockito.inOrder(decoration, windowDecorByTaskIdSpy) + + onTaskOpening(task) + desktopModeWindowDecorViewModel.destroyWindowDecoration(task) + + inOrder.verify(decoration).close() + inOrder.verify(windowDecorByTaskIdSpy).remove(task.taskId) + } + private fun onTaskOpening(task: RunningTaskInfo, leash: SurfaceControl = SurfaceControl()) { desktopModeWindowDecorViewModel.onTaskOpening( task, diff --git a/libs/hostgraphics/ADisplay.cpp b/libs/hostgraphics/ADisplay.cpp new file mode 100644 index 000000000000..9cc1f40184e3 --- /dev/null +++ b/libs/hostgraphics/ADisplay.cpp @@ -0,0 +1,159 @@ +/* + * 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. + */ + +#include <apex/display.h> +#include <utils/Errors.h> + +namespace android::display::impl { + +/** + * Implementation of ADisplayConfig + */ +struct DisplayConfigImpl { + /** + * The width in pixels of the display configuration. + */ + int32_t width{1080}; + + /** + * The height in pixels of the display configuration. + */ + + int32_t height{1920}; + + /** + * The refresh rate of the display configuration, in frames per second. + */ + float fps{60.0}; + + /** + * The vsync offset at which surfaceflinger runs, in nanoseconds. + */ + int64_t sfOffset{0}; + + /** + * The vsync offset at which applications run, in nanoseconds. + */ + int64_t appOffset{0}; +}; + +// DisplayConfigImpl allocation is not managed through C++ memory apis, so +// preventing calling the destructor here. +static_assert(std::is_trivially_destructible<DisplayConfigImpl>::value); + +/** + * Implementation of ADisplay + */ +struct DisplayImpl { + /** + * The type of the display, i.e. whether it is an internal or external + * display. + */ + ADisplayType type; + + /** + * The preferred WCG dataspace + */ + ADataSpace wcgDataspace; + + /** + * The preferred WCG pixel format + */ + AHardwareBuffer_Format wcgPixelFormat; + + /** + * The config for this display. + */ + DisplayConfigImpl config; +}; + +// DisplayImpl allocation is not managed through C++ memory apis, so +// preventing calling the destructor here. +static_assert(std::is_trivially_destructible<DisplayImpl>::value); + +} // namespace android::display::impl + +using namespace android; +using namespace android::display::impl; + +namespace android { + +int ADisplay_acquirePhysicalDisplays(ADisplay*** outDisplays) { + // This is running on host, so there are no physical displays available. + // Create 1 fake display instead. + DisplayImpl** const impls = reinterpret_cast<DisplayImpl**>( + malloc(sizeof(DisplayImpl*) + sizeof(DisplayImpl))); + DisplayImpl* const displayData = reinterpret_cast<DisplayImpl*>(impls + 1); + + displayData[0] = DisplayImpl{ADisplayType::DISPLAY_TYPE_INTERNAL, + ADataSpace::ADATASPACE_UNKNOWN, + AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, + DisplayConfigImpl()}; + impls[0] = displayData; + *outDisplays = reinterpret_cast<ADisplay**>(impls); + return 1; +} + +void ADisplay_release(ADisplay** displays) { + if (displays == nullptr) { + return; + } + free(displays); +} + +float ADisplay_getMaxSupportedFps(ADisplay* display) { + DisplayImpl* impl = reinterpret_cast<DisplayImpl*>(display); + return impl->config.fps; +} + +ADisplayType ADisplay_getDisplayType(ADisplay* display) { + return reinterpret_cast<DisplayImpl*>(display)->type; +} + +void ADisplay_getPreferredWideColorFormat(ADisplay* display, ADataSpace* outDataspace, + AHardwareBuffer_Format* outPixelFormat) { + DisplayImpl* impl = reinterpret_cast<DisplayImpl*>(display); + *outDataspace = impl->wcgDataspace; + *outPixelFormat = impl->wcgPixelFormat; +} + +int ADisplay_getCurrentConfig(ADisplay* display, ADisplayConfig** outConfig) { + DisplayImpl* impl = reinterpret_cast<DisplayImpl*>(display); + *outConfig = reinterpret_cast<ADisplayConfig*>(&impl->config); + return OK; +} + +int32_t ADisplayConfig_getWidth(ADisplayConfig* config) { + return reinterpret_cast<DisplayConfigImpl*>(config)->width; +} + +int32_t ADisplayConfig_getHeight(ADisplayConfig* config) { + return reinterpret_cast<DisplayConfigImpl*>(config)->height; +} + +float ADisplayConfig_getFps(ADisplayConfig* config) { + return reinterpret_cast<DisplayConfigImpl*>(config)->fps; +} + +int64_t ADisplayConfig_getCompositorOffsetNanos(ADisplayConfig* config) { + return reinterpret_cast<DisplayConfigImpl*>(config)->sfOffset; +} + +int64_t ADisplayConfig_getAppVsyncOffsetNanos(ADisplayConfig* config) { + return reinterpret_cast<DisplayConfigImpl*>(config)->appOffset; +} + +} // namespace android diff --git a/libs/hostgraphics/Android.bp b/libs/hostgraphics/Android.bp index f166fdeafa41..4407af68de99 100644 --- a/libs/hostgraphics/Android.bp +++ b/libs/hostgraphics/Android.bp @@ -22,6 +22,7 @@ cc_library_host_static { srcs: [ ":libui_host_common", + "ADisplay.cpp", "Fence.cpp", "HostBufferQueue.cpp", "PublicFormat.cpp", @@ -32,16 +33,21 @@ cc_library_host_static { // When frameworks/native/include will be removed from the list of automatic includes. // We will have to copy necessary headers with a pre-build step (generated headers). ".", - "frameworks/native/libs/nativebase/include", - "frameworks/native/libs/nativewindow/include", "frameworks/native/libs/arect/include", "frameworks/native/libs/ui/include_private", ], + + header_libs: [ + "libnativebase_headers", + "libnativedisplay_headers", + "libnativewindow_headers", + ], + export_include_dirs: ["."], target: { windows: { enabled: true, - } + }, }, } diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 40239b8c2719..54f94f5c4b14 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -346,6 +346,7 @@ cc_defaults { "jni/android_util_PathParser.cpp", "jni/Bitmap.cpp", + "jni/BitmapRegionDecoder.cpp", "jni/BufferUtils.cpp", "jni/HardwareBufferHelpers.cpp", "jni/BitmapFactory.cpp", @@ -421,7 +422,6 @@ cc_defaults { "jni/android_graphics_TextureLayer.cpp", "jni/android_graphics_HardwareRenderer.cpp", "jni/android_graphics_HardwareBufferRenderer.cpp", - "jni/BitmapRegionDecoder.cpp", "jni/GIFMovie.cpp", "jni/GraphicsStatsService.cpp", "jni/Movie.cpp", @@ -559,8 +559,13 @@ cc_defaults { "AnimatorManager.cpp", "CanvasTransform.cpp", "DamageAccumulator.cpp", + "DeviceInfo.cpp", + "FrameInfo.cpp", + "FrameInfoVisualizer.cpp", + "FrameMetricsReporter.cpp", "Gainmap.cpp", "Interpolator.cpp", + "JankTracker.cpp", "LightingInfo.cpp", "Matrix.cpp", "Mesh.cpp", @@ -623,13 +628,8 @@ cc_defaults { "utils/NdkUtils.cpp", "AutoBackendTextureRelease.cpp", "DeferredLayerUpdater.cpp", - "DeviceInfo.cpp", - "FrameInfo.cpp", - "FrameInfoVisualizer.cpp", "HardwareBitmapUploader.cpp", "HWUIProperties.sysprop", - "JankTracker.cpp", - "FrameMetricsReporter.cpp", "Layer.cpp", "LayerUpdateQueue.cpp", "ProfileDataContainer.cpp", @@ -643,7 +643,10 @@ cc_defaults { cflags: ["-Wno-implicit-fallthrough"], }, host: { - header_libs: ["libnativebase_headers"], + header_libs: [ + "libnativebase_headers", + "libnativedisplay_headers", + ], local_include_dirs: ["platform/host"], @@ -655,7 +658,11 @@ cc_defaults { "platform/host/WebViewFunctorManager.cpp", ], - cflags: ["-Wno-unused-private-field"], + cflags: [ + "-DHWUI_NULL_GPU", + "-DNULL_GPU_MAX_TEXTURE_SIZE=4096", + "-Wno-unused-private-field", + ], }, }, } diff --git a/libs/hwui/FrameInfoVisualizer.cpp b/libs/hwui/FrameInfoVisualizer.cpp index 59f21694fb77..1e53fc2164c7 100644 --- a/libs/hwui/FrameInfoVisualizer.cpp +++ b/libs/hwui/FrameInfoVisualizer.cpp @@ -249,6 +249,7 @@ bool FrameInfoVisualizer::consumeProperties() { } void FrameInfoVisualizer::dumpData(int fd) { +#ifdef __ANDROID__ RETURN_IF_PROFILING_DISABLED(); // This method logs the last N frames (where N is <= mDataSize) since the @@ -268,6 +269,7 @@ void FrameInfoVisualizer::dumpData(int fd) { durationMS(i, FrameInfoIndex::IssueDrawCommandsStart, FrameInfoIndex::SwapBuffers), durationMS(i, FrameInfoIndex::SwapBuffers, FrameInfoIndex::FrameCompleted)); } +#endif } } /* namespace uirenderer */ diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp index 4b0ddd2fa2ef..638a060bdb1c 100644 --- a/libs/hwui/JankTracker.cpp +++ b/libs/hwui/JankTracker.cpp @@ -17,10 +17,10 @@ #include "JankTracker.h" #include <cutils/ashmem.h> +#include <cutils/trace.h> #include <errno.h> #include <inttypes.h> #include <log/log.h> -#include <sys/mman.h> #include <algorithm> #include <cmath> @@ -278,7 +278,7 @@ void JankTracker::recomputeThresholds(int64_t frameBudget) REQUIRES(mDataMutex) void JankTracker::dumpData(int fd, const ProfileDataDescription* description, const ProfileData* data) { - +#ifdef __ANDROID__ if (description) { switch (description->type) { case JankTrackerType::Generic: @@ -296,9 +296,11 @@ void JankTracker::dumpData(int fd, const ProfileDataDescription* description, } data->dump(fd); dprintf(fd, "\n"); +#endif } void JankTracker::dumpFrames(int fd) { +#ifdef __ANDROID__ dprintf(fd, "\n\n---PROFILEDATA---\n"); for (size_t i = 0; i < static_cast<size_t>(FrameInfoIndex::NumIndexes); i++) { dprintf(fd, "%s", FrameInfoNames[i]); @@ -315,6 +317,7 @@ void JankTracker::dumpFrames(int fd) { } } dprintf(fd, "\n---PROFILEDATA---\n\n"); +#endif } void JankTracker::reset() REQUIRES(mDataMutex) { diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h index e358b57f6fe1..b1ad8b2eb1b9 100644 --- a/libs/hwui/RenderProperties.h +++ b/libs/hwui/RenderProperties.h @@ -16,32 +16,29 @@ #pragma once -#ifdef __ANDROID__ // Layoutlib does not support device info -#include "DeviceInfo.h" -#endif // __ANDROID__ - -#include "Outline.h" -#include "Rect.h" -#include "RevealClip.h" -#include "effects/StretchEffect.h" -#include "utils/MathUtils.h" -#include "utils/PaintUtils.h" - #include <SkBlendMode.h> -#include <SkImageFilter.h> #include <SkCamera.h> #include <SkColor.h> +#include <SkImageFilter.h> #include <SkMatrix.h> #include <SkRegion.h> - #include <androidfw/ResourceTypes.h> #include <cutils/compiler.h> #include <stddef.h> #include <utils/Log.h> + #include <algorithm> #include <ostream> #include <vector> +#include "DeviceInfo.h" +#include "Outline.h" +#include "Rect.h" +#include "RevealClip.h" +#include "effects/StretchEffect.h" +#include "utils/MathUtils.h" +#include "utils/PaintUtils.h" + class SkBitmap; class SkColorFilter; class SkPaint; @@ -546,13 +543,9 @@ public: } bool fitsOnLayer() const { -#ifdef __ANDROID__ // Layoutlib does not support device info const DeviceInfo* deviceInfo = DeviceInfo::get(); return mPrimitiveFields.mWidth <= deviceInfo->maxTextureSize() && mPrimitiveFields.mHeight <= deviceInfo->maxTextureSize(); -#else - return mPrimitiveFields.mWidth <= 4096 && mPrimitiveFields.mHeight <= 4096; -#endif } bool promotedToLayer() const { diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp index 536ff781badc..af169f4bc4cd 100644 --- a/libs/hwui/VectorDrawable.cpp +++ b/libs/hwui/VectorDrawable.cpp @@ -16,6 +16,7 @@ #include "VectorDrawable.h" +#include <gui/TraceUtils.h> #include <math.h> #include <string.h> #include <utils/Log.h> @@ -26,12 +27,7 @@ #include "SkSamplingOptions.h" #include "SkScalar.h" #include "hwui/Paint.h" - -#ifdef __ANDROID__ #include "renderthread/RenderThread.h" -#endif - -#include <gui/TraceUtils.h> #include "utils/Macros.h" #include "utils/VectorDrawableUtils.h" @@ -544,7 +540,7 @@ bool Tree::allocateBitmapIfNeeded(Cache& cache, int width, int height) { } bool Tree::canReuseBitmap(Bitmap* bitmap, int width, int height) { - return bitmap && width <= bitmap->width() && height <= bitmap->height(); + return bitmap && width == bitmap->width() && height == bitmap->height(); } void Tree::onPropertyChanged(TreeProperties* prop) { diff --git a/media/java/android/media/metrics/PlaybackSession.java b/media/java/android/media/metrics/PlaybackSession.java index f8dd756c5f9c..6223acf8ffa4 100644 --- a/media/java/android/media/metrics/PlaybackSession.java +++ b/media/java/android/media/metrics/PlaybackSession.java @@ -24,7 +24,10 @@ import com.android.internal.util.AnnotationValidations; import java.util.Objects; /** - * An instances of this class represents a session of media playback. + * An instance of this class represents a session of media playback used to report playback + * metrics and events. + * + * Create a new instance using {@link MediaMetricsManager#createPlaybackSession}. */ public final class PlaybackSession implements AutoCloseable { private final @NonNull String mId; @@ -80,6 +83,21 @@ public final class PlaybackSession implements AutoCloseable { mManager.reportTrackChangeEvent(mId, event); } + /** + * A session ID is used to identify a unique playback and to tie together lower-level + * playback components. + * + * Associate this session with a {@link MediaCodec} by passing the ID into + * {@link MediaFormat} through {@link MediaFormat#LOG_SESSION_ID} when + * creating the {@link MediaCodec}. + * + * Associate this session with an {@link AudioTrack} by calling + * {@link AudioTrack#setLogSessionId}. + * + * Associate this session with {@link MediaDrm} and {@link MediaCrypto} by calling + * {@link MediaDrm#getPlaybackComponent} and then calling + * {@link PlaybackComponent#setLogSessionId}. + */ public @NonNull LogSessionId getSessionId() { return mLogSessionId; } diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index 8396005b1b63..0fc80dd55fa9 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -2895,6 +2895,10 @@ static void extractBufferFromContext( jint offset, jint size, std::shared_ptr<C2Buffer> *buffer) { + if ((offset + size) > context->capacity()) { + ALOGW("extractBufferFromContext: offset + size provided exceed capacity"); + return; + } *buffer = context->toC2Buffer(offset, size); if (*buffer == nullptr) { if (!context->mMemory) { @@ -2995,18 +2999,15 @@ static void android_media_MediaCodec_native_queueLinearBlock( "MediaCodec.LinearBlock#obtain method to obtain a compatible buffer."); return; } - sp<CryptoInfosWrapper> cryptoInfos = new CryptoInfosWrapper{decltype(cryptoInfos->value)()}; - jint sampleSize = 0; + sp<CryptoInfosWrapper> cryptoInfos = nullptr; + jint sampleSize = totalSize; if (cryptoInfoArray != nullptr) { + cryptoInfos = new CryptoInfosWrapper{decltype(cryptoInfos->value)()}; extractCryptoInfosFromObjectArray(env, &sampleSize, &cryptoInfos->value, cryptoInfoArray, &errorDetailMsg); - } else { - sampleSize = totalSize; - std::unique_ptr<CodecCryptoInfo> cryptoInfo{new MediaCodecCryptoInfo(totalSize)}; - cryptoInfos->value.push_back(std::move(cryptoInfo)); } if (env->ExceptionCheck()) { // Creation of cryptoInfo failed. Let the exception bubble up. diff --git a/native/android/Android.bp b/native/android/Android.bp index 7f3792d06795..752ebdf3d0e3 100644 --- a/native/android/Android.bp +++ b/native/android/Android.bp @@ -98,6 +98,7 @@ cc_library_shared { "libpowermanager", "android.hardware.configstore@1.0", "android.hardware.configstore-utils", + "android.os.flags-aconfig-cc", "libnativedisplay", ], diff --git a/nfc/api/current.txt b/nfc/api/current.txt index 9d0221a3ae68..c0db089253e7 100644 --- a/nfc/api/current.txt +++ b/nfc/api/current.txt @@ -268,9 +268,9 @@ package android.nfc.cardemulation { ctor public PollingFrame(int, @Nullable byte[], int, int); method public int describeContents(); method @NonNull public byte[] getData(); - method public int getGain(); method public int getTimestamp(); method public int getType(); + method public int getVendorSpecificGain(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.PollingFrame> CREATOR; field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final int POLLING_LOOP_TYPE_A = 65; // 0x41 diff --git a/nfc/java/android/nfc/cardemulation/PollingFrame.java b/nfc/java/android/nfc/cardemulation/PollingFrame.java index 994f4ae1c2e3..29d7bdf37fe4 100644 --- a/nfc/java/android/nfc/cardemulation/PollingFrame.java +++ b/nfc/java/android/nfc/cardemulation/PollingFrame.java @@ -157,7 +157,7 @@ public final class PollingFrame implements Parcelable{ mType = frame.getInt(KEY_POLLING_LOOP_TYPE); byte[] data = frame.getByteArray(KEY_POLLING_LOOP_DATA); mData = (data == null) ? new byte[0] : data; - mGain = frame.getByte(KEY_POLLING_LOOP_GAIN); + mGain = frame.getInt(KEY_POLLING_LOOP_GAIN, -1); mTimestamp = frame.getInt(KEY_POLLING_LOOP_TIMESTAMP); } @@ -194,8 +194,9 @@ public final class PollingFrame implements Parcelable{ /** * Returns the gain representing the field strength of the NFC field when this polling loop * frame was observed. + * @return the gain or -1 if there is no gain measurement associated with this frame. */ - public int getGain() { + public int getVendorSpecificGain() { return mGain; } @@ -227,7 +228,9 @@ public final class PollingFrame implements Parcelable{ public Bundle toBundle() { Bundle frame = new Bundle(); frame.putInt(KEY_POLLING_LOOP_TYPE, getType()); - frame.putByte(KEY_POLLING_LOOP_GAIN, (byte) getGain()); + if (getVendorSpecificGain() != -1) { + frame.putInt(KEY_POLLING_LOOP_GAIN, (byte) getVendorSpecificGain()); + } frame.putByteArray(KEY_POLLING_LOOP_DATA, getData()); frame.putInt(KEY_POLLING_LOOP_TIMESTAMP, getTimestamp()); return frame; @@ -236,7 +239,7 @@ public final class PollingFrame implements Parcelable{ @Override public String toString() { return "PollingFrame { Type: " + (char) getType() - + ", gain: " + getGain() + + ", gain: " + getVendorSpecificGain() + ", timestamp: " + Integer.toUnsignedString(getTimestamp()) + ", data: [" + HexFormat.ofDelimiter(" ").formatHex(getData()) + "] }"; } diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityAnnotations.java b/packages/Connectivity/framework/src/android/net/ConnectivityAnnotations.java deleted file mode 100644 index eb1faa0aa25c..000000000000 --- a/packages/Connectivity/framework/src/android/net/ConnectivityAnnotations.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net; - -import android.annotation.IntDef; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Type annotations for constants used in the connectivity API surface. - * - * The annotations are maintained in a separate class so that it can be built as - * a separate library that other modules can build against, as Typedef should not - * be exposed as SystemApi. - * - * @hide - */ -public final class ConnectivityAnnotations { - private ConnectivityAnnotations() {} - - @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, value = { - ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER, - ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY, - ConnectivityManager.MULTIPATH_PREFERENCE_PERFORMANCE, - }) - public @interface MultipathPreference {} - - @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = false, value = { - ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED, - ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED, - ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED, - }) - public @interface RestrictBackgroundStatus {} -} diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt index e2857f96cdf2..892eabf14191 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt @@ -30,6 +30,7 @@ import android.graphics.drawable.Drawable import android.text.TextUtils import android.util.Log import androidx.activity.result.IntentSenderRequest +import androidx.credentials.PasswordCredential import androidx.credentials.PublicKeyCredential import androidx.credentials.provider.Action import androidx.credentials.provider.AuthenticationAction @@ -125,6 +126,7 @@ private fun getCredentialOptionInfoList( pendingIntent = credentialEntry.pendingIntent, fillInIntent = it.frameworkExtrasIntent, credentialType = CredentialType.PASSWORD, + rawCredentialType = PasswordCredential.TYPE_PASSWORD_CREDENTIAL, credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(), userName = credentialEntry.username.toString(), displayName = credentialEntry.displayName?.toString(), @@ -134,6 +136,9 @@ private fun getCredentialOptionInfoList( isAutoSelectable = credentialEntry.isAutoSelectAllowed && credentialEntry.isAutoSelectAllowedFromOption, entryGroupId = credentialEntry.entryGroupId.toString(), + isDefaultIconPreferredAsSingleProvider = + credentialEntry.isDefaultIconPreferredAsSingleProvider, + affiliatedDomain = credentialEntry.affiliatedDomain?.toString(), ) ) } @@ -147,6 +152,7 @@ private fun getCredentialOptionInfoList( pendingIntent = credentialEntry.pendingIntent, fillInIntent = it.frameworkExtrasIntent, credentialType = CredentialType.PASSKEY, + rawCredentialType = PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL, credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(), userName = credentialEntry.username.toString(), displayName = credentialEntry.displayName?.toString(), @@ -158,6 +164,9 @@ private fun getCredentialOptionInfoList( isAutoSelectable = credentialEntry.isAutoSelectAllowed && credentialEntry.isAutoSelectAllowedFromOption, entryGroupId = credentialEntry.entryGroupId.toString(), + isDefaultIconPreferredAsSingleProvider = + credentialEntry.isDefaultIconPreferredAsSingleProvider, + affiliatedDomain = credentialEntry.affiliatedDomain?.toString(), ) ) } @@ -171,6 +180,7 @@ private fun getCredentialOptionInfoList( pendingIntent = credentialEntry.pendingIntent, fillInIntent = it.frameworkExtrasIntent, credentialType = CredentialType.UNKNOWN, + rawCredentialType = credentialEntry.type, credentialTypeDisplayName = credentialEntry.typeDisplayName?.toString().orEmpty(), userName = credentialEntry.title.toString(), @@ -181,6 +191,9 @@ private fun getCredentialOptionInfoList( isAutoSelectable = credentialEntry.isAutoSelectAllowed && credentialEntry.isAutoSelectAllowedFromOption, entryGroupId = credentialEntry.entryGroupId.toString(), + isDefaultIconPreferredAsSingleProvider = + credentialEntry.isDefaultIconPreferredAsSingleProvider, + affiliatedDomain = credentialEntry.affiliatedDomain?.toString(), ) ) } diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/CredentialEntryInfo.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/CredentialEntryInfo.kt index a5d4730967a3..a657e97de3cc 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/CredentialEntryInfo.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/CredentialEntryInfo.kt @@ -31,6 +31,11 @@ class CredentialEntryInfo( fillInIntent: Intent?, /** Type of this credential used for sorting. Not localized so must not be directly displayed. */ val credentialType: CredentialType, + /** + * String type value of this credential used for sorting. Not localized so must not be directly + * displayed. + */ + val rawCredentialType: String, /** Localized type value of this credential used for display purpose. */ val credentialTypeDisplayName: String, val providerDisplayName: String, @@ -42,6 +47,8 @@ class CredentialEntryInfo( val isAutoSelectable: Boolean, val entryGroupId: String, // Used for deduplication, and displayed as the grouping title // "For <value-of-entryGroupId>" on the more-option screen. + val isDefaultIconPreferredAsSingleProvider: Boolean, + val affiliatedDomain: String?, ) : EntryInfo( providerId, entryKey, diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index ccf401da5a4c..6a1998a5e24e 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -20,6 +20,7 @@ import android.content.ComponentName import android.content.Context import android.content.pm.PackageInfo import android.content.pm.PackageManager +import android.credentials.GetCredentialRequest import android.credentials.selection.CreateCredentialProviderData import android.credentials.selection.DisabledProviderData import android.credentials.selection.Entry @@ -44,6 +45,9 @@ import androidx.credentials.CreateCredentialRequest import androidx.credentials.CreateCustomCredentialRequest import androidx.credentials.CreatePasswordRequest import androidx.credentials.CreatePublicKeyCredentialRequest +import androidx.credentials.PasswordCredential +import androidx.credentials.PriorityHints +import androidx.credentials.PublicKeyCredential import androidx.credentials.provider.CreateEntry import androidx.credentials.provider.RemoteEntry import org.json.JSONObject @@ -162,6 +166,25 @@ private fun getPackageInfo( /** Utility functions for converting CredentialManager data structures to or from UI formats. */ class GetFlowUtils { companion object { + fun extractTypePriorityMap(request: GetCredentialRequest): Map<String, Int> { + val typePriorityMap = mutableMapOf<String, Int>() + request.credentialOptions.forEach {option -> + // TODO(b/280085288) - use jetpack conversion method when exposed, rather than + // parsing from the raw Bundle + val priority = option.candidateQueryData.getInt( + "androidx.credentials.BUNDLE_KEY_TYPE_PRIORITY_VALUE", + when (option.type) { + PasswordCredential.TYPE_PASSWORD_CREDENTIAL -> + PriorityHints.PRIORITY_PASSWORD_OR_SIMILAR + PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL -> 100 + else -> PriorityHints.PRIORITY_DEFAULT + } + ) + typePriorityMap[option.type] = priority + } + return typePriorityMap + } + // Returns the list (potentially empty) of enabled provider. fun toProviderList( providerDataList: List<GetCredentialProviderData>, @@ -193,6 +216,9 @@ class GetFlowUtils { null } } + + val typePriorityMap = extractTypePriorityMap(getCredentialRequest) + return com.android.credentialmanager.getflow.RequestDisplayInfo( appName = originName?.ifEmpty { null } ?: getAppLabel(context.packageManager, requestInfo.packageName) @@ -203,6 +229,7 @@ class GetFlowUtils { // exposed. "androidx.credentials.BUNDLE_KEY_PREFER_IDENTITY_DOC_UI"), preferTopBrandingContent = preferTopBrandingContent, + typePriorityMap = typePriorityMap, ) } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt index 293e1112636e..4e1f4ee2e565 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt @@ -33,7 +33,6 @@ import android.graphics.drawable.Icon import android.os.Bundle import android.os.CancellationSignal import android.os.OutcomeReceiver -import android.provider.Settings import android.service.autofill.AutofillService import android.service.autofill.Dataset import android.service.autofill.Field @@ -140,7 +139,7 @@ class CredentialAutofillService : AutofillService() { override fun onResult(result: GetCandidateCredentialsResponse) { Log.i(TAG, "getCandidateCredentials onResult") val fillResponse = convertToFillResponse(result, request, - responseClientState) + responseClientState, GetFlowUtils.extractTypePriorityMap(getCredRequest)) if (fillResponse != null) { callback.onSuccess(fillResponse) } else { @@ -197,7 +196,8 @@ class CredentialAutofillService : AutofillService() { private fun convertToFillResponse( getCredResponse: GetCandidateCredentialsResponse, filLRequest: FillRequest, - responseClientState: Bundle + responseClientState: Bundle, + typePriorityMap: Map<String, Int>, ): FillResponse? { val candidateProviders = getCredResponse.candidateProviderDataList if (candidateProviders.isEmpty()) { @@ -213,7 +213,7 @@ class CredentialAutofillService : AutofillService() { autofillIdToProvidersMap.forEach { (autofillId, providers) -> validFillResponse = processProvidersForAutofillId( filLRequest, autofillId, providers, entryIconMap, fillResponseBuilder, - getCredResponse.intent) + getCredResponse.intent, typePriorityMap) .or(validFillResponse) } if (!validFillResponse) { @@ -229,7 +229,8 @@ class CredentialAutofillService : AutofillService() { providerDataList: ArrayList<GetCredentialProviderData>, entryIconMap: Map<String, Icon>, fillResponseBuilder: FillResponse.Builder, - bottomSheetIntent: Intent + bottomSheetIntent: Intent, + typePriorityMap: Map<String, Int>, ): Boolean { val providerList = GetFlowUtils.toProviderList( providerDataList, @@ -237,7 +238,8 @@ class CredentialAutofillService : AutofillService() { if (providerList.isEmpty()) { return false } - val providerDisplayInfo: ProviderDisplayInfo = toProviderDisplayInfo(providerList) + val providerDisplayInfo: ProviderDisplayInfo = + toProviderDisplayInfo(providerList, typePriorityMap) var totalEntryCount = providerDisplayInfo.sortedUserNameToCredentialEntryList.size val inlineSuggestionsRequest = filLRequest.inlineSuggestionsRequest val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt index 8ff17e0d333a..965ee860672e 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt @@ -78,6 +78,8 @@ fun Entry( isLockedAuthEntry: Boolean = false, enforceOneLine: Boolean = false, onTextLayout: (TextLayoutResult) -> Unit = {}, + /** Get flow only, if present, where be drawn as a line above the headline. */ + affiliatedDomainText: String? = null, ) { val iconPadding = Modifier.wrapContentSize().padding( // Horizontal padding should be 16dp, but the suggestion chip itself @@ -102,6 +104,13 @@ fun Entry( ) { // Apply weight so that the trailing icon can always show. Column(modifier = Modifier.wrapContentHeight().fillMaxWidth().weight(1f)) { + if (!affiliatedDomainText.isNullOrBlank()) { + BodySmallText( + text = affiliatedDomainText, + enforceOneLine = enforceOneLine, + onTextLayout = onTextLayout, + ) + } SmallTitleText( text = entryHeadlineText, enforceOneLine = enforceOneLine, @@ -143,14 +152,14 @@ fun Entry( }, ) } - } else if (entrySecondLineText != null) { + } else if (!entrySecondLineText.isNullOrBlank()) { BodySmallText( text = entrySecondLineText, enforceOneLine = enforceOneLine, onTextLayout = onTextLayout, ) } - if (entryThirdLineText != null) { + if (!entryThirdLineText.isNullOrBlank()) { BodySmallText( text = entryThirdLineText, enforceOneLine = enforceOneLine, diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt index 02afc547e3f8..7966a86903f3 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt @@ -34,9 +34,9 @@ class RemoteViewsFactory { private const val passwordCharacterLength = 15 fun createDropdownPresentation( - context: Context, - icon: Icon, - credentialEntryInfo: CredentialEntryInfo + context: Context, + icon: Icon, + credentialEntryInfo: CredentialEntryInfo ): RemoteViews { var layoutId: Int = com.android.credentialmanager.R.layout .credman_dropdown_presentation_layout @@ -45,41 +45,37 @@ class RemoteViewsFactory { return remoteViews } setRemoteViewsPaddings(remoteViews, context, /* primaryTextBottomPadding=*/0) - if (credentialEntryInfo.credentialType == CredentialType.PASSKEY) { - val displayName = credentialEntryInfo.displayName ?: credentialEntryInfo.userName - remoteViews.setTextViewText(android.R.id.text1, displayName) - val secondaryText = if (credentialEntryInfo.displayName != null) + val displayName = credentialEntryInfo.displayName ?: credentialEntryInfo.userName + remoteViews.setTextViewText(android.R.id.text1, displayName) + val secondaryText = + if (credentialEntryInfo.displayName != null + && (credentialEntryInfo.displayName != credentialEntryInfo.userName)) (credentialEntryInfo.userName + " " + bulletPoint + " " + credentialEntryInfo.credentialTypeDisplayName + " " + bulletPoint + " " + credentialEntryInfo.providerDisplayName) else (credentialEntryInfo.credentialTypeDisplayName + " " + bulletPoint + " " + credentialEntryInfo.providerDisplayName) - remoteViews.setTextViewText(android.R.id.text2, secondaryText) - } else { - remoteViews.setTextViewText(android.R.id.text1, credentialEntryInfo.userName) - remoteViews.setTextViewText(android.R.id.text2, - bulletPoint.repeat(passwordCharacterLength)) - } + remoteViews.setTextViewText(android.R.id.text2, secondaryText) val textColorPrimary = ContextCompat.getColor(context, - com.android.credentialmanager.R.color.text_primary) + com.android.credentialmanager.R.color.text_primary) remoteViews.setTextColor(android.R.id.text1, textColorPrimary) val textColorSecondary = ContextCompat.getColor(context, com.android .credentialmanager.R.color.text_secondary) remoteViews.setTextColor(android.R.id.text2, textColorSecondary) remoteViews.setImageViewIcon(android.R.id.icon1, icon); remoteViews.setBoolean( - android.R.id.icon1, setAdjustViewBoundsMethodName, true); + android.R.id.icon1, setAdjustViewBoundsMethodName, true); remoteViews.setInt( - android.R.id.icon1, - setMaxHeightMethodName, - context.resources.getDimensionPixelSize( - com.android.credentialmanager.R.dimen.autofill_icon_size)); + android.R.id.icon1, + setMaxHeightMethodName, + context.resources.getDimensionPixelSize( + com.android.credentialmanager.R.dimen.autofill_icon_size)); remoteViews.setContentDescription(android.R.id.icon1, credentialEntryInfo .providerDisplayName); val drawableId = - com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one + com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one remoteViews.setInt( - android.R.id.content, setBackgroundResourceMethodName, drawableId); + android.R.id.content, setBackgroundResourceMethodName, drawableId); return remoteViews } @@ -89,68 +85,68 @@ class RemoteViewsFactory { val remoteViews = RemoteViews(context.packageName, layoutId) setRemoteViewsPaddings(remoteViews, context) remoteViews.setTextViewText(android.R.id.text1, ContextCompat.getString(context, - com.android.credentialmanager - .R.string.dropdown_presentation_more_sign_in_options_text)) + com.android.credentialmanager + .R.string.dropdown_presentation_more_sign_in_options_text)) val textColorPrimary = ContextCompat.getColor(context, - com.android.credentialmanager.R.color.text_primary) + com.android.credentialmanager.R.color.text_primary) remoteViews.setTextColor(android.R.id.text1, textColorPrimary) val icon = Icon.createWithResource(context, com .android.credentialmanager.R.drawable.more_horiz_24px) icon.setTint(ContextCompat.getColor(context, - com.android.credentialmanager.R.color.sign_in_options_icon_color)) + com.android.credentialmanager.R.color.sign_in_options_icon_color)) remoteViews.setImageViewIcon(android.R.id.icon1, icon) remoteViews.setBoolean( - android.R.id.icon1, setAdjustViewBoundsMethodName, true); + android.R.id.icon1, setAdjustViewBoundsMethodName, true); remoteViews.setInt( - android.R.id.icon1, - setMaxHeightMethodName, - context.resources.getDimensionPixelSize( - com.android.credentialmanager.R.dimen.autofill_icon_size)); + android.R.id.icon1, + setMaxHeightMethodName, + context.resources.getDimensionPixelSize( + com.android.credentialmanager.R.dimen.autofill_icon_size)); val drawableId = - com.android.credentialmanager.R.drawable.more_options_list_item + com.android.credentialmanager.R.drawable.more_options_list_item remoteViews.setInt( - android.R.id.content, setBackgroundResourceMethodName, drawableId); + android.R.id.content, setBackgroundResourceMethodName, drawableId); return remoteViews } private fun setRemoteViewsPaddings( - remoteViews: RemoteViews, context: Context) { + remoteViews: RemoteViews, context: Context) { val bottomPadding = context.resources.getDimensionPixelSize( - com.android.credentialmanager.R.dimen.autofill_view_bottom_padding) + com.android.credentialmanager.R.dimen.autofill_view_bottom_padding) setRemoteViewsPaddings(remoteViews, context, bottomPadding) } private fun setRemoteViewsPaddings( - remoteViews: RemoteViews, context: Context, primaryTextBottomPadding: Int) { + remoteViews: RemoteViews, context: Context, primaryTextBottomPadding: Int) { val leftPadding = context.resources.getDimensionPixelSize( - com.android.credentialmanager.R.dimen.autofill_view_left_padding) + com.android.credentialmanager.R.dimen.autofill_view_left_padding) val iconToTextPadding = context.resources.getDimensionPixelSize( - com.android.credentialmanager.R.dimen.autofill_view_icon_to_text_padding) + com.android.credentialmanager.R.dimen.autofill_view_icon_to_text_padding) val rightPadding = context.resources.getDimensionPixelSize( - com.android.credentialmanager.R.dimen.autofill_view_right_padding) + com.android.credentialmanager.R.dimen.autofill_view_right_padding) val topPadding = context.resources.getDimensionPixelSize( - com.android.credentialmanager.R.dimen.autofill_view_top_padding) + com.android.credentialmanager.R.dimen.autofill_view_top_padding) val bottomPadding = context.resources.getDimensionPixelSize( - com.android.credentialmanager.R.dimen.autofill_view_bottom_padding) + com.android.credentialmanager.R.dimen.autofill_view_bottom_padding) remoteViews.setViewPadding( - android.R.id.icon1, - leftPadding, - /* top=*/0, - /* right=*/0, - /* bottom=*/0) + android.R.id.icon1, + leftPadding, + /* top=*/0, + /* right=*/0, + /* bottom=*/0) remoteViews.setViewPadding( - android.R.id.text1, - iconToTextPadding, - /* top=*/topPadding, - /* right=*/rightPadding, - primaryTextBottomPadding) + android.R.id.text1, + iconToTextPadding, + /* top=*/topPadding, + /* right=*/rightPadding, + primaryTextBottomPadding) remoteViews.setViewPadding( - android.R.id.text2, - iconToTextPadding, - /* top=*/0, - /* right=*/rightPadding, - /* bottom=*/bottomPadding) + android.R.id.text2, + iconToTextPadding, + /* top=*/0, + /* right=*/rightPadding, + /* bottom=*/bottomPadding) } private fun isDarkMode(context: Context): Boolean { diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt index 4ef776099119..af78573ee9e9 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt @@ -336,7 +336,7 @@ fun CreationSelectionCard( if (!footerDescription.isNullOrBlank()) { item { Row(modifier = Modifier.fillMaxWidth().wrapContentHeight()) { - BodySmallText(text = footerDescription) + BodyMediumText(text = footerDescription) } } item { Divider(thickness = 24.dp, color = Color.Transparent) } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt index 660db7007224..bc0ea02d5b15 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt @@ -41,6 +41,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextLayoutResult @@ -375,6 +376,7 @@ fun PrimarySelectionCard( } internal const val MAX_ENTRY_FOR_PRIMARY_PAGE = 4 + /** Draws the primary credential selection page, used starting from android V. */ @Composable fun PrimarySelectionCardVImpl( @@ -399,6 +401,10 @@ fun PrimarySelectionCardVImpl( ) SheetContainerCard { val preferTopBrandingContent = requestDisplayInfo.preferTopBrandingContent + val singleProviderId = findSingleProviderIdForPrimaryPage( + primaryPageCredentialEntryList, + primaryPageLockedEntryList + ) if (preferTopBrandingContent != null) { item { HeadlineProviderIconAndName( @@ -409,10 +415,6 @@ fun PrimarySelectionCardVImpl( } else { // When only one provider's entries will be displayed on the primary page, display that // provider's icon + name up top. - val singleProviderId = findSingleProviderIdForPrimaryPage( - primaryPageCredentialEntryList, - primaryPageLockedEntryList - ) if (singleProviderId != null) { // First should always work but just to be safe. val providerInfo = providerInfoList.firstOrNull { it.id == singleProviderId } @@ -479,14 +481,17 @@ fun PrimarySelectionCardVImpl( CredentialContainerCard { Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { primaryPageCredentialEntryList.forEach { + val entry = it.sortedCredentialEntryList.first() CredentialEntryRow( - credentialEntryInfo = it.sortedCredentialEntryList.first(), + credentialEntryInfo = entry, onEntrySelected = onEntrySelected, enforceOneLine = true, onTextLayout = { showMoreForTruncatedEntry.value = it.hasVisualOverflow }, hasSingleEntry = hasSingleEntry, + shouldOverrideIcon = entry.isDefaultIconPreferredAsSingleProvider && + (singleProviderId != null), ) } primaryPageLockedEntryList.forEach { @@ -750,30 +755,47 @@ fun CredentialEntryRow( onTextLayout: (TextLayoutResult) -> Unit = {}, // Make optional since the secondary page doesn't care about this value. hasSingleEntry: Boolean? = null, + // For primary page only, if all display entries come from the same provider AND if that + // provider has opted in via isDefaultIconPreferredAsSingleProvider, then we override the + // display icon to the default icon for the given credential type. + shouldOverrideIcon: Boolean = false, ) { val (username, displayName) = if (credentialEntryInfo.credentialType == CredentialType.PASSKEY) userAndDisplayNameForPasskey( credentialEntryInfo.userName, credentialEntryInfo.displayName ?: "") else Pair(credentialEntryInfo.userName, credentialEntryInfo.displayName) + + // For primary page, if + val overrideIcon: Painter? = + if (shouldOverrideIcon) { + when (credentialEntryInfo.credentialType) { + CredentialType.PASSKEY -> painterResource(R.drawable.ic_passkey_24) + CredentialType.PASSWORD -> painterResource(R.drawable.ic_password_24) + else -> painterResource(R.drawable.ic_other_sign_in_24) + } + } else null + Entry( onClick = { onEntrySelected(credentialEntryInfo) }, - iconImageBitmap = credentialEntryInfo.icon?.toBitmap()?.asImageBitmap(), + iconImageBitmap = + if (overrideIcon == null) credentialEntryInfo.icon?.toBitmap()?.asImageBitmap() else null, shouldApplyIconImageBitmapTint = credentialEntryInfo.shouldTintIcon, // Fall back to iconPainter if iconImageBitmap isn't available iconPainter = - if (credentialEntryInfo.icon == null) painterResource(R.drawable.ic_other_sign_in_24) + if (overrideIcon != null) overrideIcon + else if (credentialEntryInfo.icon == null) painterResource(R.drawable.ic_other_sign_in_24) else null, entryHeadlineText = username, - entrySecondLineText = + entrySecondLineText = displayName, + entryThirdLineText = (if (hasSingleEntry != null && hasSingleEntry) if (credentialEntryInfo.credentialType == CredentialType.PASSKEY || credentialEntryInfo.credentialType == CredentialType.PASSWORD) - listOf(displayName) + emptyList() // Still show the type display name for all non-password/passkey types since it won't be // mentioned in the bottom sheet heading. - else listOf(displayName, credentialEntryInfo.credentialTypeDisplayName) + else listOf(credentialEntryInfo.credentialTypeDisplayName) else listOf( - displayName, credentialEntryInfo.credentialTypeDisplayName, credentialEntryInfo.providerDisplayName )).filterNot(TextUtils::isEmpty).let { itemsToDisplay -> @@ -784,6 +806,7 @@ fun CredentialEntryRow( }, enforceOneLine = enforceOneLine, onTextLayout = onTextLayout, + affiliatedDomainText = credentialEntryInfo.affiliatedDomain, ) } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt index e7f11a15a06c..e35acae547a6 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt @@ -18,19 +18,21 @@ package com.android.credentialmanager.getflow import android.credentials.flags.Flags.selectorUiImprovementsEnabled import android.graphics.drawable.Drawable +import androidx.credentials.PriorityHints import com.android.credentialmanager.model.get.ProviderInfo import com.android.credentialmanager.model.EntryInfo -import com.android.credentialmanager.model.CredentialType import com.android.credentialmanager.model.get.AuthenticationEntryInfo import com.android.credentialmanager.model.get.CredentialEntryInfo import com.android.credentialmanager.model.get.RemoteEntryInfo import com.android.internal.util.Preconditions +import java.time.Instant data class GetCredentialUiState( val isRequestForAllOptions: Boolean, val providerInfoList: List<ProviderInfo>, val requestDisplayInfo: RequestDisplayInfo, - val providerDisplayInfo: ProviderDisplayInfo = toProviderDisplayInfo(providerInfoList), + val providerDisplayInfo: ProviderDisplayInfo = + toProviderDisplayInfo(providerInfoList, requestDisplayInfo.typePriorityMap), val currentScreenState: GetScreenState = toGetScreenState( providerDisplayInfo, isRequestForAllOptions), val activeEntry: EntryInfo? = toActiveEntry(providerDisplayInfo), @@ -79,6 +81,8 @@ data class RequestDisplayInfo( val preferIdentityDocUi: Boolean, // A top level branding icon + display name preferred by the app. val preferTopBrandingContent: TopBrandingContent?, + // Map of credential type -> priority. + val typePriorityMap: Map<String, Int>, ) data class TopBrandingContent( @@ -119,7 +123,8 @@ enum class GetScreenState { * @hide */ fun toProviderDisplayInfo( - providerInfoList: List<ProviderInfo> + providerInfoList: List<ProviderInfo>, + typePriorityMap: Map<String, Int>, ): ProviderDisplayInfo { val userNameToCredentialEntryMap = mutableMapOf<String, MutableList<CredentialEntryInfo>>() val authenticationEntryList = mutableListOf<AuthenticationEntryInfo>() @@ -147,16 +152,22 @@ fun toProviderDisplayInfo( } // Compose sortedUserNameToCredentialEntryList - val comparator = CredentialEntryInfoComparatorByTypeThenTimestamp() + val comparator = CredentialEntryInfoComparatorByTypeThenTimestamp(typePriorityMap) // Sort per username userNameToCredentialEntryMap.values.forEach { it.sortWith(comparator) } - // Transform to list of PerUserNameCredentialEntryLists and then sort across usernames + // Transform to list of PerUserNameCredentialEntryLists and then sort the outer list (of + // entries grouped by username / entryGroupId) based on the latest timestamp within that + // PerUserNameCredentialEntryList val sortedUserNameToCredentialEntryList = userNameToCredentialEntryMap.map { PerUserNameCredentialEntryList(it.key, it.value) }.sortedWith( - compareByDescending { it.sortedCredentialEntryList.first().lastUsedTimeMillis } + compareByDescending { + it.sortedCredentialEntryList.maxByOrNull{ entry -> + entry.lastUsedTimeMillis ?: Instant.MIN + }?.lastUsedTimeMillis ?: Instant.MIN + } ) return ProviderDisplayInfo( @@ -203,16 +214,25 @@ private fun toGetScreenState( else GetScreenState.PRIMARY_SELECTION } -internal class CredentialEntryInfoComparatorByTypeThenTimestamp : Comparator<CredentialEntryInfo> { +internal class CredentialEntryInfoComparatorByTypeThenTimestamp( + val typePriorityMap: Map<String, Int>, +) : Comparator<CredentialEntryInfo> { override fun compare(p0: CredentialEntryInfo, p1: CredentialEntryInfo): Int { - // First prefer passkey type for its security benefits - if (p0.credentialType != p1.credentialType) { - if (CredentialType.PASSKEY == p0.credentialType) { + // First rank by priorities of each credential type. + if (p0.rawCredentialType != p1.rawCredentialType) { + val p0Priority = typePriorityMap.getOrDefault( + p0.rawCredentialType, PriorityHints.PRIORITY_DEFAULT + ) + val p1Priority = typePriorityMap.getOrDefault( + p1.rawCredentialType, PriorityHints.PRIORITY_DEFAULT + ) + if (p0Priority < p1Priority) { return -1 - } else if (CredentialType.PASSKEY == p1.credentialType) { + } else if (p1Priority < p0Priority) { return 1 } } + // Then rank by last used timestamps. val p0LastUsedTimeMillis = p0.lastUsedTimeMillis val p1LastUsedTimeMillis = p1.lastUsedTimeMillis // Then order by last used timestamp diff --git a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt index d9ba36e1cdfc..28d83ee157b6 100644 --- a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt +++ b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt @@ -53,6 +53,7 @@ class GetCredScreenshotTest(emulationSpec: DeviceEmulationSpec) { preferImmediatelyAvailableCredentials = false, preferIdentityDocUi = false, preferTopBrandingContent = null, + typePriorityMap = emptyMap(), ) } @@ -68,7 +69,7 @@ class GetCredScreenshotTest(emulationSpec: DeviceEmulationSpec) { fun singleCredentialScreen_M3BottomSheetDisabled() { setFlagsRule.disableFlags(Flags.FLAG_SELECTOR_UI_IMPROVEMENTS_ENABLED) val providerInfoList = buildProviderInfoList() - val providerDisplayInfo = toProviderDisplayInfo(providerInfoList) + val providerDisplayInfo = toProviderDisplayInfo(providerInfoList, emptyMap()) val activeEntry = toActiveEntry(providerDisplayInfo) screenshotRule.screenshotTest("singleCredentialScreen") { ModalBottomSheet( @@ -96,7 +97,7 @@ class GetCredScreenshotTest(emulationSpec: DeviceEmulationSpec) { fun singleCredentialScreen_M3BottomSheetEnabled() { setFlagsRule.enableFlags(Flags.FLAG_SELECTOR_UI_IMPROVEMENTS_ENABLED) val providerInfoList = buildProviderInfoList() - val providerDisplayInfo = toProviderDisplayInfo(providerInfoList) + val providerDisplayInfo = toProviderDisplayInfo(providerInfoList, emptyMap()) val activeEntry = toActiveEntry(providerDisplayInfo) screenshotRule.screenshotTest( "singleCredentialScreen_newM3BottomSheet", @@ -148,6 +149,9 @@ class GetCredScreenshotTest(emulationSpec: DeviceEmulationSpec) { lastUsedTimeMillis = null, isAutoSelectable = false, entryGroupId = "username", + isDefaultIconPreferredAsSingleProvider = false, + rawCredentialType = "unknown-type", + affiliatedDomain = null, ) ), authenticationEntryList = emptyList(), diff --git a/packages/PackageInstaller/res/values-el/strings.xml b/packages/PackageInstaller/res/values-el/strings.xml index 61aa4e65128a..3c37df68456c 100644 --- a/packages/PackageInstaller/res/values-el/strings.xml +++ b/packages/PackageInstaller/res/values-el/strings.xml @@ -117,7 +117,7 @@ <string name="unarchive_error_generic_title" msgid="7123457671482449992">"Κάτι πήγε στραβά"</string> <string name="unarchive_error_generic_body" msgid="4486803312463813079">"Παρουσιάστηκε κάποιο πρόβλημα κατά την επαναφορά αυτής της εφαρμογής"</string> <string name="unarchive_error_storage_title" msgid="5080723357273852630">"Δεν επαρκεί ο αποθηκευτικός χώρος"</string> - <string name="unarchive_error_storage_body" msgid="6879544407568780524">"Για να επαναφέρετε αυτή την εφαρμογή, μπορείτε να ελευθερώσετε χώρο στη συσκευή. Απαιτούμενος αποθηκευτικός χώρος: <xliff:g id="BYTES">%1$s</xliff:g>"</string> + <string name="unarchive_error_storage_body" msgid="6879544407568780524">"Για να επαναφέρετε αυτή την εφαρμογή, μπορείτε να αποδεσμεύσετε χώρο στη συσκευή. Απαιτούμενος αποθηκευτικός χώρος: <xliff:g id="BYTES">%1$s</xliff:g>"</string> <string name="unarchive_action_required_title" msgid="4971245740162604619">"Απαιτούμενη ενέργεια"</string> <string name="unarchive_action_required_body" msgid="1679431572983989231">"Ακολουθήστε τα επόμενα βήματα για να επαναφέρετε αυτή την εφαρμογή"</string> <string name="unarchive_error_installer_disabled_title" msgid="4815715617014985605">"Το <xliff:g id="INSTALLERNAME">%1$s</xliff:g> είναι απενεργοποιημένο"</string> diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java index 634e067a12d4..cf2f85ed5356 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java @@ -20,7 +20,6 @@ import static android.content.res.AssetFileDescriptor.UNKNOWN_LENGTH; import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID; -import android.Manifest; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; @@ -28,10 +27,10 @@ import android.app.DialogFragment; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; -import android.content.pm.Flags; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.res.AssetFileDescriptor; +import android.Manifest; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; @@ -201,7 +200,7 @@ public class InstallStaging extends Activity { params.setPermissionState(Manifest.permission.USE_FULL_SCREEN_INTENT, PackageInstaller.SessionParams.PERMISSION_STATE_DENIED); - if (pfd != null && Flags.readInstallInfo()) { + if (pfd != null) { try { final PackageInstaller.InstallInfo result = installer.readInstallInfo(pfd, debugPathName, 0); diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java index e95a8e63d644..45bfe5469172 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java @@ -31,7 +31,6 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.ApplicationInfo; -import android.content.pm.Flags; import android.content.pm.InstallSourceInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; @@ -400,10 +399,7 @@ public class PackageInstallerActivity extends Activity { final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1 /* defaultValue */); final SessionInfo info = mInstaller.getSessionInfo(sessionId); - String resolvedPath = null; - if (info != null && Flags.getResolvedApkPath()) { - resolvedPath = info.getResolvedBaseApkPath(); - } + String resolvedPath = info != null ? info.getResolvedBaseApkPath() : null; if (info == null || !info.isSealed() || resolvedPath == null) { Log.w(TAG, "Session " + sessionId + " in funky state; ignoring"); finish(); 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 22caabdabbf0..aeabbd53d177 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt @@ -25,7 +25,6 @@ import android.content.ContentResolver import android.content.Context import android.content.Intent import android.content.pm.ApplicationInfo -import android.content.pm.Flags import android.content.pm.PackageInfo import android.content.pm.PackageInstaller import android.content.pm.PackageInstaller.SessionInfo @@ -363,7 +362,7 @@ class InstallRepository(private val context: Context) { params.setPermissionState( Manifest.permission.USE_FULL_SCREEN_INTENT, SessionParams.PERMISSION_STATE_DENIED ) - if (pfd != null && Flags.readInstallInfo()) { + if (pfd != null) { try { val installInfo = packageInstaller.readInstallInfo(pfd, debugPathName, 0) params.setAppPackageName(installInfo.packageName) @@ -426,8 +425,7 @@ class InstallRepository(private val context: Context) { if (PackageInstaller.ACTION_CONFIRM_INSTALL == intent.action) { val info = packageInstaller.getSessionInfo(sessionId) - val resolvedPath = - if (Flags.getResolvedApkPath()) info?.resolvedBaseApkPath else null + val resolvedPath = info?.resolvedBaseApkPath if (info == null || !info.isSealed || resolvedPath == null) { Log.w(LOG_TAG, "Session $sessionId in funky state; ignoring") return InstallAborted(ABORT_REASON_INTERNAL_ERROR) diff --git a/packages/SettingsLib/DataStore/Android.bp b/packages/SettingsLib/DataStore/Android.bp new file mode 100644 index 000000000000..868a4a50577e --- /dev/null +++ b/packages/SettingsLib/DataStore/Android.bp @@ -0,0 +1,16 @@ +package { + default_applicable_licenses: ["frameworks_base_license"], +} + +android_library { + name: "SettingsLibDataStore", + defaults: [ + "SettingsLintDefaults", + ], + srcs: ["src/**/*"], + static_libs: [ + "androidx.annotation_annotation", + "androidx.collection_collection-ktx", + "guava", + ], +} diff --git a/packages/SettingsLib/DataStore/AndroidManifest.xml b/packages/SettingsLib/DataStore/AndroidManifest.xml new file mode 100644 index 000000000000..fb446279e240 --- /dev/null +++ b/packages/SettingsLib/DataStore/AndroidManifest.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.settingslib.datastore"> + + <uses-sdk android:minSdkVersion="21" /> +</manifest> diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreContext.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreContext.kt new file mode 100644 index 000000000000..c6d6f772c5df --- /dev/null +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreContext.kt @@ -0,0 +1,72 @@ +/* + * 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.datastore + +import android.app.backup.BackupAgent +import android.app.backup.BackupDataOutput +import android.app.backup.BackupHelper +import android.os.Build +import android.os.ParcelFileDescriptor +import androidx.annotation.RequiresApi + +/** + * Context for backup. + * + * @see BackupHelper.performBackup + * @see BackupDataOutput + */ +class BackupContext +internal constructor( + /** + * An open, read-only file descriptor pointing to the last backup state provided by the + * application. May be null, in which case no prior state is being provided and the application + * should perform a full backup. + * + * TODO: the state should support marshall/unmarshall for incremental back up. + */ + val oldState: ParcelFileDescriptor?, + + /** An open, read/write BackupDataOutput pointing to the backup data destination. */ + private val data: BackupDataOutput, + + /** + * An open, read/write file descriptor pointing to an empty file. The application should record + * the final backup. + */ + val newState: ParcelFileDescriptor, +) { + /** + * The quota in bytes for the application's current backup operation. + * + * @see [BackupDataOutput.getQuota] + */ + val quota: Long + @RequiresApi(Build.VERSION_CODES.O) get() = data.quota + + /** + * Additional information about the backup transport. + * + * See [BackupAgent] for supported flags. + * + * @see [BackupDataOutput.getTransportFlags] + */ + val transportFlags: Int + @RequiresApi(Build.VERSION_CODES.P) get() = data.transportFlags +} + +/** Context for restore. */ +class RestoreContext(val key: String) diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt new file mode 100644 index 000000000000..6a7ef5a35ac8 --- /dev/null +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.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.settingslib.datastore + +import android.app.backup.BackupDataOutput +import android.app.backup.BackupHelper +import androidx.annotation.BinderThread +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream + +/** Entity for back up and restore. */ +interface BackupRestoreEntity { + /** + * Key of the entity. + * + * The key string must be unique within the data set. Note that it is invalid if the first + * character is \uFF00 or higher. + * + * @see BackupDataOutput.writeEntityHeader + */ + val key: String + + /** + * Backs up the entity. + * + * @param backupContext context for backup + * @param outputStream output stream to back up data + * @return false if backup file is deleted, otherwise true + */ + @BinderThread + @Throws(IOException::class) + fun backup(backupContext: BackupContext, outputStream: OutputStream): EntityBackupResult + + /** + * Restores the entity. + * + * @param restoreContext context for restore + * @param inputStream An open input stream from which the backup data can be read. + * @see BackupHelper.restoreEntity + */ + @BinderThread + @Throws(IOException::class) + fun restore(restoreContext: RestoreContext, inputStream: InputStream) +} + +/** Result of the backup operation. */ +enum class EntityBackupResult { + /** Update the entity. */ + UPDATE, + /** Leave the entity intact. */ + INTACT, + /** Delete the entity. */ + DELETE, +} diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt new file mode 100644 index 000000000000..88d9dd6400ee --- /dev/null +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt @@ -0,0 +1,140 @@ +/* + * 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.datastore + +import android.app.backup.BackupAgentHelper +import android.app.backup.BackupDataInputStream +import android.app.backup.BackupDataOutput +import android.app.backup.BackupHelper +import android.os.ParcelFileDescriptor +import android.util.Log +import com.google.common.io.ByteStreams +import java.io.ByteArrayOutputStream +import java.io.FilterInputStream +import java.io.InputStream +import java.io.OutputStream + +internal const val LOG_TAG = "BackupRestoreStorage" + +/** + * Storage with backup and restore support. Subclass must implement either [Observable] or + * [KeyedObservable] interface. + * + * The storage is identified by a unique string [name] and data set is split into entities + * ([BackupRestoreEntity]). + */ +abstract class BackupRestoreStorage : BackupHelper { + /** + * A unique string used to disambiguate the various storages within backup agent. + * + * It will be used as the `keyPrefix` of [BackupAgentHelper.addHelper]. + */ + abstract val name: String + + private val entities: List<BackupRestoreEntity> by lazy { createBackupRestoreEntities() } + + /** Entities to back up and restore. */ + abstract fun createBackupRestoreEntities(): List<BackupRestoreEntity> + + override fun performBackup( + oldState: ParcelFileDescriptor?, + data: BackupDataOutput, + newState: ParcelFileDescriptor, + ) { + val backupContext = BackupContext(oldState, data, newState) + if (!enableBackup(backupContext)) { + Log.i(LOG_TAG, "[$name] Backup disabled") + return + } + Log.i(LOG_TAG, "[$name] Backup start") + for (entity in entities) { + val key = entity.key + val outputStream = ByteArrayOutputStream() + val result = + try { + entity.backup(backupContext, wrapBackupOutputStream(outputStream)) + } catch (exception: Exception) { + Log.e(LOG_TAG, "[$name] Fail to backup entity $key", exception) + continue + } + when (result) { + EntityBackupResult.UPDATE -> { + val payload = outputStream.toByteArray() + val size = payload.size + data.writeEntityHeader(key, size) + data.writeEntityData(payload, size) + Log.i(LOG_TAG, "[$name] Backup entity $key: $size bytes") + } + EntityBackupResult.INTACT -> { + Log.i(LOG_TAG, "[$name] Backup entity $key intact") + } + EntityBackupResult.DELETE -> { + data.writeEntityHeader(key, -1) + Log.i(LOG_TAG, "[$name] Backup entity $key deleted") + } + } + } + Log.i(LOG_TAG, "[$name] Backup end") + } + + /** Returns if backup is enabled. */ + open fun enableBackup(backupContext: BackupContext): Boolean = true + + fun wrapBackupOutputStream(outputStream: OutputStream): OutputStream { + return outputStream + } + + override fun restoreEntity(data: BackupDataInputStream) { + val key = data.key + if (!enableRestore()) { + Log.i(LOG_TAG, "[$name] Restore disabled, ignore entity $key") + return + } + val entity = entities.firstOrNull { it.key == key } + if (entity == null) { + Log.w(LOG_TAG, "[$name] Cannot find handler for entity $key") + return + } + Log.i(LOG_TAG, "[$name] Restore $key: ${data.size()} bytes") + val restoreContext = RestoreContext(key) + try { + entity.restore(restoreContext, wrapRestoreInputStream(data)) + } catch (exception: Exception) { + Log.e(LOG_TAG, "[$name] Fail to restore entity $key", exception) + } + } + + /** Returns if restore is enabled. */ + open fun enableRestore(): Boolean = true + + fun wrapRestoreInputStream(inputStream: BackupDataInputStream): InputStream { + return LimitedNoCloseInputStream(inputStream) + } + + override fun writeNewStateDescription(newState: ParcelFileDescriptor) {} +} + +/** + * Wrapper of [BackupDataInputStream], limiting the number of bytes that can be read and make + * [close] no-op. + */ +class LimitedNoCloseInputStream(inputStream: BackupDataInputStream) : + FilterInputStream(ByteStreams.limit(inputStream, inputStream.size().toLong())) { + override fun close() { + // do not close original input stream + } +} diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt new file mode 100644 index 000000000000..221e2e89c33d --- /dev/null +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt @@ -0,0 +1,153 @@ +/* + * 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.datastore + +import android.app.Application +import android.app.backup.BackupAgentHelper +import android.app.backup.BackupManager +import android.content.Context +import android.util.Log +import com.google.common.util.concurrent.MoreExecutors +import java.util.concurrent.ConcurrentHashMap + +/** Manager of [BackupRestoreStorage]. */ +class BackupRestoreStorageManager private constructor(private val application: Application) { + private val storages = ConcurrentHashMap<String, BackupRestoreStorage>() + + private val executor = MoreExecutors.directExecutor() + + private val observer = Observer { reason -> notifyBackupManager(null, reason) } + + private val keyedObserver = + KeyedObserver<Any?> { key, reason -> notifyBackupManager(key, reason) } + + private fun notifyBackupManager(key: Any?, reason: Int) { + // prefer not triggering backup immediately after restore + if (reason == ChangeReason.RESTORE) return + // TODO: log storage name + Log.d(LOG_TAG, "Notify BackupManager data changed for change: key=$key") + BackupManager.dataChanged(application.packageName) + } + + /** + * Adds all the registered [BackupRestoreStorage] as the helpers of given [BackupAgentHelper]. + * + * @see BackupAgentHelper.addHelper + */ + fun addBackupAgentHelpers(backupAgentHelper: BackupAgentHelper) { + for ((keyPrefix, storage) in storages) { + backupAgentHelper.addHelper(keyPrefix, storage) + } + } + + /** + * Callback when restore finished. + * + * The observers of the storages will be notified. + */ + fun onRestoreFinished() { + for (storage in storages.values) { + storage.notifyRestoreFinished() + } + } + + private fun BackupRestoreStorage.notifyRestoreFinished() { + when (this) { + is KeyedObservable<*> -> notifyChange(ChangeReason.RESTORE) + is Observable -> notifyChange(ChangeReason.RESTORE) + } + } + + /** + * Adds a list of storages. + * + * The storage MUST implement [KeyedObservable] or [Observable]. + */ + fun add(vararg storages: BackupRestoreStorage) { + for (storage in storages) add(storage) + } + + /** + * Adds a storage. + * + * The storage MUST implement [KeyedObservable] or [Observable]. + */ + fun add(storage: BackupRestoreStorage) { + val name = storage.name + val oldStorage = storages.put(name, storage) + if (oldStorage != null) { + throw IllegalStateException( + "Storage name '$name' conflicts:\n\told: $oldStorage\n\tnew: $storage" + ) + } + storage.addObserver() + } + + private fun BackupRestoreStorage.addObserver() { + when (this) { + is KeyedObservable<*> -> addObserver(keyedObserver, executor) + is Observable -> addObserver(observer, executor) + else -> + throw IllegalArgumentException( + "$this does not implement either KeyedObservable or Observable" + ) + } + } + + /** Removes all the storages. */ + fun removeAll() { + for ((name, _) in storages) remove(name) + } + + /** Removes storage with given name. */ + fun remove(name: String): BackupRestoreStorage? { + val storage = storages.remove(name) + storage?.removeObserver() + return storage + } + + private fun BackupRestoreStorage.removeObserver() { + when (this) { + is KeyedObservable<*> -> removeObserver(keyedObserver) + is Observable -> removeObserver(observer) + } + } + + /** Returns storage with given name. */ + fun get(name: String): BackupRestoreStorage? = storages[name] + + /** Returns storage with given name, exception is raised if not found. */ + fun getOrThrow(name: String): BackupRestoreStorage = storages[name]!! + + companion object { + @Volatile private var instance: BackupRestoreStorageManager? = null + + /** Returns the singleton of manager. */ + @JvmStatic + fun getInstance(context: Context): BackupRestoreStorageManager { + val result = instance + if (result != null) return result + synchronized(this) { + if (instance == null) { + instance = + BackupRestoreStorageManager(context.applicationContext as Application) + } + } + return instance!! + } + } +} diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt new file mode 100644 index 000000000000..3ed4d46459cf --- /dev/null +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt @@ -0,0 +1,183 @@ +/* + * 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.datastore + +import androidx.annotation.AnyThread +import androidx.annotation.GuardedBy +import androidx.collection.MutableScatterMap +import java.util.WeakHashMap +import java.util.concurrent.Executor + +/** + * Callback to be informed of changes in [KeyedObservable] object. + * + * The observer is weakly referenced, a strong reference must be kept. + */ +fun interface KeyedObserver<in K> { + /** + * Called by [KeyedObservable] in the event of changes. + * + * This callback will run in the given [Executor] when observer is added. + * + * @param key key that has been changed + * @param reason the reason of change + * @see KeyedObservable.addObserver + */ + fun onKeyChanged(key: K, @ChangeReason reason: Int) +} + +/** + * A key-value observable object allows to observe change with [KeyedObserver]. + * + * Notes: + * - The order in which observers will be notified is unspecified. + * - The observer is weakly referenced to avoid memory leaking, the call site must keep a strong + * reference of the observer. + * - It is possible that the callback may be triggered even there is no real data change. For + * example, when data restore/clear happens, it might be too complex to check if data is really + * changed, thus all the registered observers are notified directly. + */ +@AnyThread +interface KeyedObservable<K> { + /** + * Adds an observer for any key. + * + * The observer will be notified whenever a change happens. The [KeyedObserver.onKeyChanged] + * callback will be invoked with specific key that is modified. However, `null` key is passed in + * the cases that a bunch of keys are changed simultaneously (e.g. clear data, restore happens). + * + * @param observer observer to be notified + * @param executor executor to run the callback + */ + fun addObserver(observer: KeyedObserver<K?>, executor: Executor) + + /** + * Adds an observer on given key. + * + * The observer will be notified only when the given key is changed. + * + * @param key key to observe + * @param observer observer to be notified + * @param executor executor to run the callback + */ + fun addObserver(key: K, observer: KeyedObserver<K>, executor: Executor) + + /** Removes observer. */ + fun removeObserver(observer: KeyedObserver<K?>) + + /** Removes observer on given key. */ + fun removeObserver(key: K, observer: KeyedObserver<K>) + + /** + * Notifies all observers that a change occurs. + * + * All the any key and keyed observers are notified. + * + * @param reason reason of the change + */ + fun notifyChange(@ChangeReason reason: Int) + + /** + * Notifies observers that a change occurs on given key. + * + * The any key and specific key observers are notified. + * + * @param key key of the change + * @param reason reason of the change + */ + fun notifyChange(key: K, @ChangeReason reason: Int) +} + +/** A thread safe implementation of [KeyedObservable]. */ +class KeyedDataObservable<K> : KeyedObservable<K> { + // Instead of @GuardedBy("this"), guarded by itself because KeyedDataObservable object could be + // synchronized outside by the holder + @GuardedBy("itself") private val observers = WeakHashMap<KeyedObserver<K?>, Executor>() + + @GuardedBy("itself") + private val keyedObservers = MutableScatterMap<K, WeakHashMap<KeyedObserver<K>, Executor>>() + + override fun addObserver(observer: KeyedObserver<K?>, executor: Executor) { + val oldExecutor = synchronized(observers) { observers.put(observer, executor) } + if (oldExecutor != null && oldExecutor != executor) { + throw IllegalStateException("Add $observer twice, old=$oldExecutor, new=$executor") + } + } + + override fun addObserver(key: K, observer: KeyedObserver<K>, executor: Executor) { + val oldExecutor = + synchronized(keyedObservers) { + keyedObservers.getOrPut(key) { WeakHashMap() }.put(observer, executor) + } + if (oldExecutor != null && oldExecutor != executor) { + throw IllegalStateException("Add $observer twice, old=$oldExecutor, new=$executor") + } + } + + override fun removeObserver(observer: KeyedObserver<K?>) { + synchronized(observers) { observers.remove(observer) } + } + + override fun removeObserver(key: K, observer: KeyedObserver<K>) { + synchronized(keyedObservers) { + val observers = keyedObservers[key] + if (observers?.remove(observer) != null && observers.isEmpty()) { + keyedObservers.remove(key) + } + } + } + + override fun notifyChange(@ChangeReason reason: Int) { + // make a copy to avoid potential ConcurrentModificationException + val observers = synchronized(observers) { observers.entries.toTypedArray() } + val keyedObservers = synchronized(keyedObservers) { keyedObservers.copy() } + for (entry in observers) { + val observer = entry.key // avoid reference "entry" + entry.value.execute { observer.onKeyChanged(null, reason) } + } + for (pair in keyedObservers) { + val key = pair.first + for (entry in pair.second) { + val observer = entry.key // avoid reference "entry" + entry.value.execute { observer.onKeyChanged(key, reason) } + } + } + } + + private fun MutableScatterMap<K, WeakHashMap<KeyedObserver<K>, Executor>>.copy(): + List<Pair<K, Array<Map.Entry<KeyedObserver<K>, Executor>>>> { + val result = ArrayList<Pair<K, Array<Map.Entry<KeyedObserver<K>, Executor>>>>(size) + forEach { key, value -> result.add(Pair(key, value.entries.toTypedArray())) } + return result + } + + override fun notifyChange(key: K, @ChangeReason reason: Int) { + // make a copy to avoid potential ConcurrentModificationException + val observers = synchronized(observers) { observers.entries.toTypedArray() } + val keyedObservers = + synchronized(keyedObservers) { keyedObservers[key]?.entries?.toTypedArray() } + ?: arrayOf() + for (entry in observers) { + val observer = entry.key // avoid reference "entry" + entry.value.execute { observer.onKeyChanged(key, reason) } + } + for (entry in keyedObservers) { + val observer = entry.key // avoid reference "entry" + entry.value.execute { observer.onKeyChanged(key, reason) } + } + } +} diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/ObservableBackupRestoreStorage.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/ObservableBackupRestoreStorage.kt new file mode 100644 index 000000000000..0e399c01e763 --- /dev/null +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/ObservableBackupRestoreStorage.kt @@ -0,0 +1,25 @@ +/* + * 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.datastore + +/** + * A [BackupRestoreStorage] that implements [Observable]. + * + * This class provides the [Observable] implementations on top of [DataObservable] by delegation. + */ +abstract class ObservableBackupRestoreStorage : + BackupRestoreStorage(), Observable by DataObservable() diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/Observer.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/Observer.kt new file mode 100644 index 000000000000..6d0ca6690c9f --- /dev/null +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/Observer.kt @@ -0,0 +1,121 @@ +/* + * 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.datastore + +import androidx.annotation.AnyThread +import androidx.annotation.GuardedBy +import androidx.annotation.IntDef +import java.util.WeakHashMap +import java.util.concurrent.Executor + +/** The reason of a change. */ +@IntDef( + ChangeReason.UNKNOWN, + ChangeReason.UPDATE, + ChangeReason.DELETE, + ChangeReason.RESTORE, + ChangeReason.SYNC_ACROSS_PROFILES, +) +@Retention(AnnotationRetention.SOURCE) +annotation class ChangeReason { + companion object { + /** Unknown reason of the change. */ + const val UNKNOWN = 0 + /** Data is updated. */ + const val UPDATE = 1 + /** Data is deleted. */ + const val DELETE = 2 + /** Data is restored from backup/restore framework. */ + const val RESTORE = 3 + /** Data is synced from another profile (e.g. personal profile to work profile). */ + const val SYNC_ACROSS_PROFILES = 4 + } +} + +/** + * Callback to be informed of changes in [Observable] object. + * + * The observer is weakly referenced, a strong reference must be kept. + */ +fun interface Observer { + /** + * Called by [Observable] in the event of changes. + * + * This callback will run in the given [Executor] when observer is added. + * + * @param reason the reason of change + * @see [Observable.addObserver] for the notices. + */ + fun onChanged(@ChangeReason reason: Int) +} + +/** An observable object allows to observe change with [Observer]. */ +@AnyThread +interface Observable { + /** + * Adds an observer. + * + * Notes: + * - The order in which observers will be notified is unspecified. + * - The observer is weakly referenced to avoid memory leaking, the call site must keep a strong + * reference of the observer. + * - It is possible that the callback may be triggered even there is no real data change. For + * example, when data restore/clear happens, it might be too complex to check if data is + * really changed, thus all the registered observers are notified directly. + * + * @param observer observer to be notified + * @param executor executor to run the [Observer.onChanged] callback + */ + fun addObserver(observer: Observer, executor: Executor) + + /** Removes given observer. */ + fun removeObserver(observer: Observer) + + /** + * Notifies observers that a change occurs. + * + * @param reason reason of the change + */ + fun notifyChange(@ChangeReason reason: Int) +} + +/** A thread safe implementation of [Observable]. */ +class DataObservable : Observable { + // Instead of @GuardedBy("this"), guarded by itself because DataObservable object could be + // synchronized outside by the holder + @GuardedBy("itself") private val observers = WeakHashMap<Observer, Executor>() + + override fun addObserver(observer: Observer, executor: Executor) { + val oldExecutor = synchronized(observers) { observers.put(observer, executor) } + if (oldExecutor != null && oldExecutor != executor) { + throw IllegalStateException("Add $observer twice, old=$oldExecutor, new=$executor") + } + } + + override fun removeObserver(observer: Observer) { + synchronized(observers) { observers.remove(observer) } + } + + override fun notifyChange(@ChangeReason reason: Int) { + // make a copy to avoid potential ConcurrentModificationException + val entries = synchronized(observers) { observers.entries.toTypedArray() } + for (entry in entries) { + val observer = entry.key // avoid reference "entry" + entry.value.execute { observer.onChanged(reason) } + } + } +} diff --git a/packages/SettingsLib/DataStore/tests/Android.bp b/packages/SettingsLib/DataStore/tests/Android.bp new file mode 100644 index 000000000000..8770dfa013d0 --- /dev/null +++ b/packages/SettingsLib/DataStore/tests/Android.bp @@ -0,0 +1,24 @@ +package { + default_applicable_licenses: ["frameworks_base_license"], +} + +android_app { + name: "SettingsLibDataStoreShell", + platform_apis: true, +} + +android_robolectric_test { + name: "SettingsLibDataStoreTest", + srcs: ["src/**/*"], + static_libs: [ + "SettingsLibDataStore", + "androidx.test.ext.junit", + "guava", + "mockito-robolectric-prebuilt", // mockito deps order matters! + "mockito-kotlin2", + ], + java_resource_dirs: ["config"], + instrumentation_for: "SettingsLibDataStoreShell", + coverage_libs: ["SettingsLibDataStore"], + upstream: true, +} diff --git a/packages/SettingsLib/DataStore/tests/AndroidManifest.xml b/packages/SettingsLib/DataStore/tests/AndroidManifest.xml new file mode 100644 index 000000000000..ffc24e4330d1 --- /dev/null +++ b/packages/SettingsLib/DataStore/tests/AndroidManifest.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.settingslib.datastore.test"> + + <application android:debuggable="true" /> +</manifest> diff --git a/packages/SettingsLib/DataStore/tests/config/robolectric.properties b/packages/SettingsLib/DataStore/tests/config/robolectric.properties new file mode 100644 index 000000000000..fab7251d020b --- /dev/null +++ b/packages/SettingsLib/DataStore/tests/config/robolectric.properties @@ -0,0 +1 @@ +sdk=NEWEST_SDK diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt new file mode 100644 index 000000000000..bb791dc9a23c --- /dev/null +++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt @@ -0,0 +1,109 @@ +/* + * 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.datastore + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import com.google.common.util.concurrent.MoreExecutors +import java.util.concurrent.Executor +import java.util.concurrent.atomic.AtomicInteger +import org.junit.Assert.assertThrows +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule +import org.mockito.kotlin.any +import org.mockito.kotlin.never +import org.mockito.kotlin.reset +import org.mockito.kotlin.verify + +@RunWith(AndroidJUnit4::class) +class ObserverTest { + @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule() + + @Mock private lateinit var observer1: Observer + + @Mock private lateinit var observer2: Observer + + @Mock private lateinit var executor: Executor + + private val observable = DataObservable() + + @Test + fun addObserver_sameExecutor() { + observable.addObserver(observer1, executor) + observable.addObserver(observer1, executor) + } + + @Test + fun addObserver_differentExecutor() { + observable.addObserver(observer1, executor) + assertThrows(IllegalStateException::class.java) { + observable.addObserver(observer1, MoreExecutors.directExecutor()) + } + } + + @Test + fun addObserver_weaklyReferenced() { + val counter = AtomicInteger() + var observer: Observer? = Observer { counter.incrementAndGet() } + observable.addObserver(observer!!, MoreExecutors.directExecutor()) + + observable.notifyChange(ChangeReason.UPDATE) + assertThat(counter.get()).isEqualTo(1) + + // trigger GC, the observer callback should not be invoked + @Suppress("unused") + observer = null + System.gc() + System.runFinalization() + + observable.notifyChange(ChangeReason.UPDATE) + assertThat(counter.get()).isEqualTo(1) + } + + @Test + fun addObserver_notifyObservers_removeObserver() { + observable.addObserver(observer1, MoreExecutors.directExecutor()) + observable.addObserver(observer2, executor) + + observable.notifyChange(ChangeReason.DELETE) + + verify(observer1).onChanged(ChangeReason.DELETE) + verify(observer2, never()).onChanged(any()) + verify(executor).execute(any()) + + reset(observer1, executor) + observable.removeObserver(observer2) + + observable.notifyChange(ChangeReason.UPDATE) + verify(observer1).onChanged(ChangeReason.UPDATE) + verify(executor, never()).execute(any()) + } + + @Test + fun notifyChange_addObserverWithinCallback() { + // ConcurrentModificationException is raised if it is not implemented correctly + observable.addObserver( + { observable.addObserver(observer1, executor) }, + MoreExecutors.directExecutor() + ) + observable.notifyChange(ChangeReason.UPDATE) + } +} diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml index 7245099c4ff2..cc23f6eedf9a 100644 --- a/packages/SettingsLib/res/values-af/strings.xml +++ b/packages/SettingsLib/res/values-af/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Geneutraliseer deur <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Laaiproses is onderbreek om battery te beskerm"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Gaan die laaibykomstigheid na"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ongeveer <xliff:g id="TIME_REMAINING">%1$s</xliff:g> oor"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Ongeveer <xliff:g id="TIME_REMAINING">%1$s</xliff:g> oor (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Ongeveer <xliff:g id="TIME_REMAINING">%1$s</xliff:g> oor gegrond op jou gebruik"</string> diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml index dc91df021347..0ecd376626c4 100644 --- a/packages/SettingsLib/res/values-am/strings.xml +++ b/packages/SettingsLib/res/values-am/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"በ<xliff:g id="TITLE">%1$s</xliff:g> ተሽሯል"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - ባትሪን ለመጠበቅ ኃይል መሙላት በይቆይ ላይ"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - የኃይል መሙላት መለዋወጫን ይፈትሹ"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ገደማ ቀርቷል"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>) ገደማ ቀርቷል"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"በአጠቃቀምዎ መሠረት <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ገደማ ቀርቷል"</string> diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml index 87c4d7fb99a7..1f313aef239c 100644 --- a/packages/SettingsLib/res/values-ar/strings.xml +++ b/packages/SettingsLib/res/values-ar/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"تم الاستبدال بـ <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - الشحن معلَّق لحماية البطارية"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - فحص ملحق الشحن"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"يتبقى <xliff:g id="TIME_REMAINING">%1$s</xliff:g> تقريبًا."</string> <string name="power_discharging_duration" msgid="1076561255466053220">"يتبقى <xliff:g id="TIME_REMAINING">%1$s</xliff:g> تقريبًا (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"يتبقى <xliff:g id="TIME_REMAINING">%1$s</xliff:g> تقريبًا، بناءً على استخدامك"</string> diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml index bcf9f5d25980..a4be8e997311 100644 --- a/packages/SettingsLib/res/values-as/strings.xml +++ b/packages/SettingsLib/res/values-as/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g>ৰ দ্বাৰা অগ্ৰাহ্য কৰা হৈছে"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - বেটাৰী সুৰক্ষিত কৰিবলৈ চাৰ্জিং স্থগিত ৰখা হৈছে"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - চাৰ্জিঙৰ সৈতে জড়িত আনুষংগিক সামগ্ৰী পৰীক্ষা কৰক"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"প্রায় <xliff:g id="TIME_REMAINING">%1$s</xliff:g> বাকী আছে"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"প্রায় <xliff:g id="TIME_REMAINING">%1$s</xliff:g> বাকী আছে (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"আপোনাৰ ব্যৱহাৰৰ ওপৰত ভিত্তি কৰি প্ৰায় <xliff:g id="TIME_REMAINING">%1$s</xliff:g> বাকী আছে"</string> diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml index 22dbff2036bb..df6dad208bd8 100644 --- a/packages/SettingsLib/res/values-az/strings.xml +++ b/packages/SettingsLib/res/values-az/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> tərəfindən qəbul edilmir"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Batareyanı qorumaq üçün şarj gözlədilir"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Şarj aksesuarını yoxlayın"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Təxminən <xliff:g id="TIME_REMAINING">%1$s</xliff:g> qalıb"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Təxminən <xliff:g id="TIME_REMAINING">%1$s</xliff:g> qalıb (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"İstifadəyə əsasən təxminən <xliff:g id="TIME_REMAINING">%1$s</xliff:g> qalıb"</string> diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml index ee58e8ef5e04..8bb8c838ed39 100644 --- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml +++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Zamenjuje ga <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>–<xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – punjenje je na čekanju da bi se zaštitila baterija"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Proverite dodatnu opremu za punjenje"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Preostalo je oko <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Preostalo je oko <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Preostalo je oko <xliff:g id="TIME_REMAINING">%1$s</xliff:g> na osnovu korišćenja"</string> diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml index 079a8f3aaa11..433c2430a40d 100644 --- a/packages/SettingsLib/res/values-be/strings.xml +++ b/packages/SettingsLib/res/values-be/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Перавызначаны <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – зарадка прыпынена, каб абараніць акумулятар"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g>, праверце зарадную прыладу"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Зараду хопіць прыблізна на <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Зараду (<xliff:g id="LEVEL">%2$s</xliff:g>) хопіць прыблізна на <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Зараду пры такім выкарыстанні хопіць прыблізна на <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml index 5a0c0b779f55..27e27bef4f3a 100644 --- a/packages/SettingsLib/res/values-bg/strings.xml +++ b/packages/SettingsLib/res/values-bg/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Заменено от „<xliff:g id="TITLE">%1$s</xliff:g>“"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Зареждането е поставено на пауза с цел запазване на батерията"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Проверете аксесоара за зареждане"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Още около <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Още около <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Още около <xliff:g id="TIME_REMAINING">%1$s</xliff:g> въз основа на използването"</string> diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml index 6fb6fb3dc6cd..2cf055654f3b 100644 --- a/packages/SettingsLib/res/values-bn/strings.xml +++ b/packages/SettingsLib/res/values-bn/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> এর দ্বারা ওভাররাইড করা হয়েছে"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - ব্যাটারিকে সুরক্ষিত রাখতে চার্জিং হোল্ড করা হয়েছে"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - চার্জিং অ্যাক্সেসরি চেক করুন"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"আর আনুমানিক <xliff:g id="TIME_REMAINING">%1$s</xliff:g> চলবে"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"আর আনুমানিক <xliff:g id="TIME_REMAINING">%1$s</xliff:g> চলবে (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"ব্যবহারের উপর ভিত্তি করে আর আনুমানিক <xliff:g id="TIME_REMAINING">%1$s</xliff:g> চলবে"</string> diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml index a430848e79f3..64ccf98c3aa8 100644 --- a/packages/SettingsLib/res/values-bs/strings.xml +++ b/packages/SettingsLib/res/values-bs/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Zamjenjuje <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – punjenje je na čekanju radi zaštite baterije"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – provjerite opremu za punjenje"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Preostalo je još oko <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Preostalo je još oko <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Preostalo je još oko <xliff:g id="TIME_REMAINING">%1$s</xliff:g> na osnovu vaše potrošnje"</string> diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml index 0bbb97960a76..81f9e97a2203 100644 --- a/packages/SettingsLib/res/values-ca/strings.xml +++ b/packages/SettingsLib/res/values-ca/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"S\'ha substituït per <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>: <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g>: la càrrega s\'ha posat en espera per protegir la bateria"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g>: revisa l\'accessori de càrrega"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Temps restant aproximat: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Temps restant aproximat: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Temps restant aproximat segons l\'ús que en fas: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml index 97d0d15bfedf..cb8c36b55580 100644 --- a/packages/SettingsLib/res/values-cs/strings.xml +++ b/packages/SettingsLib/res/values-cs/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Přepsáno nastavením <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Nabíjení je pozastaveno za účelem ochrany baterie"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Zkontrolujte nabíjecí příslušenství"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Zbývá asi <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Zbývá asi <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Při vašem obvyklém využití zbývá asi <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml index 096042246e01..625f05b57ac8 100644 --- a/packages/SettingsLib/res/values-da/strings.xml +++ b/packages/SettingsLib/res/values-da/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Tilsidesat af <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Opladningen er sat på pause for at beskytte batteriet"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Tjek opladningstilbehøret"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ca. <xliff:g id="TIME_REMAINING">%1$s</xliff:g> tilbage"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Ca. <xliff:g id="TIME_REMAINING">%1$s</xliff:g> tilbage (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Ca. <xliff:g id="TIME_REMAINING">%1$s</xliff:g> tilbage, alt efter hvordan du bruger enheden"</string> diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml index cb36cca64ff4..f622f61505f5 100644 --- a/packages/SettingsLib/res/values-de/strings.xml +++ b/packages/SettingsLib/res/values-de/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Außer Kraft gesetzt von \"<xliff:g id="TITLE">%1$s</xliff:g>\""</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Ladevorgang zum Schutz des Akkus angehalten"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Ladezubehör prüfen"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Noch etwa <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Noch etwa <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Bei deinem Nutzungsmuster hast du noch ca. <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml index 4953884b0d74..861f4722edfb 100644 --- a/packages/SettingsLib/res/values-el/strings.xml +++ b/packages/SettingsLib/res/values-el/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Αντικαταστάθηκε από <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Η φόρτιση τέθηκε σε αναμονή για προστασία της μπαταρίας"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Έλεγχος αξεσουάρ φόρτισης"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Απομένει/ουν περίπου <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Απομένει/ουν περίπου <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Απομένει/ουν περίπου <xliff:g id="TIME_REMAINING">%1$s</xliff:g>, βάσει της χρήσης σας"</string> diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml index d730fb7951e1..e6923de1af8c 100644 --- a/packages/SettingsLib/res/values-en-rAU/strings.xml +++ b/packages/SettingsLib/res/values-en-rAU/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Charging on hold to protect battery"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – check charging accessory"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left based on your usage"</string> diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml index d730fb7951e1..e6923de1af8c 100644 --- a/packages/SettingsLib/res/values-en-rGB/strings.xml +++ b/packages/SettingsLib/res/values-en-rGB/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Charging on hold to protect battery"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – check charging accessory"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left based on your usage"</string> diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml index d730fb7951e1..e6923de1af8c 100644 --- a/packages/SettingsLib/res/values-en-rIN/strings.xml +++ b/packages/SettingsLib/res/values-en-rIN/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Charging on hold to protect battery"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – check charging accessory"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left based on your usage"</string> diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml index 775f23c14eac..a6fdf569954c 100644 --- a/packages/SettingsLib/res/values-es-rUS/strings.xml +++ b/packages/SettingsLib/res/values-es-rUS/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Reemplazado por <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Se detuvo la carga para proteger la batería"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Verifica el accesorio de carga"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tiempo restante: aproximadamente <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Tiempo restante: aproximadamente <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Tiempo restante: aproximadamente <xliff:g id="TIME_REMAINING">%1$s</xliff:g> en función de tu uso"</string> diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml index 70b85029a2b0..d57a33a305c6 100644 --- a/packages/SettingsLib/res/values-es/strings.xml +++ b/packages/SettingsLib/res/values-es/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Anulado por <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>: <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Carga pausada para proteger la batería"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Comprueba el accesorio de carga"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tiempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Tiempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Tiempo restante aproximado según tu uso: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml index daae2147ce7b..dfe78d8f3557 100644 --- a/packages/SettingsLib/res/values-et/strings.xml +++ b/packages/SettingsLib/res/values-et/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Alistas <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – laadimine on aku kaitsmiseks ootele pandud"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – kontrollige laadimistarvikut"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ligikaudu <xliff:g id="TIME_REMAINING">%1$s</xliff:g> jäänud"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Ligikaudu <xliff:g id="TIME_REMAINING">%1$s</xliff:g> jäänud (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Teie kasutuse põhjal on jäänud ligikaudu <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml index 2fd01f3ab848..3c6309dad77f 100644 --- a/packages/SettingsLib/res/values-eu/strings.xml +++ b/packages/SettingsLib/res/values-eu/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> hobespena gainjarri zaio"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g>: kargatze-prozesua zain dago bateria babesteko"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Eman begiratu bat kargatzeko osagarriari"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> inguru gelditzen dira"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> inguru gelditzen dira (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Erabilera kontuan izanda, <xliff:g id="TIME_REMAINING">%1$s</xliff:g> inguru gelditzen dira"</string> diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml index 9a1e1076eae9..2cb1873e5fbe 100644 --- a/packages/SettingsLib/res/values-fa/strings.xml +++ b/packages/SettingsLib/res/values-fa/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"توسط <xliff:g id="TITLE">%1$s</xliff:g> لغو شد"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - برای محافظت از باتری، شارژ موقتاً متوقف شده است"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - لوازم شارژ را بررسی کنید"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"تقریباً <xliff:g id="TIME_REMAINING">%1$s</xliff:g> شارژ باقی مانده است"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"تقریباً <xliff:g id="TIME_REMAINING">%1$s</xliff:g> شارژ باقی مانده است (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"براساس مصرفتان، تقریباً <xliff:g id="TIME_REMAINING">%1$s</xliff:g> شارژ باقی مانده است"</string> diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml index 0faa32c54442..29459ca5ee1f 100644 --- a/packages/SettingsLib/res/values-fi/strings.xml +++ b/packages/SettingsLib/res/values-fi/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Tämän ohittaa <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Lataus on keskeytetty akun suojaamiseksi"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Tarkista latauslisävaruste"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Noin <xliff:g id="TIME_REMAINING">%1$s</xliff:g> jäljellä"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Noin <xliff:g id="TIME_REMAINING">%1$s</xliff:g> jäljellä (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Noin <xliff:g id="TIME_REMAINING">%1$s</xliff:g> jäljellä käyttösi perusteella"</string> diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml index 24de4cd07a80..f97b36ea895d 100644 --- a/packages/SettingsLib/res/values-fr-rCA/strings.xml +++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml @@ -257,7 +257,7 @@ <string name="adb_wireless_verifying_qrcode_text" msgid="6123192424916029207">"Association de l\'appareil en cours…"</string> <string name="adb_qrcode_pairing_device_failed_msg" msgid="6936292092592914132">"Échec de l\'association de l\'appareil Soit le code QR est incorrect, soit l\'appareil n\'est pas connecté au même réseau."</string> <string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"Adresse IP et port"</string> - <string name="adb_wireless_qrcode_pairing_title" msgid="1906409667944674707">"Numériser le code QR"</string> + <string name="adb_wireless_qrcode_pairing_title" msgid="1906409667944674707">"Balayer le code QR"</string> <string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"Associer l\'appareil par Wi-Fi en numérisant un code QR"</string> <string name="adb_wireless_no_network_msg" msgid="2365795244718494658">"Veuillez vous connecter à un réseau Wi-Fi"</string> <string name="keywords_adb_wireless" msgid="6507505581882171240">"adb, débogage, développeur"</string> @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Remplacé par <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> : <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – La recharge a été mise en pause pour protéger la pile"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Vérifier l\'accessoire de recharge"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Il reste environ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Il reste environ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Il reste environ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> en fonction de votre usage"</string> diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml index 8f61c8d604fa..7f7ba637b47d 100644 --- a/packages/SettingsLib/res/values-fr/strings.xml +++ b/packages/SettingsLib/res/values-fr/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Remplacé par <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Recharge en pause pour protéger la batterie"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> : vérifiez l\'accessoire de recharge"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Temps restant : environ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Temps restant : environ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Temps restant en fonction de votre utilisation : environ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml index 4cd9e2ca86b7..179d946c23da 100644 --- a/packages/SettingsLib/res/values-gl/strings.xml +++ b/packages/SettingsLib/res/values-gl/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Anulado por <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g>. A carga púxose en pausa para protexer a batería"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g>. Comproba o accesorio de carga"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Tempo restante aproximado (<xliff:g id="LEVEL">%2$s</xliff:g>): <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Tempo restante aproximado en función do uso: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml index 15f92875e22d..cafe86c26b77 100644 --- a/packages/SettingsLib/res/values-gu/strings.xml +++ b/packages/SettingsLib/res/values-gu/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> દ્વારા ઓવરરાઇડ થયું"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - બૅટરીને સુરક્ષિત રાખવા માટે, ચાર્જિંગ હોલ્ડ પર રાખવામાં આવ્યું છે"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - ચાર્જિંગ ઍક્સેસરી ચેક કરો"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"લગભગ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> બાકી છે"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"લગભગ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> બાકી છે (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"તમારા વપરાશના આધારે લગભગ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> બાકી છે"</string> diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml index 5c94489f8f77..53cf7247b996 100644 --- a/packages/SettingsLib/res/values-hi/strings.xml +++ b/packages/SettingsLib/res/values-hi/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> के द्वारा ओवरराइड किया गया"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - बैटरी को सुरक्षित रखने के लिए, फ़ोन को चार्ज होने से रोक दिया गया है"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्जिंग ऐक्सेसरी की जांच करें"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"बैटरी करीब <xliff:g id="TIME_REMAINING">%1$s</xliff:g> में खत्म हो जाएगी"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"बैटरी करीब <xliff:g id="TIME_REMAINING">%1$s</xliff:g> में खत्म हो जाएगी (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"आपके इस्तेमाल के हिसाब से बैटरी करीब <xliff:g id="TIME_REMAINING">%1$s</xliff:g> में खत्म हो जाएगी"</string> diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml index cc7b327e9d4f..f06baad6ea6c 100644 --- a/packages/SettingsLib/res/values-hr/strings.xml +++ b/packages/SettingsLib/res/values-hr/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Premošćeno postavkom <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – punjenje je pauzirano radi zaštite baterije"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – provjerite dodatak za punjenje"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Još oko <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Još otprilike <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Još otprilike <xliff:g id="TIME_REMAINING">%1$s</xliff:g> na temelju vaše upotrebe"</string> diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml index e9fd9dbd54e9..39d905f0bf46 100644 --- a/packages/SettingsLib/res/values-hu/strings.xml +++ b/packages/SettingsLib/res/values-hu/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Felülírva erre: <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Az akkumulátor védelme érdekében a töltés szünetel"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Ellenőrizze a töltőtartozékot"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Körülbelül <xliff:g id="TIME_REMAINING">%1$s</xliff:g> maradt hátra"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Körülbelül <xliff:g id="TIME_REMAINING">%1$s</xliff:g> maradt hátra (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Körülbelül <xliff:g id="TIME_REMAINING">%1$s</xliff:g> maradt hátra az eszköz használata alapján"</string> diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml index d6b356005ac6..55f09e297f2a 100644 --- a/packages/SettingsLib/res/values-hy/strings.xml +++ b/packages/SettingsLib/res/values-hy/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Գերազանցված է <xliff:g id="TITLE">%1$s</xliff:g>-ից"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Լիցքավորումը դադարեցվել է՝ մարտկոցը պաշտպանելու համար"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Ստուգեք լիցքավորիչը"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Լիցքը կբավարարի մոտ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Լիցքը (<xliff:g id="LEVEL">%2$s</xliff:g>) կբավարարի մոտ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Լիցքը կբավարարի մոտ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>՝ կախված օգտագործման եղանակից"</string> diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml index 398853aa5b02..885729da1b66 100644 --- a/packages/SettingsLib/res/values-in/strings.xml +++ b/packages/SettingsLib/res/values-in/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Digantikan oleh <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Pengisian daya dihentikan sementara untuk melindungi baterai"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Periksa aksesori pengisian daya"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Sekitar <xliff:g id="TIME_REMAINING">%1$s</xliff:g> lagi"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Sekitar <xliff:g id="TIME_REMAINING">%1$s</xliff:g> lagi (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Sekitar <xliff:g id="TIME_REMAINING">%1$s</xliff:g> lagi berdasarkan penggunaan Anda"</string> diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml index 2be92e5dff33..020191b55e7c 100644 --- a/packages/SettingsLib/res/values-is/strings.xml +++ b/packages/SettingsLib/res/values-is/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Hnekkt af <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Hleðsla í bið til að vernda rafhlöðuna"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Athugaðu hleðslubúnaðinn"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Um það bil <xliff:g id="TIME_REMAINING">%1$s</xliff:g> eftir"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Um það bil <xliff:g id="TIME_REMAINING">%1$s</xliff:g> eftir (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Um það bil <xliff:g id="TIME_REMAINING">%1$s</xliff:g> eftir miðað við notkun þína"</string> diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml index 3dc69e8ace46..37a20675abe7 100644 --- a/packages/SettingsLib/res/values-it/strings.xml +++ b/packages/SettingsLib/res/values-it/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Valore sostituito da <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Ricarica in sospeso per proteggere la batteria"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Controlla l\'accessorio di ricarica"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tempo rimanente: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> circa"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Tempo rimanente: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> circa (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Tempo rimanente in base al tuo utilizzo: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> circa"</string> diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml index 9ef9cf1bc043..d87d0f8dafbc 100644 --- a/packages/SettingsLib/res/values-ja/strings.xml +++ b/packages/SettingsLib/res/values-ja/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g>によって上書き済み"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - バッテリーを保護するため、充電を一時停止しています"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - 充電用アクセサリを確認してください"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"残り時間: 約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"残り時間: 約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>(<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"残り時間: 約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>(使用状況に基づく)"</string> diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml index 4ddb0054c329..7812f0b21601 100644 --- a/packages/SettingsLib/res/values-ka/strings.xml +++ b/packages/SettingsLib/res/values-ka/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"უკუგებულია <xliff:g id="TITLE">%1$s</xliff:g>-ის მიერ"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – დატენვა შეჩერებულია ბატარეის დასაცავად"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g>: მიმდინარეობს დამტენი აქსესუარის შემოწმება"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"დარჩა დაახლოებით <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"დარჩა დაახლოებით <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"დარჩა დაახლოებით <xliff:g id="TIME_REMAINING">%1$s</xliff:g>, ბატარეის მოხმარების გათვალისწინებით"</string> diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml index fa46e8e93db6..300d4e517013 100644 --- a/packages/SettingsLib/res/values-kk/strings.xml +++ b/packages/SettingsLib/res/values-kk/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> үстінен басқан"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g>: батареяны қорғау үшін зарядтау кідіртіледі."</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – зарядтау құрылғысын тексеріңіз"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Шамамен <xliff:g id="TIME_REMAINING">%1$s</xliff:g> қалды"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Шамамен <xliff:g id="TIME_REMAINING">%1$s</xliff:g> қалды (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Пайдалану деректеріңізге сәйкес енді шамамен <xliff:g id="TIME_REMAINING">%1$s</xliff:g> қалды"</string> diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml index cd2a6c41ef5a..373cfc656789 100644 --- a/packages/SettingsLib/res/values-km/strings.xml +++ b/packages/SettingsLib/res/values-km/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"បដិសេធដោយ <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - កំពុងផ្អាកការសាកថ្ម ដើម្បីការពារថ្ម"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - ពិនិត្យមើលគ្រឿងសាកថ្ម"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"នៅសល់ប្រហែល <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ទៀត"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"នៅសល់ប្រហែល <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ទៀត (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"នៅសល់ប្រហែល <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ទៀត ផ្អែកលើការប្រើប្រាស់របស់អ្នក"</string> diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml index a01a4bd1c37d..bf84cc48aa39 100644 --- a/packages/SettingsLib/res/values-kn/strings.xml +++ b/packages/SettingsLib/res/values-kn/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ಮೂಲಕ ಅತಿಕ್ರಮಿಸುತ್ತದೆ"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - ಬ್ಯಾಟರಿಯನ್ನು ರಕ್ಷಿಸಲು ಚಾರ್ಜಿಂಗ್ ಅನ್ನು ಹೋಲ್ಡ್ ಮಾಡಲಾಗಿದೆ"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - ಚಾರ್ಜಿಂಗ್ ಆ್ಯಕ್ಸೆಸರಿಯನ್ನು ಪರಿಶೀಲಿಸಿ"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ಸಮಯ ಬಾಕಿ ಉಳಿದಿದೆ"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"(<xliff:g id="LEVEL">%2$s</xliff:g>) ತಲುಪಲು <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ಸಮಯ ಬಾಕಿ ಉಳಿದಿದೆ"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"ನಿಮ್ಮ ಬಳಕೆಯ ಆಧಾರದ ಮೇಲೆ ಸುಮಾರು <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ಸಮಯ ಬಾಕಿ ಉಳಿದಿದೆ"</string> diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml index c9c92e5894f7..b184ef43e5b7 100644 --- a/packages/SettingsLib/res/values-ko/strings.xml +++ b/packages/SettingsLib/res/values-ko/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> 우선 적용됨"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>, <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - 배터리 보호를 위해 충전 일시중지"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - 충전 액세서리 확인"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"남은 시간: 약 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"남은 시간 약 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>(<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"내 사용량을 기준으로 약 <xliff:g id="TIME_REMAINING">%1$s</xliff:g> 남음"</string> diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml index 44b01472b232..e565e444536c 100644 --- a/packages/SettingsLib/res/values-ky/strings.xml +++ b/packages/SettingsLib/res/values-ky/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> менен алмаштырылган"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Батареяны коргоо үчүн кубаттоо тындырылды"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Кубаттоо шайманын текшериңиз"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Болжол менен <xliff:g id="TIME_REMAINING">%1$s</xliff:g> калды"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Болжол менен <xliff:g id="TIME_REMAINING">%1$s</xliff:g> калды (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Колдонгонуңузга караганда болжол менен <xliff:g id="TIME_REMAINING">%1$s</xliff:g> калды"</string> diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml index 70e9b68551c3..6caaf952b364 100644 --- a/packages/SettingsLib/res/values-lo/strings.xml +++ b/packages/SettingsLib/res/values-lo/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"ຖືກແທນໂດຍ <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - ຢຸດການສາກຊົ່ວຄາວເພື່ອປົກປ້ອງແບັດເຕີຣີ"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - ກວດສອບອຸປະກອນເສີມສຳລັບການສາກ"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"ເຫຼືອອີກປະມານ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"ເຫຼືອອີກປະມານ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"ເຫຼືອອີກປະມານ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ອ້າງອີງຈາກການນຳໃຊ້ຂອງທ່ານ"</string> diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml index d50634c1ead1..70bcc2100ddd 100644 --- a/packages/SettingsLib/res/values-mk/strings.xml +++ b/packages/SettingsLib/res/values-mk/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Прескокнато според <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - полнењето е паузирано за да се заштити батеријата"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Проверете го додатокот за полнење"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Уште околу <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Уште околу <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Уште околу <xliff:g id="TIME_REMAINING">%1$s</xliff:g> според вашето користење"</string> diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml index 085b7c16180c..9a297e5fb4d8 100644 --- a/packages/SettingsLib/res/values-ml/strings.xml +++ b/packages/SettingsLib/res/values-ml/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ഉപയോഗിച്ച് അസാധുവാക്കി"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - ബാറ്ററി പരിരക്ഷിക്കാൻ ചാർജിംഗ് ഹോൾഡിലാണ്"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - ചാർജിംഗ് ആക്സസറി പരിശോധിക്കുക"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"ഏതാണ്ട് <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ശേഷിക്കുന്നു"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"ഏതാണ്ട് <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ശേഷിക്കുന്നു (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"നിങ്ങളുടെ ഉപയോഗത്തെ അടിസ്ഥാനമാക്കി ഏതാണ്ട് <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ശേഷിക്കുന്നു"</string> diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml index 60a31e8e3c3b..000e306afef3 100644 --- a/packages/SettingsLib/res/values-mn/strings.xml +++ b/packages/SettingsLib/res/values-mn/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Давхарласан <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Батарейг хамгаалахын тулд цэнэглэхийг хүлээлгэсэн"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Цэнэглэх нэмэлт хэрэгслийг шалгах"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ойролцоогоор <xliff:g id="TIME_REMAINING">%1$s</xliff:g> үлдсэн"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Ойролцоогоор <xliff:g id="TIME_REMAINING">%1$s</xliff:g> үлдсэн (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Таны хэрэглээнд үндэслэн ойролцоогоор <xliff:g id="TIME_REMAINING">%1$s</xliff:g> үлдсэн"</string> diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml index 32de0e5f5daa..6af0fbdc0ac8 100644 --- a/packages/SettingsLib/res/values-mr/strings.xml +++ b/packages/SettingsLib/res/values-mr/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> द्वारे अधिलिखित"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - बॅटरीचे संरक्षण करण्यासाठी चार्जिंग थांबवले आहे"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्जिंगसंबंधित ॲक्सेसरी तपासा"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"अंदाजे <xliff:g id="TIME_REMAINING">%1$s</xliff:g> बाकी आहे"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"अंदाजे <xliff:g id="TIME_REMAINING">%1$s</xliff:g> बाकी आहे (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"तुमच्या वापरावर आधारित अंदाजे <xliff:g id="TIME_REMAINING">%1$s</xliff:g> बाकी आहे"</string> diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml index f0abd94dd9d1..621b4694026b 100644 --- a/packages/SettingsLib/res/values-ms/strings.xml +++ b/packages/SettingsLib/res/values-ms/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Diatasi oleh <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Pengecasan ditunda untuk melindungi bateri"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Periksa aksesori pengecasan"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Kira-kira <xliff:g id="TIME_REMAINING">%1$s</xliff:g> lagi"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Kira-kira <xliff:g id="TIME_REMAINING">%1$s</xliff:g> lagi (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Kira-kira <xliff:g id="TIME_REMAINING">%1$s</xliff:g> lagi berdasarkan penggunaan anda"</string> diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml index 3d942855e00e..60161c344ebb 100644 --- a/packages/SettingsLib/res/values-my/strings.xml +++ b/packages/SettingsLib/res/values-my/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> မှ ကျော်၍ လုပ်ထားသည်။"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - ဘက်ထရီကာကွယ်ရန် အားသွင်းခြင်းကို ခဏရပ်ထားသည်"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - အားသွင်းပစ္စည်း စစ်ရန်"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ခန့် ကျန်သည်"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ခန့် ကျန်သည် (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"သင်၏ အသုံးပြုမှု အပေါ် မူတည်၍ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ခန့် ကျန်သည်"</string> diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml index d286da13d8f1..256a71bb57c4 100644 --- a/packages/SettingsLib/res/values-nb/strings.xml +++ b/packages/SettingsLib/res/values-nb/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overstyres av <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Ladingen er satt på vent for å beskytte batteriet"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Sjekk ladetilbehøret"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Omtrent <xliff:g id="TIME_REMAINING">%1$s</xliff:g> igjen"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Omtrent <xliff:g id="TIME_REMAINING">%1$s</xliff:g> igjen (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Omtrent <xliff:g id="TIME_REMAINING">%1$s</xliff:g> igjen basert på bruken din"</string> diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml index 146418c83e69..fdd965a6e52a 100644 --- a/packages/SettingsLib/res/values-ne/strings.xml +++ b/packages/SettingsLib/res/values-ne/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> द्वारा अधिरोहित"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - ब्याट्री जोगाउन चार्जिङ होल्ड गरिएको छ"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्जिङ एक्सेसरी जाँच्नुहोस्"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"लगभग <xliff:g id="TIME_REMAINING">%1$s</xliff:g> बाँकी छ"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"लगभग <xliff:g id="TIME_REMAINING">%1$s</xliff:g> बाँकी छ (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"तपाईंको प्रयोगको आधारमा लगभग <xliff:g id="TIME_REMAINING">%1$s</xliff:g> बाँकी छ"</string> diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml index f5ae2540d7ae..72e57af602e8 100644 --- a/packages/SettingsLib/res/values-nl/strings.xml +++ b/packages/SettingsLib/res/values-nl/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overschreven door <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g>: opladen is in de wacht gezet om de batterij te beschermen"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Oplaadaccessoire checken"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Nog ongeveer <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Nog ongeveer <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Nog ongeveer <xliff:g id="TIME_REMAINING">%1$s</xliff:g> op basis van je gebruik"</string> diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml index b71cf0ebd125..723af105f8ea 100644 --- a/packages/SettingsLib/res/values-or/strings.xml +++ b/packages/SettingsLib/res/values-or/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ଦ୍ୱାରା ଓଭର୍ରାଇଡ୍ କରାଯାଇଛି"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - ବେଟେରୀକୁ ସୁରକ୍ଷିତ ରଖିବା ପାଇଁ ଚାର୍ଜିଂ ହୋଲ୍ଡରେ ଅଛି"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - ଚାର୍ଜିଂ ଆକସେସୋରୀକୁ ଯାଞ୍ଚ କରନ୍ତୁ"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"ପାଖାପାଖି <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ବଳକା ଅଛି"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"ପାଖାପାଖି <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ପାଇଁ (<xliff:g id="LEVEL">%2$s</xliff:g>) ବଳକା ଅଛି"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"ଆପଣଙ୍କ ବ୍ୟବହାରକୁ ଆଧାର କରି ପାଖାପାଖି <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ବଳକା ଅଛି"</string> diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml index 76d49214761d..8a77c12f6c0c 100644 --- a/packages/SettingsLib/res/values-pa/strings.xml +++ b/packages/SettingsLib/res/values-pa/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ਦੁਆਰਾ ਓਵਰਰਾਈਡ ਕੀਤਾ"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - ਬੈਟਰੀ ਦੀ ਸੁਰੱਖਿਆ ਲਈ ਚਾਰਜਿੰਗ ਨੂੰ ਰੋਕਿਆ ਗਿਆ ਹੈ"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - ਚਾਰਜਿੰਗ ਐਕਸੈਸਰੀ ਦੀ ਜਾਂਚ ਕਰੋ"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"ਲਗਭਗ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ਬਾਕੀ"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"ਲਗਭਗ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ਬਾਕੀ (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"ਤੁਹਾਡੀ ਵਰਤੋਂ ਦੇ ਆਧਾਰ \'ਤੇ ਲਗਭਗ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ਬਾਕੀ"</string> diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml index 1b76b6f0fd03..375a35f6edef 100644 --- a/packages/SettingsLib/res/values-pl/strings.xml +++ b/packages/SettingsLib/res/values-pl/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Nadpisana przez <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – wstrzymano ładowanie, aby chronić baterię"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – sprawdź akcesoria do ładowania"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Jeszcze około <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Jeszcze około <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Jeszcze około <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (na podstawie Twojego sposobu korzystania)"</string> diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml index bdfff4aa93c7..55b9db509200 100644 --- a/packages/SettingsLib/res/values-pt-rBR/strings.xml +++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Substituído por <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Carregamento suspenso para proteger a bateria"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g>: verifique o acessório de carregamento"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Tempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Tempo restante aproximado, com base no seu uso: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml index 6fb851b7a32e..ca704e7bbb87 100644 --- a/packages/SettingsLib/res/values-pt-rPT/strings.xml +++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Substituído por <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Carregamento em espera para proteger a bateria"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Verificar acessório de carregamento"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Resta(m) cerca de <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Resta(m) cerca de <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Resta(m) cerca de <xliff:g id="TIME_REMAINING">%1$s</xliff:g> com base na sua utilização"</string> diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml index bdfff4aa93c7..55b9db509200 100644 --- a/packages/SettingsLib/res/values-pt/strings.xml +++ b/packages/SettingsLib/res/values-pt/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Substituído por <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Carregamento suspenso para proteger a bateria"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g>: verifique o acessório de carregamento"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Tempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Tempo restante aproximado, com base no seu uso: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml index e8d68520b5c4..fa89c1a4c3d8 100644 --- a/packages/SettingsLib/res/values-ro/strings.xml +++ b/packages/SettingsLib/res/values-ro/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Valoare înlocuită de <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Încărcarea s-a întrerupt pentru a proteja bateria"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Verifică accesoriul de încărcare"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Timp aproximativ rămas: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Timp aproximativ rămas: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"În baza utilizării, timpul rămas este de aproximativ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml index ef1e817614d7..d85307098a35 100644 --- a/packages/SettingsLib/res/values-ru/strings.xml +++ b/packages/SettingsLib/res/values-ru/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Новая настройка: <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"Уровень заряда – <xliff:g id="PERCENTAGE">%1$s</xliff:g>. <xliff:g id="TIME_STRING">%2$s</xliff:g>."</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g>, зарядка приостановлена для защиты батареи"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g>, проверьте зарядное устройство"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Заряда хватит примерно на <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Заряда (<xliff:g id="LEVEL">%2$s</xliff:g>) хватит примерно на <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Заряда хватит примерно на <xliff:g id="TIME_REMAINING">%1$s</xliff:g> при текущем уровне расхода"</string> diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml index cf83ee8cc27f..093f216afccc 100644 --- a/packages/SettingsLib/res/values-si/strings.xml +++ b/packages/SettingsLib/res/values-si/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> මගින් ඉක්මවන ලදී"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - බැටරිය ආරක්ෂා කිරීම සඳහා ආරෝපණය රඳවා තබා ඇත"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - ආරෝපණ ආයිත්තම පරීක්ෂා කරන්න"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ක් පමණ ඉතිරියි"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ක් පමණ ඉතිරියි (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"ඔබේ භාවිතය මත පදනම්ව <xliff:g id="TIME_REMAINING">%1$s</xliff:g> පමණ ඉතිරිව ඇත"</string> diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml index 577a6c1ff15c..1c8293312871 100644 --- a/packages/SettingsLib/res/values-sk/strings.xml +++ b/packages/SettingsLib/res/values-sk/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Prekonané predvoľbou <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – nabíjanie je pozastavené, aby sa chránila batéria"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – skontrolujte nabíjacie príslušenstvo"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ešte približne <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Zostáva približne <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Zostáva približne <xliff:g id="TIME_REMAINING">%1$s</xliff:g> – závisí to od intenzity využitia"</string> diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml index 7acd64d1ab4e..f97dd78f8179 100644 --- a/packages/SettingsLib/res/values-sl/strings.xml +++ b/packages/SettingsLib/res/values-sl/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Preglasila nastavitev: <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Zaradi zaščite baterije je polnjenje na čakanju"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Preverite pripomoček za polnjenje"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Še približno <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Še približno <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Glede na način uporabe še približno <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml index a2b2042db952..a802f115da4a 100644 --- a/packages/SettingsLib/res/values-sq/strings.xml +++ b/packages/SettingsLib/res/values-sq/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Mbivendosur nga <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Karikimi është vendosur në pritje për të mbrojtur baterinë"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Kontrollo aksesorin e karikimit"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Rreth <xliff:g id="TIME_REMAINING">%1$s</xliff:g> të mbetura"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Rreth <xliff:g id="TIME_REMAINING">%1$s</xliff:g> të mbetura (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Rreth <xliff:g id="TIME_REMAINING">%1$s</xliff:g> të mbetura bazuar në përdorimin tënd"</string> diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml index 14dd7720ce79..df2215911673 100644 --- a/packages/SettingsLib/res/values-sr/strings.xml +++ b/packages/SettingsLib/res/values-sr/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Замењује га <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>–<xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – пуњење је на чекању да би се заштитила батерија"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Проверите додатну опрему за пуњење"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Преостало је око <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Преостало је око <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Преостало је око <xliff:g id="TIME_REMAINING">%1$s</xliff:g> на основу коришћења"</string> diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml index c9262e9d2494..79b8399527a0 100644 --- a/packages/SettingsLib/res/values-sv/strings.xml +++ b/packages/SettingsLib/res/values-sv/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Har åsidosatts av <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Laddningen har pausats för att skydda batteriet"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Kontrollera laddningstillbehöret"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Cirka <xliff:g id="TIME_REMAINING">%1$s</xliff:g> kvar"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Cirka <xliff:g id="TIME_REMAINING">%1$s</xliff:g> kvar (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Cirka <xliff:g id="TIME_REMAINING">%1$s</xliff:g> kvar utifrån din användning"</string> diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml index 92c9c7c573cd..c0ace5805e63 100644 --- a/packages/SettingsLib/res/values-sw/strings.xml +++ b/packages/SettingsLib/res/values-sw/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Imetanguliwa na <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Imesitisha kuchaji ili kulinda betri yako"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Kagua kifaa cha kuchaji"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Zimesalia takribani <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Zimesalia takribani <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Zimesalia takribani <xliff:g id="TIME_REMAINING">%1$s</xliff:g> kulingana na jinsi unavyoitumia"</string> diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml index ec73fbed7264..ebca26dc3063 100644 --- a/packages/SettingsLib/res/values-ta/strings.xml +++ b/packages/SettingsLib/res/values-ta/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> மூலம் மேலெழுதப்பட்டது"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - பேட்டரியைப் பாதுகாப்பதற்காகச் சார்ஜிங் இடைநிறுத்தப்பட்டுள்ளது"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - சார்ஜிங் துணைக்கருவியைச் சரிபாருங்கள்"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"கிட்டத்தட்ட <xliff:g id="TIME_REMAINING">%1$s</xliff:g> மீதமுள்ளது"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"கிட்டத்தட்ட <xliff:g id="TIME_REMAINING">%1$s</xliff:g> மீதமுள்ளது (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"உபயோகத்தின் அடிப்படையில் கிட்டத்தட்ட <xliff:g id="TIME_REMAINING">%1$s</xliff:g> மீதமுள்ளது"</string> diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml index 0ffdf6a680ae..cf3c08acc54c 100644 --- a/packages/SettingsLib/res/values-te/strings.xml +++ b/packages/SettingsLib/res/values-te/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ద్వారా భర్తీ చేయబడింది"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - బ్యాటరీని రక్షించడానికి ఛార్జింగ్ హోల్డ్లో ఉంచబడింది"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - ఛార్జింగ్ యాక్సెసరీని ఎంచుకోండి"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> సమయం మిగిలి ఉంది"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"దాదాపు <xliff:g id="TIME_REMAINING">%1$s</xliff:g> సమయం మిగిలి ఉంది (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"మీ వినియోగం ఆధారంగా దాదాపు <xliff:g id="TIME_REMAINING">%1$s</xliff:g> సమయం మిగిలి ఉంది"</string> diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml index 7bccc25c65dd..a74265ee3e9a 100644 --- a/packages/SettingsLib/res/values-th/strings.xml +++ b/packages/SettingsLib/res/values-th/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"แทนที่โดย <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - หยุดการชาร์จชั่วคราวเพื่อถนอมแบตเตอรี่"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - ตรวจสอบอุปกรณ์เสริมสำหรับการชาร์จ"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"เหลืออีกประมาณ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"เหลืออีกประมาณ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"เหลืออีกประมาณ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ขึ้นอยู่กับการใช้งานของคุณ"</string> diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml index 19ba24fcbda4..18d3d3b626fa 100644 --- a/packages/SettingsLib/res/values-tl/strings.xml +++ b/packages/SettingsLib/res/values-tl/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Na-override ng <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Naka-hold ang pag-charge para protektahan ang baterya"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Suriin ang accessory sa pag-charge"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Humigit-kumulang <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ang natitira"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Humigit-kumulang <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ang natitira (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Humigit-kumulang <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ang natitira batay sa iyong paggamit"</string> diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml index 80d2fe294ea3..c4241150ad12 100644 --- a/packages/SettingsLib/res/values-tr/strings.xml +++ b/packages/SettingsLib/res/values-tr/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> tarafından geçersiz kılındı"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Pili korumak için şarj beklemede"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Şarj aksesuarını kontrol edin"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Yaklaşık <xliff:g id="TIME_REMAINING">%1$s</xliff:g> kaldı"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Yaklaşık <xliff:g id="TIME_REMAINING">%1$s</xliff:g> kaldı (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Kullanımınıza dayalı olarak yaklaşık <xliff:g id="TIME_REMAINING">%1$s</xliff:g> kaldı"</string> diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml index b1c64f8222b3..97592f970b67 100644 --- a/packages/SettingsLib/res/values-uk/strings.xml +++ b/packages/SettingsLib/res/values-uk/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Замінено на <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – заряджання призупинено, щоб захистити акумулятор"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – перевірте зарядний пристрій"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Залишилося приблизно <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Залишилося приблизно <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Згідно з даними про використання залишилося приблизно <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml index f71f4a2174b1..9d3405234e5b 100644 --- a/packages/SettingsLib/res/values-ur/strings.xml +++ b/packages/SettingsLib/res/values-ur/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> کے ذریعہ منسوخ کردیا گیا"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - بیٹری کی حفاظت کرنے کے لیے چارجنگ ہولڈ پر ہے"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - چارجنگ ایکسیسری چیک کریں"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"تقریباً <xliff:g id="TIME_REMAINING">%1$s</xliff:g> باقی ہے"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"تقریباً <xliff:g id="TIME_REMAINING">%1$s</xliff:g> باقی ہے (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"آپ کے استعمال کی بنیاد پر تقریباً <xliff:g id="TIME_REMAINING">%1$s</xliff:g> باقی ہے"</string> diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml index a3ef6b64d9f9..bd38bb44df6e 100644 --- a/packages/SettingsLib/res/values-uz/strings.xml +++ b/packages/SettingsLib/res/values-uz/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> bilan almashtirildi"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Batareyani himoyalash uchun quvvatlash toʻxtatildi"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Quvvatlash aksessuarini tekshiring"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Taxminan <xliff:g id="TIME_REMAINING">%1$s</xliff:g> qoldi"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Taxminan <xliff:g id="TIME_REMAINING">%1$s</xliff:g> qoldi (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Quvvati tugashiga taxminan <xliff:g id="TIME_REMAINING">%1$s</xliff:g> qoldi"</string> diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml index 3ac91fac6cc0..5b91df3e44a2 100644 --- a/packages/SettingsLib/res/values-vi/strings.xml +++ b/packages/SettingsLib/res/values-vi/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Bị ghi đè bởi <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Đang tạm ngưng sạc để bảo vệ pin"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Hãy kiểm tra phụ kiện sạc"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Còn khoảng <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Còn khoảng <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Còn khoảng <xliff:g id="TIME_REMAINING">%1$s</xliff:g> dựa trên mức sử dụng của bạn"</string> diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml index 7ff6ceec5fa1..594922a4050d 100644 --- a/packages/SettingsLib/res/values-zh-rCN/strings.xml +++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"已被“<xliff:g id="TITLE">%1$s</xliff:g>”覆盖"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - 为保护电池,已暂停充电"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - 请检查充电配件"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"大约还可使用<xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"大约还可使用<xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"根据您的使用情况,大约还可使用<xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml index 53b55bba0bd0..b3aafae4a3c6 100644 --- a/packages/SettingsLib/res/values-zh-rHK/strings.xml +++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"已由「<xliff:g id="TITLE">%1$s</xliff:g>」覆寫"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - 為保護電池,目前暫停充電"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - 檢查充電配件"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"還有大約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"還有大約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"根據你的使用情況,還有大約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml index f9ee07d07fd0..c3be21a93d8c 100644 --- a/packages/SettingsLib/res/values-zh-rTW/strings.xml +++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"已改為<xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - 為保護電池,目前暫停充電"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> · 請檢查充電配件"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"還能使用約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"目前電量為 <xliff:g id="LEVEL">%2$s</xliff:g>,還能使用約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"根據你的使用情形,還能使用約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml index 8ff64d141b94..f6aaf813af84 100644 --- a/packages/SettingsLib/res/values-zu/strings.xml +++ b/packages/SettingsLib/res/values-zu/strings.xml @@ -457,8 +457,7 @@ <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Igitshezwe ngaphezulu yi-<xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Ukushaja kumisiwe ukuze kuvikelwe ibhethri"</string> - <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) --> - <skip /> + <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> - Hlola insiza yokushaja"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Cishe u-<xliff:g id="TIME_REMAINING">%1$s</xliff:g> osele"</string> <string name="power_discharging_duration" msgid="1076561255466053220">"Cishe u-<xliff:g id="TIME_REMAINING">%1$s</xliff:g> osele (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string> <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Cishe u-<xliff:g id="TIME_REMAINING">%1$s</xliff:g> osele ngokususelwe ekusebenziseni wakho"</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 61c3ce7f6988..c2c82b35317b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -1764,40 +1764,4 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> boolean getUnpairing() { return mUnpairing; } - - ListenableFuture<Void> syncProfileForMemberDevice() { - return ThreadUtils.getBackgroundExecutor() - .submit( - () -> { - List<Pair<LocalBluetoothProfile, Boolean>> toSync = - Stream.of( - mProfileManager.getA2dpProfile(), - mProfileManager.getHeadsetProfile(), - mProfileManager.getHearingAidProfile(), - mProfileManager.getLeAudioProfile(), - mProfileManager.getLeAudioBroadcastAssistantProfile()) - .filter(Objects::nonNull) - .map(profile -> new Pair<>(profile, profile.isEnabled(mDevice))) - .toList(); - - for (var t : toSync) { - LocalBluetoothProfile profile = t.first; - boolean enabledForMain = t.second; - - for (var member : mMemberDevices) { - BluetoothDevice btDevice = member.getDevice(); - - if (enabledForMain != profile.isEnabled(btDevice)) { - Log.d(TAG, "Syncing profile " + profile + " to " - + enabledForMain + " for member device " - + btDevice.getAnonymizedAddress() + " of main device " - + mDevice.getAnonymizedAddress()); - profile.setEnabled(btDevice, enabledForMain); - } - } - } - return null; - } - ); - } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java index 32eec7e709af..4e52c77f27b4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java @@ -363,7 +363,6 @@ public class CachedBluetoothDeviceManager { if (profileId == BluetoothProfile.HEADSET || profileId == BluetoothProfile.A2DP || profileId == BluetoothProfile.LE_AUDIO - || profileId == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT || profileId == BluetoothProfile.CSIP_SET_COORDINATOR) { return mCsipDeviceManager.onProfileConnectionStateChangedIfProcessed(cachedDevice, state); diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java index e67ec48d3401..a49314aae1b3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java @@ -379,7 +379,6 @@ public class CsipDeviceManager { if (hasChanged) { log("addMemberDevicesIntoMainDevice: After changed, CachedBluetoothDevice list: " + mCachedDevices); - preferredMainDevice.syncProfileForMemberDevice(); } return hasChanged; } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java index ca47efdc5df3..1069b715d946 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java @@ -346,11 +346,15 @@ public class HearingAidDeviceManager { } else { long hiSyncId = asha.getHiSyncId(cachedDevice.getDevice()); if (isValidHiSyncId(hiSyncId)) { - final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder() + final HearingAidInfo info = new HearingAidInfo.Builder() .setAshaDeviceSide(asha.getDeviceSide(cachedDevice.getDevice())) .setAshaDeviceMode(asha.getDeviceMode(cachedDevice.getDevice())) - .setHiSyncId(hiSyncId); - return infoBuilder.build(); + .setHiSyncId(hiSyncId) + .build(); + if (DEBUG) { + Log.d(TAG, "generateHearingAidInfo, " + cachedDevice + ", info=" + info); + } + return info; } } @@ -358,15 +362,20 @@ public class HearingAidDeviceManager { final LeAudioProfile leAudioProfile = profileManager.getLeAudioProfile(); if (hapClientProfile == null || leAudioProfile == null) { Log.w(TAG, "HapClientProfile or LeAudioProfile is not supported on this device"); - } else { + } else if (cachedDevice.getProfiles().stream().anyMatch( + p -> p instanceof HapClientProfile)) { int audioLocation = leAudioProfile.getAudioLocation(cachedDevice.getDevice()); int hearingAidType = hapClientProfile.getHearingAidType(cachedDevice.getDevice()); if (audioLocation != BluetoothLeAudio.AUDIO_LOCATION_INVALID && hearingAidType != HapClientProfile.HearingAidType.TYPE_INVALID) { - final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder() + final HearingAidInfo info = new HearingAidInfo.Builder() .setLeAudioLocation(audioLocation) - .setHapDeviceType(hearingAidType); - return infoBuilder.build(); + .setHapDeviceType(hearingAidType) + .build(); + if (DEBUG) { + Log.d(TAG, "generateHearingAidInfo, " + cachedDevice + ", info=" + info); + } + return info; } } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index bdb58719b1a8..5f026c451152 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -74,6 +74,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -84,6 +85,7 @@ public abstract class InfoMediaManager extends MediaManager { private static final String TAG = "InfoMediaManager"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + protected final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>(); /** Checked exception that signals the specified package is not present in the system. */ public static class PackageNotAvailableException extends Exception { @@ -227,6 +229,16 @@ public abstract class InfoMediaManager extends MediaManager { Api34Impl.onRouteListingPreferenceUpdated(routeListingPreference, mPreferenceItemMap); } + protected final MediaDevice findMediaDevice(@NonNull String id) { + for (MediaDevice mediaDevice : mMediaDevices) { + if (mediaDevice.getId().equals(id)) { + return mediaDevice; + } + } + Log.e(TAG, "findMediaDevice() can't find device with id: " + id); + return null; + } + /** * Get current device that played media. * @return MediaDevice @@ -433,7 +445,7 @@ public abstract class InfoMediaManager extends MediaManager { protected final synchronized void refreshDevices() { rebuildDeviceList(); - dispatchDeviceListAdded(); + dispatchDeviceListAdded(mMediaDevices); } // MediaRoute2Info.getType was made public on API 34, but exists since API 30. diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java index 8bebd6ee3448..d562c8a82f2d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java @@ -15,9 +15,9 @@ */ package com.android.settingslib.media; +import android.annotation.NonNull; import android.app.Notification; import android.content.Context; -import android.util.Log; import java.util.ArrayList; import java.util.Collection; @@ -29,10 +29,7 @@ import java.util.concurrent.CopyOnWriteArrayList; */ public abstract class MediaManager { - private static final String TAG = "MediaManager"; - protected final Collection<MediaDeviceCallback> mCallbacks = new CopyOnWriteArrayList<>(); - protected final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>(); protected Context mContext; protected Notification mNotification; @@ -54,19 +51,9 @@ public abstract class MediaManager { } } - protected MediaDevice findMediaDevice(String id) { - for (MediaDevice mediaDevice : mMediaDevices) { - if (mediaDevice.getId().equals(id)) { - return mediaDevice; - } - } - Log.e(TAG, "findMediaDevice() can't found device"); - return null; - } - - protected void dispatchDeviceListAdded() { + protected void dispatchDeviceListAdded(@NonNull List<MediaDevice> devices) { for (MediaDeviceCallback callback : getCallbacks()) { - callback.onDeviceListAdded(new ArrayList<>(mMediaDevices)); + callback.onDeviceListAdded(new ArrayList<>(devices)); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java deleted file mode 100644 index 69f83a4dfa3c..000000000000 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java +++ /dev/null @@ -1,447 +0,0 @@ -/* - * Copyright (C) 2017 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.wifi; - -import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED; -import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.getMaxNetworkSelectionDisableReason; - -import static com.android.settingslib.flags.Flags.newStatusBarIcons; - -import android.content.Context; -import android.content.Intent; -import android.graphics.drawable.Drawable; -import android.icu.text.MessageFormat; -import android.net.wifi.ScanResult; -import android.net.wifi.WifiConfiguration; -import android.net.wifi.WifiConfiguration.NetworkSelectionStatus; -import android.net.wifi.WifiInfo; -import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo; -import android.os.Bundle; -import android.os.SystemClock; -import android.util.Log; - -import androidx.annotation.VisibleForTesting; - -import com.android.settingslib.R; - -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; - -public class WifiUtils { - - private static final String TAG = "WifiUtils"; - - private static final int INVALID_RSSI = -127; - - /** - * The intent action shows Wi-Fi dialog to connect Wi-Fi network. - * <p> - * Input: The calling package should put the chosen - * com.android.wifitrackerlib.WifiEntry#getKey() to a string extra in the request bundle into - * the {@link #EXTRA_CHOSEN_WIFI_ENTRY_KEY}. - * <p> - * Output: Nothing. - */ - @VisibleForTesting - static final String ACTION_WIFI_DIALOG = "com.android.settings.WIFI_DIALOG"; - - /** - * Specify a key that indicates the WifiEntry to be configured. - */ - @VisibleForTesting - static final String EXTRA_CHOSEN_WIFI_ENTRY_KEY = "key_chosen_wifientry_key"; - - /** - * The lookup key for a boolean that indicates whether a chosen WifiEntry request to connect to. - * {@code true} means a chosen WifiEntry request to connect to. - */ - @VisibleForTesting - static final String EXTRA_CONNECT_FOR_CALLER = "connect_for_caller"; - - /** - * The intent action shows network details settings to allow configuration of Wi-Fi. - * <p> - * In some cases, a matching Activity may not exist, so ensure you - * safeguard against this. - * <p> - * Input: The calling package should put the chosen - * com.android.wifitrackerlib.WifiEntry#getKey() to a string extra in the request bundle into - * the {@link #KEY_CHOSEN_WIFIENTRY_KEY}. - * <p> - * Output: Nothing. - */ - public static final String ACTION_WIFI_DETAILS_SETTINGS = - "android.settings.WIFI_DETAILS_SETTINGS"; - public static final String KEY_CHOSEN_WIFIENTRY_KEY = "key_chosen_wifientry_key"; - public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args"; - - static final int[] WIFI_PIE = getIconsBasedOnFlag(); - - private static int[] getIconsBasedOnFlag() { - if (newStatusBarIcons()) { - return new int[] { - R.drawable.ic_wifi_0, - R.drawable.ic_wifi_1, - R.drawable.ic_wifi_2, - R.drawable.ic_wifi_3, - R.drawable.ic_wifi_4 - }; - } else { - return new int[] { - com.android.internal.R.drawable.ic_wifi_signal_0, - com.android.internal.R.drawable.ic_wifi_signal_1, - com.android.internal.R.drawable.ic_wifi_signal_2, - com.android.internal.R.drawable.ic_wifi_signal_3, - com.android.internal.R.drawable.ic_wifi_signal_4 - }; - } - } - - static final int[] NO_INTERNET_WIFI_PIE = getErrorIconsBasedOnFlag(); - - private static int [] getErrorIconsBasedOnFlag() { - if (newStatusBarIcons()) { - return new int[] { - R.drawable.ic_wifi_0_error, - R.drawable.ic_wifi_1_error, - R.drawable.ic_wifi_2_error, - R.drawable.ic_wifi_3_error, - R.drawable.ic_wifi_4_error - }; - } else { - return new int[] { - R.drawable.ic_no_internet_wifi_signal_0, - R.drawable.ic_no_internet_wifi_signal_1, - R.drawable.ic_no_internet_wifi_signal_2, - R.drawable.ic_no_internet_wifi_signal_3, - R.drawable.ic_no_internet_wifi_signal_4 - }; - } - } - - public static String buildLoggingSummary(AccessPoint accessPoint, WifiConfiguration config) { - final StringBuilder summary = new StringBuilder(); - final WifiInfo info = accessPoint.getInfo(); - // Add RSSI/band information for this config, what was seen up to 6 seconds ago - // verbose WiFi Logging is only turned on thru developers settings - if (accessPoint.isActive() && info != null) { - summary.append(" f=" + Integer.toString(info.getFrequency())); - } - summary.append(" " + getVisibilityStatus(accessPoint)); - if (config != null - && (config.getNetworkSelectionStatus().getNetworkSelectionStatus() - != NETWORK_SELECTION_ENABLED)) { - summary.append(" (" + config.getNetworkSelectionStatus().getNetworkStatusString()); - if (config.getNetworkSelectionStatus().getDisableTime() > 0) { - long now = System.currentTimeMillis(); - long diff = (now - config.getNetworkSelectionStatus().getDisableTime()) / 1000; - long sec = diff % 60; //seconds - long min = (diff / 60) % 60; //minutes - long hour = (min / 60) % 60; //hours - summary.append(", "); - if (hour > 0) summary.append(Long.toString(hour) + "h "); - summary.append(Long.toString(min) + "m "); - summary.append(Long.toString(sec) + "s "); - } - summary.append(")"); - } - - if (config != null) { - NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus(); - for (int reason = 0; reason <= getMaxNetworkSelectionDisableReason(); reason++) { - if (networkStatus.getDisableReasonCounter(reason) != 0) { - summary.append(" ") - .append(NetworkSelectionStatus - .getNetworkSelectionDisableReasonString(reason)) - .append("=") - .append(networkStatus.getDisableReasonCounter(reason)); - } - } - } - - return summary.toString(); - } - - /** - * Returns the visibility status of the WifiConfiguration. - * - * @return autojoin debugging information - * TODO: use a string formatter - * ["rssi 5Ghz", "num results on 5GHz" / "rssi 5Ghz", "num results on 5GHz"] - * For instance [-40,5/-30,2] - */ - @VisibleForTesting - static String getVisibilityStatus(AccessPoint accessPoint) { - final WifiInfo info = accessPoint.getInfo(); - StringBuilder visibility = new StringBuilder(); - StringBuilder scans24GHz = new StringBuilder(); - StringBuilder scans5GHz = new StringBuilder(); - StringBuilder scans60GHz = new StringBuilder(); - String bssid = null; - - if (accessPoint.isActive() && info != null) { - bssid = info.getBSSID(); - if (bssid != null) { - visibility.append(" ").append(bssid); - } - visibility.append(" standard = ").append(info.getWifiStandard()); - visibility.append(" rssi=").append(info.getRssi()); - visibility.append(" "); - visibility.append(" score=").append(info.getScore()); - if (accessPoint.getSpeed() != AccessPoint.Speed.NONE) { - visibility.append(" speed=").append(accessPoint.getSpeedLabel()); - } - visibility.append(String.format(" tx=%.1f,", info.getSuccessfulTxPacketsPerSecond())); - visibility.append(String.format("%.1f,", info.getRetriedTxPacketsPerSecond())); - visibility.append(String.format("%.1f ", info.getLostTxPacketsPerSecond())); - visibility.append(String.format("rx=%.1f", info.getSuccessfulRxPacketsPerSecond())); - } - - int maxRssi5 = INVALID_RSSI; - int maxRssi24 = INVALID_RSSI; - int maxRssi60 = INVALID_RSSI; - final int maxDisplayedScans = 4; - int num5 = 0; // number of scanned BSSID on 5GHz band - int num24 = 0; // number of scanned BSSID on 2.4Ghz band - int num60 = 0; // number of scanned BSSID on 60Ghz band - int numBlockListed = 0; - - // TODO: sort list by RSSI or age - long nowMs = SystemClock.elapsedRealtime(); - for (ScanResult result : accessPoint.getScanResults()) { - if (result == null) { - continue; - } - if (result.frequency >= AccessPoint.LOWER_FREQ_5GHZ - && result.frequency <= AccessPoint.HIGHER_FREQ_5GHZ) { - // Strictly speaking: [4915, 5825] - num5++; - - if (result.level > maxRssi5) { - maxRssi5 = result.level; - } - if (num5 <= maxDisplayedScans) { - scans5GHz.append( - verboseScanResultSummary(accessPoint, result, bssid, - nowMs)); - } - } else if (result.frequency >= AccessPoint.LOWER_FREQ_24GHZ - && result.frequency <= AccessPoint.HIGHER_FREQ_24GHZ) { - // Strictly speaking: [2412, 2482] - num24++; - - if (result.level > maxRssi24) { - maxRssi24 = result.level; - } - if (num24 <= maxDisplayedScans) { - scans24GHz.append( - verboseScanResultSummary(accessPoint, result, bssid, - nowMs)); - } - } else if (result.frequency >= AccessPoint.LOWER_FREQ_60GHZ - && result.frequency <= AccessPoint.HIGHER_FREQ_60GHZ) { - // Strictly speaking: [60000, 61000] - num60++; - - if (result.level > maxRssi60) { - maxRssi60 = result.level; - } - if (num60 <= maxDisplayedScans) { - scans60GHz.append( - verboseScanResultSummary(accessPoint, result, bssid, - nowMs)); - } - } - } - visibility.append(" ["); - if (num24 > 0) { - visibility.append("(").append(num24).append(")"); - if (num24 > maxDisplayedScans) { - visibility.append("max=").append(maxRssi24).append(","); - } - visibility.append(scans24GHz.toString()); - } - visibility.append(";"); - if (num5 > 0) { - visibility.append("(").append(num5).append(")"); - if (num5 > maxDisplayedScans) { - visibility.append("max=").append(maxRssi5).append(","); - } - visibility.append(scans5GHz.toString()); - } - visibility.append(";"); - if (num60 > 0) { - visibility.append("(").append(num60).append(")"); - if (num60 > maxDisplayedScans) { - visibility.append("max=").append(maxRssi60).append(","); - } - visibility.append(scans60GHz.toString()); - } - if (numBlockListed > 0) { - visibility.append("!").append(numBlockListed); - } - visibility.append("]"); - - return visibility.toString(); - } - - @VisibleForTesting - /* package */ static String verboseScanResultSummary(AccessPoint accessPoint, ScanResult result, - String bssid, long nowMs) { - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append(" \n{").append(result.BSSID); - if (result.BSSID.equals(bssid)) { - stringBuilder.append("*"); - } - stringBuilder.append("=").append(result.frequency); - stringBuilder.append(",").append(result.level); - int speed = getSpecificApSpeed(result, accessPoint.getScoredNetworkCache()); - if (speed != AccessPoint.Speed.NONE) { - stringBuilder.append(",") - .append(accessPoint.getSpeedLabel(speed)); - } - int ageSeconds = (int) (nowMs - result.timestamp / 1000) / 1000; - stringBuilder.append(",").append(ageSeconds).append("s"); - stringBuilder.append("}"); - return stringBuilder.toString(); - } - - @AccessPoint.Speed - private static int getSpecificApSpeed(ScanResult result, - Map<String, TimestampedScoredNetwork> scoredNetworkCache) { - TimestampedScoredNetwork timedScore = scoredNetworkCache.get(result.BSSID); - if (timedScore == null) { - return AccessPoint.Speed.NONE; - } - // For debugging purposes we may want to use mRssi rather than result.level as the average - // speed wil be determined by mRssi - return timedScore.getScore().calculateBadge(result.level); - } - - public static String getMeteredLabel(Context context, WifiConfiguration config) { - // meteredOverride is whether the user manually set the metered setting or not. - // meteredHint is whether the network itself is telling us that it is metered - if (config.meteredOverride == WifiConfiguration.METERED_OVERRIDE_METERED - || (config.meteredHint && !isMeteredOverridden(config))) { - return context.getString(R.string.wifi_metered_label); - } - return context.getString(R.string.wifi_unmetered_label); - } - - /** - * Returns the Internet icon resource for a given RSSI level. - * - * @param level The number of bars to show (0-4) - * @param noInternet True if a connected Wi-Fi network cannot access the Internet - */ - public static int getInternetIconResource(int level, boolean noInternet) { - int wifiLevel = level; - if (wifiLevel < 0) { - Log.e(TAG, "Wi-Fi level is out of range! level:" + level); - wifiLevel = 0; - } else if (level >= WIFI_PIE.length) { - Log.e(TAG, "Wi-Fi level is out of range! level:" + level); - wifiLevel = WIFI_PIE.length - 1; - } - return noInternet ? NO_INTERNET_WIFI_PIE[wifiLevel] : WIFI_PIE[wifiLevel]; - } - - /** - * Returns the Hotspot network icon resource. - * - * @param deviceType The device type of Hotspot network - */ - public static int getHotspotIconResource(int deviceType) { - return switch (deviceType) { - case NetworkProviderInfo.DEVICE_TYPE_PHONE -> R.drawable.ic_hotspot_phone; - case NetworkProviderInfo.DEVICE_TYPE_TABLET -> R.drawable.ic_hotspot_tablet; - case NetworkProviderInfo.DEVICE_TYPE_LAPTOP -> R.drawable.ic_hotspot_laptop; - case NetworkProviderInfo.DEVICE_TYPE_WATCH -> R.drawable.ic_hotspot_watch; - case NetworkProviderInfo.DEVICE_TYPE_AUTO -> R.drawable.ic_hotspot_auto; - default -> R.drawable.ic_hotspot_phone; // Return phone icon as default. - }; - } - - /** - * Wrapper the {@link #getInternetIconResource} for testing compatibility. - */ - public static class InternetIconInjector { - - protected final Context mContext; - - public InternetIconInjector(Context context) { - mContext = context; - } - - /** - * Returns the Internet icon for a given RSSI level. - * - * @param noInternet True if a connected Wi-Fi network cannot access the Internet - * @param level The number of bars to show (0-4) - */ - public Drawable getIcon(boolean noInternet, int level) { - return mContext.getDrawable(WifiUtils.getInternetIconResource(level, noInternet)); - } - } - - public static boolean isMeteredOverridden(WifiConfiguration config) { - return config.meteredOverride != WifiConfiguration.METERED_OVERRIDE_NONE; - } - - /** - * Returns the Intent for Wi-Fi dialog. - * - * @param key The Wi-Fi entry key - * @param connectForCaller True if a chosen WifiEntry request to connect to - */ - public static Intent getWifiDialogIntent(String key, boolean connectForCaller) { - final Intent intent = new Intent(ACTION_WIFI_DIALOG); - intent.putExtra(EXTRA_CHOSEN_WIFI_ENTRY_KEY, key); - intent.putExtra(EXTRA_CONNECT_FOR_CALLER, connectForCaller); - return intent; - } - - /** - * Returns the Intent for Wi-Fi network details settings. - * - * @param key The Wi-Fi entry key - */ - public static Intent getWifiDetailsSettingsIntent(String key) { - final Intent intent = new Intent(ACTION_WIFI_DETAILS_SETTINGS); - final Bundle bundle = new Bundle(); - bundle.putString(KEY_CHOSEN_WIFIENTRY_KEY, key); - intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, bundle); - return intent; - } - - /** - * Returns the string of Wi-Fi tethering summary for connected devices. - * - * @param context The application context - * @param connectedDevices The count of connected devices - */ - public static String getWifiTetherSummaryForConnectedDevices(Context context, - int connectedDevices) { - MessageFormat msgFormat = new MessageFormat( - context.getResources().getString(R.string.wifi_tether_connected_summary), - Locale.getDefault()); - Map<String, Object> arguments = new HashMap<>(); - arguments.put("count", connectedDevices); - return msgFormat.format(arguments); - } -} diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt new file mode 100644 index 000000000000..0117ece888c0 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt @@ -0,0 +1,506 @@ +/* + * 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.wifi + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.graphics.drawable.Drawable +import android.icu.text.MessageFormat +import android.net.wifi.ScanResult +import android.net.wifi.WifiConfiguration +import android.net.wifi.WifiConfiguration.NetworkSelectionStatus +import android.net.wifi.WifiManager +import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo +import android.os.Bundle +import android.os.SystemClock +import android.util.Log +import androidx.annotation.VisibleForTesting +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import com.android.settingslib.R +import com.android.settingslib.flags.Flags.newStatusBarIcons +import java.util.Locale +import kotlin.coroutines.resume +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.asExecutor +import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withContext + + +open class WifiUtils { + /** + * Wrapper the [.getInternetIconResource] for testing compatibility. + */ + open class InternetIconInjector(protected val context: Context) { + /** + * Returns the Internet icon for a given RSSI level. + * + * @param noInternet True if a connected Wi-Fi network cannot access the Internet + * @param level The number of bars to show (0-4) + */ + fun getIcon(noInternet: Boolean, level: Int): Drawable? { + return context.getDrawable(getInternetIconResource(level, noInternet)) + } + } + + companion object { + private const val TAG = "WifiUtils" + private const val INVALID_RSSI = -127 + + /** + * The intent action shows Wi-Fi dialog to connect Wi-Fi network. + * + * + * Input: The calling package should put the chosen + * com.android.wifitrackerlib.WifiEntry#getKey() to a string extra in the request bundle into + * the [.EXTRA_CHOSEN_WIFI_ENTRY_KEY]. + * + * + * Output: Nothing. + */ + @JvmField + @VisibleForTesting + val ACTION_WIFI_DIALOG = "com.android.settings.WIFI_DIALOG" + + /** + * Specify a key that indicates the WifiEntry to be configured. + */ + @JvmField + @VisibleForTesting + val EXTRA_CHOSEN_WIFI_ENTRY_KEY = "key_chosen_wifientry_key" + + /** + * The lookup key for a boolean that indicates whether a chosen WifiEntry request to connect to. + * `true` means a chosen WifiEntry request to connect to. + */ + @JvmField + @VisibleForTesting + val EXTRA_CONNECT_FOR_CALLER = "connect_for_caller" + + /** + * The intent action shows network details settings to allow configuration of Wi-Fi. + * + * + * In some cases, a matching Activity may not exist, so ensure you + * safeguard against this. + * + * + * Input: The calling package should put the chosen + * com.android.wifitrackerlib.WifiEntry#getKey() to a string extra in the request bundle into + * the [.KEY_CHOSEN_WIFIENTRY_KEY]. + * + * + * Output: Nothing. + */ + const val ACTION_WIFI_DETAILS_SETTINGS = "android.settings.WIFI_DETAILS_SETTINGS" + const val KEY_CHOSEN_WIFIENTRY_KEY = "key_chosen_wifientry_key" + const val EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args" + + @JvmField + val WIFI_PIE = getIconsBasedOnFlag() + + private fun getIconsBasedOnFlag(): IntArray { + return if (newStatusBarIcons()) { + intArrayOf( + R.drawable.ic_wifi_0, + R.drawable.ic_wifi_1, + R.drawable.ic_wifi_2, + R.drawable.ic_wifi_3, + R.drawable.ic_wifi_4 + ) + } else { + intArrayOf( + com.android.internal.R.drawable.ic_wifi_signal_0, + com.android.internal.R.drawable.ic_wifi_signal_1, + com.android.internal.R.drawable.ic_wifi_signal_2, + com.android.internal.R.drawable.ic_wifi_signal_3, + com.android.internal.R.drawable.ic_wifi_signal_4 + ) + } + } + + val NO_INTERNET_WIFI_PIE = getErrorIconsBasedOnFlag() + + private fun getErrorIconsBasedOnFlag(): IntArray { + return if (newStatusBarIcons()) { + intArrayOf( + R.drawable.ic_wifi_0_error, + R.drawable.ic_wifi_1_error, + R.drawable.ic_wifi_2_error, + R.drawable.ic_wifi_3_error, + R.drawable.ic_wifi_4_error + ) + } else { + intArrayOf( + R.drawable.ic_no_internet_wifi_signal_0, + R.drawable.ic_no_internet_wifi_signal_1, + R.drawable.ic_no_internet_wifi_signal_2, + R.drawable.ic_no_internet_wifi_signal_3, + R.drawable.ic_no_internet_wifi_signal_4 + ) + } + } + + @JvmStatic + fun buildLoggingSummary(accessPoint: AccessPoint, config: WifiConfiguration?): String { + val summary = StringBuilder() + val info = accessPoint.info + // Add RSSI/band information for this config, what was seen up to 6 seconds ago + // verbose WiFi Logging is only turned on thru developers settings + if (accessPoint.isActive && info != null) { + summary.append(" f=" + info.frequency.toString()) + } + summary.append(" " + getVisibilityStatus(accessPoint)) + if (config != null && (config.networkSelectionStatus.networkSelectionStatus + != NetworkSelectionStatus.NETWORK_SELECTION_ENABLED) + ) { + summary.append(" (" + config.networkSelectionStatus.networkStatusString) + if (config.networkSelectionStatus.disableTime > 0) { + val now = System.currentTimeMillis() + val diff = (now - config.networkSelectionStatus.disableTime) / 1000 + val sec = diff % 60 // seconds + val min = diff / 60 % 60 // minutes + val hour = min / 60 % 60 // hours + summary.append(", ") + if (hour > 0) summary.append(hour.toString() + "h ") + summary.append(min.toString() + "m ") + summary.append(sec.toString() + "s ") + } + summary.append(")") + } + if (config != null) { + val networkStatus = config.networkSelectionStatus + for (reason in 0..NetworkSelectionStatus.getMaxNetworkSelectionDisableReason()) { + if (networkStatus.getDisableReasonCounter(reason) != 0) { + summary.append(" ") + .append( + NetworkSelectionStatus + .getNetworkSelectionDisableReasonString(reason) + ) + .append("=") + .append(networkStatus.getDisableReasonCounter(reason)) + } + } + } + return summary.toString() + } + + /** + * Returns the visibility status of the WifiConfiguration. + * + * @return autojoin debugging information + * TODO: use a string formatter + * ["rssi 5Ghz", "num results on 5GHz" / "rssi 5Ghz", "num results on 5GHz"] + * For instance [-40,5/-30,2] + */ + @JvmStatic + @VisibleForTesting + fun getVisibilityStatus(accessPoint: AccessPoint): String { + val info = accessPoint.info + val visibility = StringBuilder() + val scans24GHz = StringBuilder() + val scans5GHz = StringBuilder() + val scans60GHz = StringBuilder() + var bssid: String? = null + if (accessPoint.isActive && info != null) { + bssid = info.bssid + if (bssid != null) { + visibility.append(" ").append(bssid) + } + visibility.append(" standard = ").append(info.wifiStandard) + visibility.append(" rssi=").append(info.rssi) + visibility.append(" ") + visibility.append(" score=").append(info.getScore()) + if (accessPoint.speed != AccessPoint.Speed.NONE) { + visibility.append(" speed=").append(accessPoint.speedLabel) + } + visibility.append(String.format(" tx=%.1f,", info.successfulTxPacketsPerSecond)) + visibility.append(String.format("%.1f,", info.retriedTxPacketsPerSecond)) + visibility.append(String.format("%.1f ", info.lostTxPacketsPerSecond)) + visibility.append(String.format("rx=%.1f", info.successfulRxPacketsPerSecond)) + } + var maxRssi5 = INVALID_RSSI + var maxRssi24 = INVALID_RSSI + var maxRssi60 = INVALID_RSSI + val maxDisplayedScans = 4 + var num5 = 0 // number of scanned BSSID on 5GHz band + var num24 = 0 // number of scanned BSSID on 2.4Ghz band + var num60 = 0 // number of scanned BSSID on 60Ghz band + val numBlockListed = 0 + + // TODO: sort list by RSSI or age + val nowMs = SystemClock.elapsedRealtime() + for (result in accessPoint.getScanResults()) { + if (result == null) { + continue + } + if (result.frequency >= AccessPoint.LOWER_FREQ_5GHZ && + result.frequency <= AccessPoint.HIGHER_FREQ_5GHZ + ) { + // Strictly speaking: [4915, 5825] + num5++ + if (result.level > maxRssi5) { + maxRssi5 = result.level + } + if (num5 <= maxDisplayedScans) { + scans5GHz.append( + verboseScanResultSummary( + accessPoint, result, bssid, + nowMs + ) + ) + } + } else if (result.frequency >= AccessPoint.LOWER_FREQ_24GHZ && + result.frequency <= AccessPoint.HIGHER_FREQ_24GHZ + ) { + // Strictly speaking: [2412, 2482] + num24++ + if (result.level > maxRssi24) { + maxRssi24 = result.level + } + if (num24 <= maxDisplayedScans) { + scans24GHz.append( + verboseScanResultSummary( + accessPoint, result, bssid, + nowMs + ) + ) + } + } else if (result.frequency >= AccessPoint.LOWER_FREQ_60GHZ && + result.frequency <= AccessPoint.HIGHER_FREQ_60GHZ + ) { + // Strictly speaking: [60000, 61000] + num60++ + if (result.level > maxRssi60) { + maxRssi60 = result.level + } + if (num60 <= maxDisplayedScans) { + scans60GHz.append( + verboseScanResultSummary( + accessPoint, result, bssid, + nowMs + ) + ) + } + } + } + visibility.append(" [") + if (num24 > 0) { + visibility.append("(").append(num24).append(")") + if (num24 > maxDisplayedScans) { + visibility.append("max=").append(maxRssi24).append(",") + } + visibility.append(scans24GHz.toString()) + } + visibility.append(";") + if (num5 > 0) { + visibility.append("(").append(num5).append(")") + if (num5 > maxDisplayedScans) { + visibility.append("max=").append(maxRssi5).append(",") + } + visibility.append(scans5GHz.toString()) + } + visibility.append(";") + if (num60 > 0) { + visibility.append("(").append(num60).append(")") + if (num60 > maxDisplayedScans) { + visibility.append("max=").append(maxRssi60).append(",") + } + visibility.append(scans60GHz.toString()) + } + if (numBlockListed > 0) { + visibility.append("!").append(numBlockListed) + } + visibility.append("]") + return visibility.toString() + } + + @JvmStatic + @VisibleForTesting /* package */ fun verboseScanResultSummary( + accessPoint: AccessPoint, + result: ScanResult, + bssid: String?, + nowMs: Long + ): String { + val stringBuilder = StringBuilder() + stringBuilder.append(" \n{").append(result.BSSID) + if (result.BSSID == bssid) { + stringBuilder.append("*") + } + stringBuilder.append("=").append(result.frequency) + stringBuilder.append(",").append(result.level) + val speed = getSpecificApSpeed(result, accessPoint.scoredNetworkCache) + if (speed != AccessPoint.Speed.NONE) { + stringBuilder.append(",") + .append(accessPoint.getSpeedLabel(speed)) + } + val ageSeconds = (nowMs - result.timestamp / 1000).toInt() / 1000 + stringBuilder.append(",").append(ageSeconds).append("s") + stringBuilder.append("}") + return stringBuilder.toString() + } + + @AccessPoint.Speed + private fun getSpecificApSpeed( + result: ScanResult, + scoredNetworkCache: Map<String, TimestampedScoredNetwork> + ): Int { + val timedScore = scoredNetworkCache[result.BSSID] ?: return AccessPoint.Speed.NONE + // For debugging purposes we may want to use mRssi rather than result.level as the average + // speed wil be determined by mRssi + return timedScore.score.calculateBadge(result.level) + } + + @JvmStatic + fun getMeteredLabel(context: Context, config: WifiConfiguration): String { + // meteredOverride is whether the user manually set the metered setting or not. + // meteredHint is whether the network itself is telling us that it is metered + return if (config.meteredOverride == WifiConfiguration.METERED_OVERRIDE_METERED || + config.meteredHint && !isMeteredOverridden( + config + ) + ) { + context.getString(R.string.wifi_metered_label) + } else context.getString(R.string.wifi_unmetered_label) + } + + /** + * Returns the Internet icon resource for a given RSSI level. + * + * @param level The number of bars to show (0-4) + * @param noInternet True if a connected Wi-Fi network cannot access the Internet + */ + @JvmStatic + fun getInternetIconResource(level: Int, noInternet: Boolean): Int { + var wifiLevel = level + if (wifiLevel < 0) { + Log.e(TAG, "Wi-Fi level is out of range! level:$level") + wifiLevel = 0 + } else if (level >= WIFI_PIE.size) { + Log.e(TAG, "Wi-Fi level is out of range! level:$level") + wifiLevel = WIFI_PIE.size - 1 + } + return if (noInternet) NO_INTERNET_WIFI_PIE[wifiLevel] else WIFI_PIE[wifiLevel] + } + + /** + * Returns the Hotspot network icon resource. + * + * @param deviceType The device type of Hotspot network + */ + @JvmStatic + fun getHotspotIconResource(deviceType: Int): Int { + return when (deviceType) { + NetworkProviderInfo.DEVICE_TYPE_PHONE -> R.drawable.ic_hotspot_phone + NetworkProviderInfo.DEVICE_TYPE_TABLET -> R.drawable.ic_hotspot_tablet + NetworkProviderInfo.DEVICE_TYPE_LAPTOP -> R.drawable.ic_hotspot_laptop + NetworkProviderInfo.DEVICE_TYPE_WATCH -> R.drawable.ic_hotspot_watch + NetworkProviderInfo.DEVICE_TYPE_AUTO -> R.drawable.ic_hotspot_auto + else -> R.drawable.ic_hotspot_phone + } + } + + @JvmStatic + fun isMeteredOverridden(config: WifiConfiguration): Boolean { + return config.meteredOverride != WifiConfiguration.METERED_OVERRIDE_NONE + } + + /** + * Returns the Intent for Wi-Fi dialog. + * + * @param key The Wi-Fi entry key + * @param connectForCaller True if a chosen WifiEntry request to connect to + */ + @JvmStatic + fun getWifiDialogIntent(key: String?, connectForCaller: Boolean): Intent { + val intent = Intent(ACTION_WIFI_DIALOG) + intent.putExtra(EXTRA_CHOSEN_WIFI_ENTRY_KEY, key) + intent.putExtra(EXTRA_CONNECT_FOR_CALLER, connectForCaller) + return intent + } + + /** + * Returns the Intent for Wi-Fi network details settings. + * + * @param key The Wi-Fi entry key + */ + @JvmStatic + fun getWifiDetailsSettingsIntent(key: String?): Intent { + val intent = Intent(ACTION_WIFI_DETAILS_SETTINGS) + val bundle = Bundle() + bundle.putString(KEY_CHOSEN_WIFIENTRY_KEY, key) + intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, bundle) + return intent + } + + /** + * Returns the string of Wi-Fi tethering summary for connected devices. + * + * @param context The application context + * @param connectedDevices The count of connected devices + */ + @JvmStatic + fun getWifiTetherSummaryForConnectedDevices( + context: Context, + connectedDevices: Int + ): String { + val msgFormat = MessageFormat( + context.resources.getString(R.string.wifi_tether_connected_summary), + Locale.getDefault() + ) + val arguments: MutableMap<String, Any> = HashMap() + arguments["count"] = connectedDevices + return msgFormat.format(arguments) + } + + @JvmStatic + fun checkWepAllowed( + context: Context, + lifecycleOwner: LifecycleOwner, + ssid: String, + onAllowed: () -> Unit, + ) { + lifecycleOwner.lifecycleScope.launch { + val wifiManager = context.getSystemService(WifiManager::class.java) ?: return@launch + if (wifiManager.queryWepAllowed()) { + onAllowed() + } else { + val intent = Intent(Intent.ACTION_MAIN).apply { + component = ComponentName( + "com.android.settings", + "com.android.settings.network.WepNetworkDialogActivity" + ) + putExtra(SSID, ssid) + } + context.startActivity(intent) + } + } + } + + private suspend fun WifiManager.queryWepAllowed(): Boolean = + withContext(Dispatchers.Default) { + suspendCancellableCoroutine { continuation -> + queryWepAllowed(Dispatchers.Default.asExecutor()) { + continuation.resume(it) + } + } + } + + const val SSID = "ssid" + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java index 461ecf5d3c84..5996dbb322fc 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java @@ -18,9 +18,7 @@ package com.android.settingslib.bluetooth; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -58,8 +56,6 @@ import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.shadow.api.Shadow; -import java.util.concurrent.ExecutionException; - @RunWith(RobolectricTestRunner.class) @Config(shadows = {ShadowBluetoothAdapter.class}) public class CachedBluetoothDeviceTest { @@ -1823,52 +1819,6 @@ public class CachedBluetoothDeviceTest { assertThat(mCachedDevice.isConnectedHearingAidDevice()).isFalse(); } - @Test - public void syncProfileForMemberDevice_hasDiff_shouldSync() - throws ExecutionException, InterruptedException { - mCachedDevice.addMemberDevice(mSubCachedDevice); - when(mProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile); - when(mProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile); - when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile); - - when(mA2dpProfile.isEnabled(mDevice)).thenReturn(true); - when(mHearingAidProfile.isEnabled(mDevice)).thenReturn(true); - when(mLeAudioProfile.isEnabled(mDevice)).thenReturn(true); - - when(mA2dpProfile.isEnabled(mSubDevice)).thenReturn(true); - when(mHearingAidProfile.isEnabled(mSubDevice)).thenReturn(false); - when(mLeAudioProfile.isEnabled(mSubDevice)).thenReturn(false); - - mCachedDevice.syncProfileForMemberDevice().get(); - - verify(mA2dpProfile, never()).setEnabled(any(BluetoothDevice.class), anyBoolean()); - verify(mHearingAidProfile).setEnabled(any(BluetoothDevice.class), eq(true)); - verify(mLeAudioProfile).setEnabled(any(BluetoothDevice.class), eq(true)); - } - - @Test - public void syncProfileForMemberDevice_noDiff_shouldNotSync() - throws ExecutionException, InterruptedException { - mCachedDevice.addMemberDevice(mSubCachedDevice); - when(mProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile); - when(mProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile); - when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile); - - when(mA2dpProfile.isEnabled(mDevice)).thenReturn(false); - when(mHearingAidProfile.isEnabled(mDevice)).thenReturn(false); - when(mLeAudioProfile.isEnabled(mDevice)).thenReturn(true); - - when(mA2dpProfile.isEnabled(mSubDevice)).thenReturn(false); - when(mHearingAidProfile.isEnabled(mSubDevice)).thenReturn(false); - when(mLeAudioProfile.isEnabled(mSubDevice)).thenReturn(true); - - mCachedDevice.syncProfileForMemberDevice().get(); - - verify(mA2dpProfile, never()).setEnabled(any(BluetoothDevice.class), anyBoolean()); - verify(mHearingAidProfile, never()).setEnabled(any(BluetoothDevice.class), anyBoolean()); - verify(mLeAudioProfile, never()).setEnabled(any(BluetoothDevice.class), anyBoolean()); - } - private HearingAidInfo getLeftAshaHearingAidInfo() { return new HearingAidInfo.Builder() .setAshaDeviceSide(HearingAidProfile.DeviceSide.SIDE_LEFT) diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java index 46e724d245f5..c3237f0e72eb 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java @@ -16,7 +16,6 @@ package com.android.settingslib.media; -import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; @@ -32,6 +31,8 @@ import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import java.util.Collections; + @RunWith(RobolectricTestRunner.class) public class MediaManagerTest { @@ -59,7 +60,7 @@ public class MediaManagerTest { public void dispatchDeviceListAdded_registerCallback_shouldDispatchCallback() { mMediaManager.registerCallback(mCallback); - mMediaManager.dispatchDeviceListAdded(); + mMediaManager.dispatchDeviceListAdded(Collections.emptyList()); verify(mCallback).onDeviceListAdded(any()); } @@ -68,9 +69,9 @@ public class MediaManagerTest { public void dispatchDeviceListRemoved_registerCallback_shouldDispatchCallback() { mMediaManager.registerCallback(mCallback); - mMediaManager.dispatchDeviceListRemoved(mMediaManager.mMediaDevices); + mMediaManager.dispatchDeviceListRemoved(Collections.emptyList()); - verify(mCallback).onDeviceListRemoved(mMediaManager.mMediaDevices); + verify(mCallback).onDeviceListRemoved(Collections.emptyList()); } @Test @@ -83,24 +84,6 @@ public class MediaManagerTest { } @Test - public void findMediaDevice_idExist_shouldReturnMediaDevice() { - mMediaManager.mMediaDevices.add(mDevice); - - final MediaDevice device = mMediaManager.findMediaDevice(TEST_ID); - - assertThat(device.getId()).isEqualTo(mDevice.getId()); - } - - @Test - public void findMediaDevice_idNotExist_shouldReturnNull() { - mMediaManager.mMediaDevices.add(mDevice); - - final MediaDevice device = mMediaManager.findMediaDevice("123"); - - assertThat(device).isNull(); - } - - @Test public void dispatchOnRequestFailed_registerCallback_shouldDispatchCallback() { mMediaManager.registerCallback(mCallback); diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index 2fa1c6eda458..a490b6f096ef 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -154,6 +154,7 @@ public class SecureSettings { Settings.Secure.MANUAL_RINGER_TOGGLE_COUNT, Settings.Secure.LOW_POWER_WARNING_ACKNOWLEDGED, Settings.Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, + Settings.Secure.EMERGENCY_THERMAL_ALERT_DISABLED, Settings.Secure.HUSH_GESTURE_USED, Settings.Secure.IN_CALL_NOTIFICATION_ENABLED, Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 2535fdb6e4d0..4cdf98cbe14e 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -235,6 +235,7 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.MANUAL_RINGER_TOGGLE_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR); VALIDATORS.put(Secure.LOW_POWER_WARNING_ACKNOWLEDGED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.EMERGENCY_THERMAL_ALERT_DISABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.IN_CALL_NOTIFICATION_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, BOOLEAN_VALIDATOR); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java index 6ff36d43f91f..ad3eb92b0519 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java @@ -20,8 +20,6 @@ import static android.provider.Settings.Config.SYNC_DISABLED_MODE_NONE; import static android.provider.Settings.Config.SYNC_DISABLED_MODE_PERSISTENT; import static android.provider.Settings.Config.SYNC_DISABLED_MODE_UNTIL_REBOOT; -import static com.android.providers.settings.Flags.supportOverrides; - import android.aconfig.Aconfig.parsed_flag; import android.aconfig.Aconfig.parsed_flags; import android.annotation.SuppressLint; @@ -269,9 +267,9 @@ public final class DeviceConfigService extends Binder { verb = CommandVerb.GET; } else if ("put".equalsIgnoreCase(cmd)) { verb = CommandVerb.PUT; - } else if (supportOverrides() && "override".equalsIgnoreCase(cmd)) { + } else if ("override".equalsIgnoreCase(cmd)) { verb = CommandVerb.OVERRIDE; - } else if (supportOverrides() && "clear_override".equalsIgnoreCase(cmd)) { + } else if ("clear_override".equalsIgnoreCase(cmd)) { verb = CommandVerb.CLEAR_OVERRIDE; } else if ("delete".equalsIgnoreCase(cmd)) { verb = CommandVerb.DELETE; @@ -285,7 +283,7 @@ public final class DeviceConfigService extends Binder { if (peekNextArg() == null) { isValid = true; } - } else if (supportOverrides() && "list_local_overrides".equalsIgnoreCase(cmd)) { + } else if ("list_local_overrides".equalsIgnoreCase(cmd)) { verb = CommandVerb.LIST_LOCAL_OVERRIDES; if (peekNextArg() == null) { isValid = true; @@ -427,14 +425,10 @@ public final class DeviceConfigService extends Binder { DeviceConfig.setProperty(namespace, key, value, makeDefault); break; case OVERRIDE: - if (supportOverrides()) { - DeviceConfig.setLocalOverride(namespace, key, value); - } + DeviceConfig.setLocalOverride(namespace, key, value); break; case CLEAR_OVERRIDE: - if (supportOverrides()) { - DeviceConfig.clearLocalOverride(namespace, key); - } + DeviceConfig.clearLocalOverride(namespace, key); break; case DELETE: pout.println(delete(iprovider, namespace, key) @@ -452,19 +446,15 @@ public final class DeviceConfigService extends Binder { } } else { for (String line : listAll(iprovider)) { - if (supportOverrides()) { - boolean isPrivate = false; - for (String privateNamespace : PRIVATE_NAMESPACES) { - if (line.startsWith(privateNamespace)) { - isPrivate = true; - break; - } + boolean isPrivate = false; + for (String privateNamespace : PRIVATE_NAMESPACES) { + if (line.startsWith(privateNamespace)) { + isPrivate = true; + break; } + } - if (!isPrivate) { - pout.println(line); - } - } else { + if (!isPrivate) { pout.println(line); } } @@ -495,18 +485,16 @@ public final class DeviceConfigService extends Binder { } break; case LIST_LOCAL_OVERRIDES: - if (supportOverrides()) { - Map<String, Map<String, String>> underlyingValues = - DeviceConfig.getUnderlyingValuesForOverriddenFlags(); - for (String overrideNamespace : underlyingValues.keySet()) { - Map<String, String> flagToValue = - underlyingValues.get(overrideNamespace); - for (String flag : flagToValue.keySet()) { - String flagText = overrideNamespace + "/" + flag; - String valueText = - DeviceConfig.getProperty(overrideNamespace, flag); - pout.println(flagText + "=" + valueText); - } + Map<String, Map<String, String>> underlyingValues = + DeviceConfig.getUnderlyingValuesForOverriddenFlags(); + for (String overrideNamespace : underlyingValues.keySet()) { + Map<String, String> flagToValue = + underlyingValues.get(overrideNamespace); + for (String flag : flagToValue.keySet()) { + String flagText = overrideNamespace + "/" + flag; + String valueText = + DeviceConfig.getProperty(overrideNamespace, flag); + pout.println(flagText + "=" + valueText); } } break; diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index febce97031bb..1ead14ab6f4c 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -3812,7 +3812,7 @@ public class SettingsProvider extends ContentProvider { } private final class UpgradeController { - private static final int SETTINGS_VERSION = 225; + private static final int SETTINGS_VERSION = 226; private final int mUserId; @@ -6011,6 +6011,28 @@ public class SettingsProvider extends ContentProvider { currentVersion = 225; } + // Version 225: Set the System#KEYBOARD_VIBRATION_ENABLED based on touch + // feedback enabled state. + if (currentVersion == 225) { + final SettingsState systemSettings = getSystemSettingsLocked(userId); + final Setting touchFeedbackSettings = systemSettings + .getSettingLocked(Settings.System.HAPTIC_FEEDBACK_ENABLED); + final Setting keyboardVibrationSettings = systemSettings + .getSettingLocked(Settings.System.KEYBOARD_VIBRATION_ENABLED); + if (keyboardVibrationSettings.isNull()) { + if (!touchFeedbackSettings.isNull()) { + // Use touch feedback settings. + systemSettings.insertSettingOverrideableByRestoreLocked( + Settings.System.KEYBOARD_VIBRATION_ENABLED, + touchFeedbackSettings.getValue(), + touchFeedbackSettings.getTag(), + touchFeedbackSettings.isDefaultFromSystem(), + SettingsState.SYSTEM_PACKAGE_NAME); + } + } + currentVersion = 226; + } + // vXXX: Add new settings above this point. if (currentVersion != newVersion) { diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index ce0257f6c85b..2a8eb9bc0845 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -72,6 +72,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; @@ -157,6 +158,9 @@ final class SettingsState { "/product/etc/aconfig_flags.pb", "/vendor/etc/aconfig_flags.pb"); + private static final String APEX_DIR = "/apex"; + private static final String APEX_ACONFIG_PATH_SUFFIX = "/etc/aconfig_flags.pb"; + /** * This tag is applied to all aconfig default value-loaded flags. */ @@ -238,7 +242,7 @@ final class SettingsState { private int mNextHistoricalOpIdx; @GuardedBy("mLock") - @Nullable + @NonNull private Map<String, Map<String, String>> mNamespaceDefaults; public static final int SETTINGS_TYPE_GLOBAL = 0; @@ -332,23 +336,29 @@ final class SettingsState { mHistoricalOperations = Build.IS_DEBUGGABLE ? new ArrayList<>(HISTORICAL_OPERATION_COUNT) : null; + mNamespaceDefaults = new HashMap<>(); + synchronized (mLock) { readStateSyncLocked(); if (Flags.loadAconfigDefaults()) { if (isConfigSettingsKey(mKey)) { - loadAconfigDefaultValuesLocked(); + loadAconfigDefaultValuesLocked(sAconfigTextProtoFilesOnDevice); } } + if (Flags.loadApexAconfigProtobufs()) { + if (isConfigSettingsKey(mKey)) { + List<String> apexProtoPaths = listApexProtoPaths(); + loadAconfigDefaultValuesLocked(apexProtoPaths); + } + } } } @GuardedBy("mLock") - private void loadAconfigDefaultValuesLocked() { - mNamespaceDefaults = new HashMap<>(); - - for (String fileName : sAconfigTextProtoFilesOnDevice) { + private void loadAconfigDefaultValuesLocked(List<String> filePaths) { + for (String fileName : filePaths) { try (FileInputStream inputStream = new FileInputStream(fileName)) { loadAconfigDefaultValues(inputStream.readAllBytes(), mNamespaceDefaults); } catch (IOException e) { @@ -357,13 +367,41 @@ final class SettingsState { } } + private List<String> listApexProtoPaths() { + LinkedList<String> paths = new LinkedList(); + + File apexDirectory = new File(APEX_DIR); + if (!apexDirectory.isDirectory()) { + return paths; + } + + File[] subdirs = apexDirectory.listFiles(); + if (subdirs == null) { + return paths; + } + + for (File prefix : subdirs) { + // For each mainline modules, there are two directories, one <modulepackage>/, + // and one <modulepackage>@<versioncode>/. Just read the former. + if (prefix.getAbsolutePath().contains("@")) { + continue; + } + + File protoPath = new File(prefix + APEX_ACONFIG_PATH_SUFFIX); + if (!protoPath.exists()) { + continue; + } + + paths.add(protoPath.getAbsolutePath()); + } + return paths; + } + @VisibleForTesting @GuardedBy("mLock") public void addAconfigDefaultValuesFromMap( @NonNull Map<String, Map<String, String>> defaultMap) { - if (mNamespaceDefaults != null) { - mNamespaceDefaults.putAll(defaultMap); - } + mNamespaceDefaults.putAll(defaultMap); } @VisibleForTesting @@ -447,7 +485,7 @@ final class SettingsState { return names; } - @Nullable + @NonNull public Map<String, Map<String, String>> getAconfigDefaultValues() { synchronized (mLock) { return mNamespaceDefaults; @@ -519,9 +557,9 @@ final class SettingsState { return false; } - // Aconfig flags are always boot stable, so we anytime we write one, we staged it to be + // Aconfig flags are always boot stable, so we anytime we write one, we stage it to be // applied on reboot. - if (Flags.stageAllAconfigFlags() && mNamespaceDefaults != null) { + if (Flags.stageAllAconfigFlags()) { int slashIndex = name.indexOf("/"); boolean stageFlag = isConfigSettingsKey(mKey) && slashIndex != -1 diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig index e5086e87173a..2e14e9b8be4c 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig +++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig @@ -25,3 +25,11 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "load_apex_aconfig_protobufs" + namespace: "core_experiments_team_internal" + description: "When enabled, loads aconfig default values in apex flag protobufs into DeviceConfig on boot." + bug: "327383546" + is_fixed_read_only: true +} diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java index ea30c69b1c45..33362a22ffba 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java @@ -25,7 +25,6 @@ import android.aconfig.Aconfig; import android.aconfig.Aconfig.parsed_flag; import android.aconfig.Aconfig.parsed_flags; import android.os.Looper; -import android.platform.test.annotations.RequiresFlagsDisabled; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; @@ -231,38 +230,6 @@ public class SettingsStateTest { } @Test - @RequiresFlagsDisabled(Flags.FLAG_LOAD_ACONFIG_DEFAULTS) - public void testAddingAconfigMapOnNullIsNoOp() { - int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0); - Object lock = new Object(); - SettingsState settingsState = new SettingsState( - InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey, - SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper()); - - parsed_flags flags = parsed_flags - .newBuilder() - .addParsedFlag(parsed_flag - .newBuilder() - .setPackage("com.android.flags") - .setName("flag5") - .setNamespace("test_namespace") - .setDescription("test flag") - .addBug("12345678") - .setState(Aconfig.flag_state.DISABLED) - .setPermission(Aconfig.flag_permission.READ_WRITE)) - .build(); - - synchronized (lock) { - Map<String, Map<String, String>> defaults = new HashMap<>(); - settingsState.loadAconfigDefaultValues(flags.toByteArray(), defaults); - settingsState.addAconfigDefaultValuesFromMap(defaults); - - assertEquals(null, settingsState.getAconfigDefaultValues()); - } - - } - - @Test public void testInvalidAconfigProtoDoesNotCrash() { Map<String, Map<String, String>> defaults = new HashMap<>(); SettingsState settingsState = getSettingStateObject(); diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index cc2e84c5a995..04cb88d5a39d 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -308,6 +308,7 @@ android_library { name: "SystemUI-tests", use_resource_processor: true, manifest: "tests/AndroidManifest-base.xml", + resource_dirs: [], additional_manifests: ["tests/AndroidManifest.xml"], srcs: [ "tests/src/**/*.kt", diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java index 085fc2959442..88181e7e2a52 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java +++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java @@ -56,10 +56,12 @@ public class AccessibilityMenuService extends AccessibilityService implements View.OnTouchListener { public static final String PACKAGE_NAME = AccessibilityMenuService.class.getPackageName(); + public static final String PACKAGE_TESTS = ".tests"; public static final String INTENT_TOGGLE_MENU = ".toggle_menu"; public static final String INTENT_HIDE_MENU = ".hide_menu"; public static final String INTENT_GLOBAL_ACTION = ".global_action"; public static final String INTENT_GLOBAL_ACTION_EXTRA = "GLOBAL_ACTION"; + public static final String INTENT_OPEN_BLOCKED = "OPEN_BLOCKED"; private static final String TAG = "A11yMenuService"; private static final long BUFFER_MILLISECONDS_TO_PREVENT_UPDATE_FAILURE = 100L; @@ -192,7 +194,7 @@ public class AccessibilityMenuService extends AccessibilityService IntentFilter hideMenuFilter = new IntentFilter(); hideMenuFilter.addAction(Intent.ACTION_SCREEN_OFF); - hideMenuFilter.addAction(PACKAGE_NAME + INTENT_HIDE_MENU); + hideMenuFilter.addAction(INTENT_HIDE_MENU); // Including WRITE_SECURE_SETTINGS enforces that we only listen to apps // with the restricted WRITE_SECURE_SETTINGS permission who broadcast this intent. @@ -200,7 +202,7 @@ public class AccessibilityMenuService extends AccessibilityService Manifest.permission.WRITE_SECURE_SETTINGS, null, Context.RECEIVER_EXPORTED); registerReceiver(mToggleMenuReceiver, - new IntentFilter(PACKAGE_NAME + INTENT_TOGGLE_MENU), + new IntentFilter(INTENT_TOGGLE_MENU), Manifest.permission.WRITE_SECURE_SETTINGS, null, Context.RECEIVER_EXPORTED); @@ -245,8 +247,9 @@ public class AccessibilityMenuService extends AccessibilityService * @return {@code true} if successful, {@code false} otherwise. */ private boolean performGlobalActionInternal(int globalAction) { - Intent intent = new Intent(PACKAGE_NAME + INTENT_GLOBAL_ACTION); + Intent intent = new Intent(INTENT_GLOBAL_ACTION); intent.putExtra(INTENT_GLOBAL_ACTION_EXTRA, globalAction); + intent.setPackage(PACKAGE_NAME + PACKAGE_TESTS); sendBroadcast(intent); Log.i("A11yMenuService", "Broadcasting global action " + globalAction); return performGlobalAction(globalAction); @@ -410,9 +413,16 @@ public class AccessibilityMenuService extends AccessibilityService private void toggleVisibility() { boolean locked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked(); - if (!locked && SystemClock.uptimeMillis() - mLastTimeTouchedOutside - > BUTTON_CLICK_TIMEOUT) { - mA11yMenuLayout.toggleVisibility(); + if (!locked) { + if (SystemClock.uptimeMillis() - mLastTimeTouchedOutside + > BUTTON_CLICK_TIMEOUT) { + mA11yMenuLayout.toggleVisibility(); + } + } else { + // Broadcast for testing. + Intent intent = new Intent(INTENT_OPEN_BLOCKED); + intent.setPackage(PACKAGE_NAME + PACKAGE_TESTS); + sendBroadcast(intent); } } } diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java index 72c1092b92d6..6546b87c8802 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java +++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java @@ -23,6 +23,7 @@ import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_QU import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_RECENTS; import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT; +import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_OPEN_BLOCKED; import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_GLOBAL_ACTION; import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_GLOBAL_ACTION_EXTRA; import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_HIDE_MENU; @@ -65,6 +66,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @RunWith(AndroidJUnit4.class) @@ -75,12 +77,11 @@ public class AccessibilityMenuServiceTest { private static final int TIMEOUT_SERVICE_STATUS_CHANGE_S = 5; private static final int TIMEOUT_UI_CHANGE_S = 5; private static final int NO_GLOBAL_ACTION = -1; - private static final String INPUT_KEYEVENT_KEYCODE_BACK = "input keyevent KEYCODE_BACK"; - private static final String TEST_PIN = "1234"; private static Instrumentation sInstrumentation; private static UiAutomation sUiAutomation; - private static AtomicInteger sLastGlobalAction; + private static final AtomicInteger sLastGlobalAction = new AtomicInteger(NO_GLOBAL_ACTION); + private static final AtomicBoolean sOpenBlocked = new AtomicBoolean(false); private static AccessibilityManager sAccessibilityManager; private static PowerManager sPowerManager; @@ -122,8 +123,6 @@ public class AccessibilityMenuServiceTest { () -> sAccessibilityManager.getEnabledAccessibilityServiceList( AccessibilityServiceInfo.FEEDBACK_ALL_MASK).stream().filter( info -> info.getId().contains(serviceName)).count() == 1); - - sLastGlobalAction = new AtomicInteger(NO_GLOBAL_ACTION); context.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -131,20 +130,28 @@ public class AccessibilityMenuServiceTest { sLastGlobalAction.set( intent.getIntExtra(INTENT_GLOBAL_ACTION_EXTRA, NO_GLOBAL_ACTION)); }}, - new IntentFilter(PACKAGE_NAME + INTENT_GLOBAL_ACTION), + new IntentFilter(INTENT_GLOBAL_ACTION), + null, null, Context.RECEIVER_EXPORTED); + + context.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + Log.i(TAG, "Received notification that menu cannot be opened."); + sOpenBlocked.set(true); + }}, + new IntentFilter(INTENT_OPEN_BLOCKED), null, null, Context.RECEIVER_EXPORTED); } @AfterClass - public static void classTeardown() throws Throwable { - clearPin(); + public static void classTeardown() { Settings.Secure.putString(sInstrumentation.getTargetContext().getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, ""); } @Before public void setup() throws Throwable { - clearPin(); + sOpenBlocked.set(false); wakeUpScreen(); sUiAutomation.executeShellCommand("input keyevent KEYCODE_MENU"); openMenu(); @@ -154,20 +161,8 @@ public class AccessibilityMenuServiceTest { public void tearDown() throws Throwable { closeMenu(); sLastGlobalAction.set(NO_GLOBAL_ACTION); - } - - private static void clearPin() throws Throwable { - sUiAutomation.executeShellCommand("locksettings clear --old " + TEST_PIN); - TestUtils.waitUntil("Device did not register as unlocked & insecure.", - TIMEOUT_SERVICE_STATUS_CHANGE_S, - () -> !sKeyguardManager.isDeviceSecure()); - } - - private static void setPin() throws Throwable { - sUiAutomation.executeShellCommand("locksettings set-pin " + TEST_PIN); - TestUtils.waitUntil("Device did not recognize as locked & secure.", - TIMEOUT_SERVICE_STATUS_CHANGE_S, - () -> sKeyguardManager.isDeviceSecure()); + // dismisses screenshot popup if present. + sUiAutomation.executeShellCommand("input keyevent KEYCODE_BACK"); } private static boolean isMenuVisible() { @@ -184,7 +179,6 @@ public class AccessibilityMenuServiceTest { private static void closeScreen() throws Throwable { Display display = sDisplayManager.getDisplay(Display.DEFAULT_DISPLAY); - setPin(); sUiAutomation.performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN); TestUtils.waitUntil("Screen did not close.", TIMEOUT_UI_CHANGE_S, @@ -194,12 +188,20 @@ public class AccessibilityMenuServiceTest { } private static void openMenu() throws Throwable { - Intent intent = new Intent(PACKAGE_NAME + INTENT_TOGGLE_MENU); + openMenu(false); + } + + private static void openMenu(boolean abandonOnBlock) throws Throwable { + Intent intent = new Intent(INTENT_TOGGLE_MENU); + intent.setPackage(PACKAGE_NAME); sInstrumentation.getContext().sendBroadcast(intent); TestUtils.waitUntil("Timed out before menu could appear.", TIMEOUT_UI_CHANGE_S, () -> { + if (sOpenBlocked.get() && abandonOnBlock) { + throw new IllegalStateException(); + } if (isMenuVisible()) { return true; } else { @@ -213,7 +215,8 @@ public class AccessibilityMenuServiceTest { if (!isMenuVisible()) { return; } - Intent intent = new Intent(PACKAGE_NAME + INTENT_HIDE_MENU); + Intent intent = new Intent(INTENT_HIDE_MENU); + intent.setPackage(PACKAGE_NAME); sInstrumentation.getContext().sendBroadcast(intent); TestUtils.waitUntil("Timed out before menu could close.", TIMEOUT_UI_CHANGE_S, () -> !isMenuVisible()); @@ -444,13 +447,13 @@ public class AccessibilityMenuServiceTest { closeScreen(); wakeUpScreen(); - boolean timedOut = false; + boolean blocked = false; try { - openMenu(); - } catch (AssertionError e) { + openMenu(true); + } catch (IllegalStateException e) { // Expected - timedOut = true; + blocked = true; } - assertThat(timedOut).isTrue(); + assertThat(blocked).isTrue(); } } diff --git a/packages/SystemUI/aconfig/predictive_back.aconfig b/packages/SystemUI/aconfig/predictive_back.aconfig index d0e6b2865891..7bbe82c212e7 100644 --- a/packages/SystemUI/aconfig/predictive_back.aconfig +++ b/packages/SystemUI/aconfig/predictive_back.aconfig @@ -4,26 +4,26 @@ flag { name: "predictive_back_sysui" namespace: "systemui" description: "Predictive Back Dispatching for SysUI" - bug: "309545085" + bug: "327737297" } flag { name: "predictive_back_animate_shade" namespace: "systemui" description: "Enable Shade Animations" - bug: "309545085" + bug: "327732946" } flag { name: "predictive_back_animate_bouncer" namespace: "systemui" description: "Enable Predictive Back Animation in Bouncer" - bug: "309545085" + bug: "327733487" } flag { name: "predictive_back_animate_dialogs" namespace: "systemui" description: "Enable Predictive Back Animation for SysUI dialogs" - bug: "309545085" + bug: "327721544" }
\ No newline at end of file diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 8c8975fdbdd5..baa139727d72 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -194,14 +194,6 @@ flag { } flag { - name: "keyguard_shade_migration_nssl" - namespace: "systemui" - description: "Moves NSSL into a shared element between the notification_panel and " - "keyguard_root_view." - bug: "278054201" -} - -flag { name: "unfold_animation_background_progress" namespace: "systemui" description: "Moves unfold animation progress calculation to a background thread" @@ -419,6 +411,13 @@ flag { } flag { + name: "smartspace_remoteviews_rendering" + namespace: "systemui" + description: "Indicate Smartspace RemoteViews rendering" + bug: "326292691" +} + +flag { name: "pin_input_field_styled_focus_state" namespace: "systemui" description: "Enables styled focus states on pin input field if keyboard is connected" 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 deleted file mode 100644 index 76931a2b4d41..000000000000 --- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright (C) 2022 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.compose - -import android.content.Context -import android.graphics.Point -import android.view.View -import android.view.WindowInsets -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.lifecycle.LifecycleOwner -import com.android.compose.theme.LocalAndroidColorScheme -import com.android.compose.theme.PlatformTheme -import com.android.compose.ui.platform.DensityAwareComposeView -import com.android.internal.policy.ScreenDecorationsUtils -import com.android.systemui.bouncer.ui.BouncerDialogFactory -import com.android.systemui.bouncer.ui.composable.BouncerContent -import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel -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.ScreenDecorProvider -import com.android.systemui.communal.ui.compose.CommunalContainer -import com.android.systemui.communal.ui.compose.CommunalHub -import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel -import com.android.systemui.communal.ui.viewmodel.CommunalViewModel -import com.android.systemui.communal.widgets.WidgetConfigurator -import com.android.systemui.keyboard.stickykeys.ui.view.createStickyKeyIndicatorView -import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel -import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint -import com.android.systemui.keyguard.ui.composable.LockscreenContent -import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint -import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel -import com.android.systemui.people.ui.compose.PeopleScreen -import com.android.systemui.people.ui.viewmodel.PeopleViewModel -import com.android.systemui.qs.footer.ui.compose.FooterActions -import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel -import com.android.systemui.scene.shared.model.Scene -import com.android.systemui.scene.shared.model.SceneDataSourceDelegator -import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.scene.ui.composable.ComposableScene -import com.android.systemui.scene.ui.composable.SceneContainer -import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel -import com.android.systemui.volume.panel.ui.composable.VolumePanelRoot -import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn - -/** The Compose facade, when Compose is available. */ -object ComposeFacade : BaseComposeFacade { - override fun isComposeAvailable(): Boolean = true - - override fun composeInitializer(): ComposeInitializer = ComposeInitializerImpl - - override fun setPeopleSpaceActivityContent( - activity: ComponentActivity, - viewModel: PeopleViewModel, - onResult: (PeopleViewModel.Result) -> Unit, - ) { - activity.setContent { PlatformTheme { PeopleScreen(viewModel, onResult) } } - } - - override fun setCommunalEditWidgetActivityContent( - activity: ComponentActivity, - viewModel: BaseCommunalViewModel, - widgetConfigurator: WidgetConfigurator, - onOpenWidgetPicker: () -> Unit, - onEditDone: () -> Unit, - ) { - activity.setContent { - PlatformTheme { - Box( - modifier = - Modifier.fillMaxSize() - .background(LocalAndroidColorScheme.current.outlineVariant), - ) { - CommunalHub( - viewModel = viewModel, - onOpenWidgetPicker = onOpenWidgetPicker, - widgetConfigurator = widgetConfigurator, - onEditDone = onEditDone, - ) - } - } - } - } - - override fun setVolumePanelActivityContent( - activity: ComponentActivity, - viewModel: VolumePanelViewModel, - onDismiss: () -> Unit, - ) { - activity.setContent { - VolumePanelRoot( - viewModel = viewModel, - onDismiss = onDismiss, - ) - } - } - - override fun createFooterActionsView( - context: Context, - viewModel: FooterActionsViewModel, - qsVisibilityLifecycleOwner: LifecycleOwner, - ): View { - return DensityAwareComposeView(context).apply { - setContent { PlatformTheme { FooterActions(viewModel, qsVisibilityLifecycleOwner) } } - } - } - - override fun createSceneContainerView( - scope: CoroutineScope, - context: Context, - viewModel: SceneContainerViewModel, - windowInsets: StateFlow<WindowInsets?>, - sceneByKey: Map<SceneKey, Scene>, - dataSourceDelegator: SceneDataSourceDelegator, - ): View { - return ComposeView(context).apply { - setContent { - PlatformTheme { - ScreenDecorProvider( - displayCutout = displayCutoutFromWindowInsets(scope, context, windowInsets), - screenCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context) - ) { - SceneContainer( - viewModel = viewModel, - sceneByKey = - sceneByKey.mapValues { (_, scene) -> scene as ComposableScene }, - dataSourceDelegator = dataSourceDelegator, - ) - } - } - } - } - } - - override fun createStickyKeysIndicatorContent( - context: Context, - viewModel: StickyKeysIndicatorViewModel - ): View { - return createStickyKeyIndicatorView(context, viewModel) - } - - override fun createCommunalView( - context: Context, - viewModel: BaseCommunalViewModel, - ): View { - return ComposeView(context).apply { - setContent { PlatformTheme { CommunalHub(viewModel = viewModel) } } - } - } - - override fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View { - return ComposeView(context).apply { - setContent { PlatformTheme { CommunalContainer(viewModel = viewModel) } } - } - } - - // TODO(b/298525212): remove once Compose exposes window inset bounds. - private fun displayCutoutFromWindowInsets( - scope: CoroutineScope, - context: Context, - windowInsets: StateFlow<WindowInsets?>, - ): StateFlow<DisplayCutout> = - windowInsets - .map { - val boundingRect = it?.displayCutout?.boundingRectTop - val width = boundingRect?.let { boundingRect.right - boundingRect.left } ?: 0 - val left = boundingRect?.left?.toDp(context) ?: 0.dp - val top = boundingRect?.top?.toDp(context) ?: 0.dp - val right = boundingRect?.right?.toDp(context) ?: 0.dp - val bottom = boundingRect?.bottom?.toDp(context) ?: 0.dp - val location = - when { - width <= 0f -> CutoutLocation.NONE - left <= 0.dp -> CutoutLocation.LEFT - right >= getDisplayWidth(context) -> CutoutLocation.RIGHT - else -> CutoutLocation.CENTER - } - DisplayCutout( - left, - top, - right, - bottom, - location, - ) - } - .stateIn(scope, SharingStarted.WhileSubscribed(), DisplayCutout()) - - // TODO(b/298525212): remove once Compose exposes window inset bounds. - private fun getDisplayWidth(context: Context): Dp { - val point = Point() - checkNotNull(context.display).getRealSize(point) - return point.x.dp - } - - // TODO(b/298525212): remove once Compose exposes window inset bounds. - private fun Int.toDp(context: Context): Dp { - return (this.toFloat() / context.resources.displayMetrics.density).dp - } - - override fun createBouncer( - context: Context, - viewModel: BouncerViewModel, - dialogFactory: BouncerDialogFactory, - ): View { - return ComposeView(context).apply { - setContent { PlatformTheme { BouncerContent(viewModel, dialogFactory) } } - } - } - - override fun createLockscreen( - context: Context, - viewModel: LockscreenContentViewModel, - blueprints: Set<@JvmSuppressWildcards LockscreenSceneBlueprint>, - ): View { - val sceneBlueprints = - blueprints.mapNotNull { it as? ComposableLockscreenSceneBlueprint }.toSet() - return ComposeView(context).apply { - setContent { - LockscreenContent(viewModel = viewModel, blueprints = sceneBlueprints) - .Content(modifier = Modifier.fillMaxSize()) - } - } - } -} diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt deleted file mode 100644 index 1674591c30b5..000000000000 --- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt +++ /dev/null @@ -1,77 +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.compose - -import android.view.View -import androidx.lifecycle.findViewTreeLifecycleOwner -import androidx.lifecycle.setViewTreeLifecycleOwner -import androidx.lifecycle.Lifecycle -import androidx.savedstate.SavedStateRegistryController -import androidx.savedstate.SavedStateRegistryOwner -import com.android.compose.animation.ViewTreeSavedStateRegistryOwner -import com.android.systemui.lifecycle.ViewLifecycleOwner - -internal object ComposeInitializerImpl : ComposeInitializer { - override fun onAttachedToWindow(root: View) { - if (root.findViewTreeLifecycleOwner() != null) { - error("root $root already has a LifecycleOwner") - } - - val parent = root.parent - if (parent is View && parent.id != android.R.id.content) { - error( - "ComposeInitializer.onAttachedToWindow(View) must be called on the content child." + - "Outside of activities and dialogs, this is usually the top-most View of a " + - "window." - ) - } - - // The lifecycle owner, which is STARTED when [root] is visible and RESUMED when [root] is - // both visible and focused. - val lifecycleOwner = ViewLifecycleOwner(root) - - // We create a trivial implementation of [SavedStateRegistryOwner] that does not do any save - // or restore because SystemUI process is always running and top-level windows using this - // initializer are created once, when the process is started. - val savedStateRegistryOwner = - object : SavedStateRegistryOwner { - private val savedStateRegistryController = - SavedStateRegistryController.create(this).apply { performRestore(null) } - - override val savedStateRegistry = savedStateRegistryController.savedStateRegistry - - override val lifecycle: Lifecycle - get() = lifecycleOwner.lifecycle - } - - // We must call [ViewLifecycleOwner.onCreate] after creating the [SavedStateRegistryOwner] - // because `onCreate` might move the lifecycle state to STARTED which will make - // [SavedStateRegistryController.performRestore] throw. - lifecycleOwner.onCreate() - - // Set the owners on the root. They will be reused by any ComposeView inside the root - // hierarchy. - root.setViewTreeLifecycleOwner(lifecycleOwner) - ViewTreeSavedStateRegistryOwner.set(root, savedStateRegistryOwner) - } - - override fun onDetachedFromWindow(root: View) { - (root.findViewTreeLifecycleOwner() as ViewLifecycleOwner).onDestroy() - root.setViewTreeLifecycleOwner(null) - ViewTreeSavedStateRegistryOwner.set(root, null) - } -} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt index 0469cbe519ea..3ec5508c81b3 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt @@ -22,15 +22,17 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import com.android.compose.animation.scene.Back import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.Swipe +import com.android.compose.animation.scene.SwipeDirection +import com.android.compose.animation.scene.UserAction +import com.android.compose.animation.scene.UserActionResult import com.android.systemui.bouncer.ui.BouncerDialogFactory import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.scene.shared.model.Direction -import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.scene.shared.model.UserAction -import com.android.systemui.scene.shared.model.UserActionResult +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.composable.ComposableScene import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow @@ -52,13 +54,13 @@ constructor( private val viewModel: BouncerViewModel, private val dialogFactory: BouncerDialogFactory, ) : ComposableScene { - override val key = SceneKey.Bouncer + override val key = Scenes.Bouncer override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> = MutableStateFlow( mapOf( - UserAction.Back to UserActionResult(SceneKey.Lockscreen), - UserAction.Swipe(Direction.DOWN) to UserActionResult(SceneKey.Lockscreen), + Back to UserActionResult(Scenes.Lockscreen), + Swipe(SwipeDirection.Down) to UserActionResult(Scenes.Lockscreen), ) ) .asStateFlow() diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt index bd539a740e81..2a13d4931b69 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt @@ -51,6 +51,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.android.compose.PlatformIconButton import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel +import com.android.systemui.common.ui.compose.SelectedUserAwareInputConnection import com.android.systemui.res.R /** UI for the input part of a password-requiring version of the bouncer. */ @@ -71,6 +72,7 @@ internal fun PasswordBouncer( val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState() val animateFailure: Boolean by viewModel.animateFailure.collectAsState() val isImeSwitcherButtonVisible by viewModel.isImeSwitcherButtonVisible.collectAsState() + val selectedUserId by viewModel.selectedUserId.collectAsState() DisposableEffect(Unit) { viewModel.onShown() @@ -87,47 +89,51 @@ internal fun PasswordBouncer( val color = MaterialTheme.colorScheme.onSurfaceVariant val lineWidthPx = with(LocalDensity.current) { 2.dp.toPx() } - TextField( - value = password, - onValueChange = viewModel::onPasswordInputChanged, - enabled = isInputEnabled, - visualTransformation = PasswordVisualTransformation(), - singleLine = true, - textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center), - keyboardOptions = - KeyboardOptions( - keyboardType = KeyboardType.Password, - imeAction = ImeAction.Done, - ), - keyboardActions = - KeyboardActions( - onDone = { viewModel.onAuthenticateKeyPressed() }, - ), - modifier = - modifier - .focusRequester(focusRequester) - .onFocusChanged { viewModel.onTextFieldFocusChanged(it.isFocused) } - .drawBehind { - drawLine( - color = color, - start = Offset(x = 0f, y = size.height - lineWidthPx), - end = Offset(size.width, y = size.height - lineWidthPx), - strokeWidth = lineWidthPx, - ) - } - .onInterceptKeyBeforeSoftKeyboard { keyEvent -> - if (keyEvent.key == Key.Back) { - viewModel.onImeDismissed() - true - } else { - false + SelectedUserAwareInputConnection(selectedUserId) { + TextField( + value = password, + onValueChange = viewModel::onPasswordInputChanged, + enabled = isInputEnabled, + visualTransformation = PasswordVisualTransformation(), + singleLine = true, + textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center), + keyboardOptions = + KeyboardOptions( + keyboardType = KeyboardType.Password, + imeAction = ImeAction.Done, + ), + keyboardActions = + KeyboardActions( + onDone = { viewModel.onAuthenticateKeyPressed() }, + ), + modifier = + modifier + .focusRequester(focusRequester) + .onFocusChanged { viewModel.onTextFieldFocusChanged(it.isFocused) } + .drawBehind { + drawLine( + color = color, + start = Offset(x = 0f, y = size.height - lineWidthPx), + end = Offset(size.width, y = size.height - lineWidthPx), + strokeWidth = lineWidthPx, + ) } - }, - trailingIcon = - if (isImeSwitcherButtonVisible) { - { ImeSwitcherButton(viewModel, color) } - } else null - ) + .onInterceptKeyBeforeSoftKeyboard { keyEvent -> + if (keyEvent.key == Key.Back) { + viewModel.onImeDismissed() + true + } else { + false + } + }, + trailingIcon = + if (isImeSwitcherButtonVisible) { + { ImeSwitcherButton(viewModel, color) } + } else { + null + } + ) + } } /** Button for changing the password input method (IME). */ diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt new file mode 100644 index 000000000000..c8e145034551 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt @@ -0,0 +1,78 @@ +/* + * 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. + */ + +@file:OptIn(ExperimentalComposeUiApi::class) + +package com.android.systemui.common.ui.compose + +import android.annotation.UserIdInt +import android.os.UserHandle +import android.view.inputmethod.EditorInfo +import android.view.inputmethod.InputConnection +import androidx.compose.runtime.Composable +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.platform.InterceptPlatformTextInput +import androidx.compose.ui.platform.PlatformTextInputMethodRequest + +/** + * Wrapper for input connection composables that need to be aware of the selected user to connect to + * the correct instance of on-device services like autofill, autocorrect, etc. + * + * Usage: + * ``` + * @Composable + * fun YourFunction(viewModel: YourViewModel) { + * val selectedUserId by viewModel.selectedUserId.collectAsState() + * + * SelectedUserAwareInputConnection(selectedUserId) { + * TextField(...) + * } + * } + * ``` + */ +@Composable +fun SelectedUserAwareInputConnection( + @UserIdInt selectedUserId: Int, + content: @Composable () -> Unit, +) { + InterceptPlatformTextInput( + interceptor = { request, nextHandler -> + // Create a new request to wrap the incoming one with some custom logic. + val modifiedRequest = + object : PlatformTextInputMethodRequest { + override fun createInputConnection(outAttributes: EditorInfo): InputConnection { + val inputConnection = request.createInputConnection(outAttributes) + // After the original request finishes initializing the EditorInfo we can + // customize it. If we needed to we could also wrap the InputConnection + // before + // returning it. + updateEditorInfo(outAttributes) + return inputConnection + } + + fun updateEditorInfo(outAttributes: EditorInfo) { + outAttributes.targetInputMethodUser = UserHandle.of(selectedUserId) + } + } + + // Send our wrapping request to the next handler, which could be the system or another + // interceptor up the tree. + nextHandler.startInputMethod(modifiedRequest) + } + ) { + content() + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt index 7535a51675e3..9ee69bc065f6 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt @@ -14,7 +14,6 @@ import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.FixedSizeEdgeDetector import com.android.compose.animation.scene.LowestZIndexScenePicker -import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.SceneTransitionLayout @@ -24,14 +23,11 @@ import com.android.compose.animation.scene.observableTransitionState import com.android.compose.animation.scene.transitions import com.android.compose.animation.scene.updateSceneTransitionLayoutState import com.android.compose.theme.LocalAndroidColorScheme -import com.android.systemui.communal.shared.model.CommunalSceneKey -import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState +import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.ui.compose.extensions.allowGestures import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.res.R -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.transform object Communal { object Elements { @@ -41,7 +37,7 @@ object Communal { } val sceneTransitions = transitions { - to(TransitionSceneKey.Communal) { + to(CommunalScenes.Communal) { spec = tween(durationMillis = 1000) translate(Communal.Elements.Content, Edge.Right) timestampRange(startMillis = 167, endMillis = 334) { @@ -49,7 +45,7 @@ val sceneTransitions = transitions { fade(Communal.Elements.Content) } } - to(TransitionSceneKey.Blank) { + to(CommunalScenes.Blank) { spec = tween(durationMillis = 1000) translate(Communal.Elements.Content, Edge.Right) timestampRange(endMillis = 167) { fade(Communal.Elements.Content) } @@ -68,14 +64,11 @@ fun CommunalContainer( modifier: Modifier = Modifier, viewModel: CommunalViewModel, ) { - val currentScene: SceneKey by - viewModel.currentScene - .transform { value -> emit(value.toTransitionSceneKey()) } - .collectAsState(TransitionSceneKey.Blank) + val currentScene: SceneKey by viewModel.currentScene.collectAsState(CommunalScenes.Blank) val sceneTransitionLayoutState = updateSceneTransitionLayoutState( currentScene, - onChangeScene = { viewModel.onSceneChanged(it.toCommunalSceneKey()) }, + onChangeScene = { viewModel.onSceneChanged(it) }, transitions = sceneTransitions, ) val touchesAllowed by viewModel.touchesAllowed.collectAsState(initial = false) @@ -83,9 +76,7 @@ fun CommunalContainer( // This effect exposes the SceneTransitionLayout's observable transition state to the rest of // the system, and unsets it when the view is disposed to avoid a memory leak. DisposableEffect(viewModel, sceneTransitionLayoutState) { - viewModel.setTransitionState( - sceneTransitionLayoutState.observableTransitionState().map { it.toModel() } - ) + viewModel.setTransitionState(sceneTransitionLayoutState.observableTransitionState()) onDispose { viewModel.setTransitionState(null) } } @@ -98,11 +89,10 @@ fun CommunalContainer( ), ) { scene( - TransitionSceneKey.Blank, + CommunalScenes.Blank, userActions = mapOf( - Swipe(SwipeDirection.Left, fromSource = Edge.Right) to - TransitionSceneKey.Communal + Swipe(SwipeDirection.Left, fromSource = Edge.Right) to CommunalScenes.Communal ) ) { // This scene shows nothing only allowing for transitions to the communal scene. @@ -110,11 +100,9 @@ fun CommunalContainer( } scene( - TransitionSceneKey.Communal, + CommunalScenes.Communal, userActions = - mapOf( - Swipe(SwipeDirection.Right, fromSource = Edge.Left) to TransitionSceneKey.Blank - ), + mapOf(Swipe(SwipeDirection.Right, fromSource = Edge.Left) to CommunalScenes.Blank), ) { CommunalScene(viewModel, modifier = modifier) } @@ -135,39 +123,3 @@ private fun SceneScope.CommunalScene( ) Box(modifier.element(Communal.Elements.Content)) { CommunalHub(viewModel = viewModel) } } - -// TODO(b/315490861): Remove these conversions once Compose can be used throughout SysUI. -object TransitionSceneKey { - val Blank = CommunalSceneKey.Blank.toTransitionSceneKey() - val Communal = CommunalSceneKey.Communal.toTransitionSceneKey() -} - -// TODO(b/315490861): Remove these conversions once Compose can be used throughout SysUI. -fun SceneKey.toCommunalSceneKey(): CommunalSceneKey { - return this.identity as CommunalSceneKey -} - -// TODO(b/315490861): Remove these conversions once Compose can be used throughout SysUI. -fun CommunalSceneKey.toTransitionSceneKey(): SceneKey { - return SceneKey(debugName = toString(), identity = this) -} - -/** - * Converts between the [SceneTransitionLayout] state class and our forked data class that can be - * used throughout SysUI. - */ -// TODO(b/315490861): Remove these conversions once Compose can be used throughout SysUI. -fun ObservableTransitionState.toModel(): ObservableCommunalTransitionState { - return when (this) { - is ObservableTransitionState.Idle -> - ObservableCommunalTransitionState.Idle(scene.toCommunalSceneKey()) - is ObservableTransitionState.Transition -> - ObservableCommunalTransitionState.Transition( - fromScene = fromScene.toCommunalSceneKey(), - toScene = toScene.toCommunalSceneKey(), - progress = progress, - isInitiatedByUserInput = isInitiatedByUserInput, - isUserInputOngoing = isUserInputOngoing, - ) - } -} 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 078da1c863ce..515c8169f1c4 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 @@ -31,8 +31,8 @@ import androidx.compose.foundation.Image 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.ColumnScope import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -93,6 +93,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTagsAsResourceId @@ -118,6 +119,7 @@ import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset import com.android.systemui.communal.ui.compose.extensions.observeTapsWithoutConsuming import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel +import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.communal.widgets.WidgetConfigurator import com.android.systemui.res.R import kotlinx.coroutines.launch @@ -197,26 +199,37 @@ fun CommunalHub( } }, ) { - CommunalHubLazyGrid( - communalContent = communalContent, - viewModel = viewModel, - contentPadding = contentPadding, - contentOffset = contentOffset, - setGridCoordinates = { gridCoordinates = it }, - updateDragPositionForRemove = { offset -> - isDraggingToRemove = - isPointerWithinCoordinates( - offset = gridCoordinates?.let { it.positionInWindow() + offset }, - containerToCheck = removeButtonCoordinates - ) - isDraggingToRemove - }, - onOpenWidgetPicker = onOpenWidgetPicker, - gridState = gridState, - contentListState = contentListState, - selectedKey = selectedKey, - widgetConfigurator = widgetConfigurator, - ) + Column(Modifier.align(Alignment.TopStart)) { + CommunalHubLazyGrid( + communalContent = communalContent, + viewModel = viewModel, + contentPadding = contentPadding, + contentOffset = contentOffset, + setGridCoordinates = { gridCoordinates = it }, + updateDragPositionForRemove = { offset -> + isDraggingToRemove = + isPointerWithinCoordinates( + offset = gridCoordinates?.let { it.positionInWindow() + offset }, + containerToCheck = removeButtonCoordinates + ) + isDraggingToRemove + }, + onOpenWidgetPicker = onOpenWidgetPicker, + gridState = gridState, + contentListState = contentListState, + selectedKey = selectedKey, + widgetConfigurator = widgetConfigurator, + ) + // TODO(b/326060686): Remove this once keyguard indication area can persist over hub + if (viewModel is CommunalViewModel) { + val isUnlocked by viewModel.deviceUnlocked.collectAsState(initial = false) + Spacer(Modifier.height(24.dp)) + LockStateIcon( + isUnlocked = isUnlocked, + modifier = Modifier.align(Alignment.CenterHorizontally), + ) + } + } if (viewModel.isEditMode && onOpenWidgetPicker != null && onEditDone != null) { Toolbar( @@ -268,7 +281,7 @@ fun CommunalHub( @OptIn(ExperimentalFoundationApi::class) @Composable -private fun BoxScope.CommunalHubLazyGrid( +private fun ColumnScope.CommunalHubLazyGrid( communalContent: List<CommunalContentModel>, viewModel: BaseCommunalViewModel, contentPadding: PaddingValues, @@ -282,7 +295,7 @@ private fun BoxScope.CommunalHubLazyGrid( widgetConfigurator: WidgetConfigurator?, ) { var gridModifier = - Modifier.align(Alignment.TopStart).onGloballyPositioned { setGridCoordinates(it) } + Modifier.align(Alignment.Start).onGloballyPositioned { setGridCoordinates(it) } var list = communalContent var dragDropState: GridDragDropState? = null if (viewModel.isEditMode && viewModel is CommunalEditModeViewModel) { @@ -364,6 +377,26 @@ private fun BoxScope.CommunalHubLazyGrid( } } +@Composable +private fun LockStateIcon( + isUnlocked: Boolean, + modifier: Modifier = Modifier, +) { + val colors = LocalAndroidColorScheme.current + val resource = + if (isUnlocked) { + R.drawable.ic_unlocked + } else { + R.drawable.ic_lock + } + Icon( + painter = painterResource(id = resource), + contentDescription = null, + tint = colors.onPrimaryContainer, + modifier = modifier.size(52.dp) + ) +} + /** * Toolbar that contains action buttons to * 1) open the widget picker diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt index 11a38f92c234..3d88ad53685e 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt @@ -19,12 +19,13 @@ package com.android.systemui.communal.ui.compose import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.Swipe +import com.android.compose.animation.scene.SwipeDirection +import com.android.compose.animation.scene.UserAction +import com.android.compose.animation.scene.UserActionResult import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.scene.shared.model.Direction -import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.scene.shared.model.UserAction -import com.android.systemui.scene.shared.model.UserActionResult +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.composable.ComposableScene import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow @@ -38,12 +39,12 @@ class CommunalScene constructor( private val viewModel: CommunalViewModel, ) : ComposableScene { - override val key = SceneKey.Communal + override val key = Scenes.Communal override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> = MutableStateFlow<Map<UserAction, UserActionResult>>( mapOf( - UserAction.Swipe(Direction.RIGHT) to UserActionResult(SceneKey.Lockscreen), + Swipe(SwipeDirection.Right) to UserActionResult(Scenes.Lockscreen), ) ) .asStateFlow() diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt index dd8664696973..a8d801abdcd0 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt @@ -18,8 +18,11 @@ package com.android.systemui.keyboard.stickykeys.ui.view import android.content.Context import android.view.View +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface @@ -61,22 +64,32 @@ fun StickyKeysIndicator(viewModel: StickyKeysIndicatorViewModel) { @Composable fun StickyKeysIndicator(stickyKeys: Map<ModifierKey, Locked>, modifier: Modifier = Modifier) { Surface( - color = MaterialTheme.colorScheme.surface, + color = MaterialTheme.colorScheme.inverseSurface, shape = MaterialTheme.shapes.medium, - modifier = modifier + modifier = modifier.heightIn(min = 84.dp).width(96.dp) ) { Column( horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, modifier = Modifier.padding(16.dp) ) { - stickyKeys.forEach { (key, isLocked) -> - key(key) { - Text( - text = key.displayedText, - fontWeight = if (isLocked.locked) FontWeight.Bold else FontWeight.Normal - ) - } - } + stickyKeys.forEach { (key, isLocked) -> key(key) { StickyKeyText(key, isLocked) } } } } } + +@Composable +private fun StickyKeyText(key: ModifierKey, isLocked: Locked, modifier: Modifier = Modifier) { + Text( + text = key.displayedText, + fontWeight = if (isLocked.locked) FontWeight.Bold else FontWeight.Normal, + style = MaterialTheme.typography.bodyMedium, + color = + if (isLocked.locked) { + MaterialTheme.colorScheme.inverseOnSurface + } else { + MaterialTheme.colorScheme.outlineVariant + }, + modifier = modifier + ) +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt index dd043dbebaa6..a02781ba63f7 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt @@ -18,19 +18,20 @@ package com.android.systemui.keyguard.ui.composable import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import com.android.compose.animation.scene.Edge +import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.Swipe +import com.android.compose.animation.scene.SwipeDirection +import com.android.compose.animation.scene.UserAction +import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.animateSceneFloatAsState import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel import com.android.systemui.qs.ui.composable.QuickSettings -import com.android.systemui.scene.shared.model.Direction -import com.android.systemui.scene.shared.model.Edge -import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.scene.shared.model.UserAction -import com.android.systemui.scene.shared.model.UserActionResult +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.composable.ComposableScene import dagger.Lazy import javax.inject.Inject @@ -50,7 +51,7 @@ constructor( viewModel: LockscreenSceneViewModel, private val lockscreenContent: Lazy<LockscreenContent>, ) : ComposableScene { - override val key = SceneKey.Lockscreen + override val key = Scenes.Lockscreen override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> = combine(viewModel.upDestinationSceneKey, viewModel.leftDestinationSceneKey, ::Pair) @@ -80,11 +81,11 @@ constructor( left: SceneKey?, ): Map<UserAction, UserActionResult> { return buildMap { - up?.let { this[UserAction.Swipe(Direction.UP)] = UserActionResult(up) } - left?.let { this[UserAction.Swipe(Direction.LEFT)] = UserActionResult(left) } - this[UserAction.Swipe(fromEdge = Edge.TOP, direction = Direction.DOWN)] = - UserActionResult(SceneKey.QuickSettings) - this[UserAction.Swipe(direction = Direction.DOWN)] = UserActionResult(SceneKey.Shade) + up?.let { this[Swipe(SwipeDirection.Up)] = UserActionResult(up) } + left?.let { this[Swipe(SwipeDirection.Left)] = UserActionResult(left) } + this[Swipe(fromSource = Edge.Top, direction = SwipeDirection.Down)] = + UserActionResult(Scenes.QuickSettings) + this[Swipe(direction = SwipeDirection.Down)] = UserActionResult(Scenes.Shade) } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt index ef6ae2ecfec9..791d629179e6 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt @@ -71,8 +71,7 @@ import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadi import com.android.systemui.notifications.ui.composable.Notifications.Form import com.android.systemui.notifications.ui.composable.Notifications.TransitionThresholds.EXPANSION_FOR_MAX_CORNER_RADIUS import com.android.systemui.notifications.ui.composable.Notifications.TransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA -import com.android.systemui.scene.ui.composable.Gone -import com.android.systemui.scene.ui.composable.Shade +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.ui.composable.ShadeHeader import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationStackAppearanceViewBinder.SCRIM_CORNER_RADIUS import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel @@ -214,7 +213,7 @@ fun SceneScope.NotificationScrollingStack( // in step with the transition so that it is 0 when it completes. if ( scrimOffset.value < 0 && - layoutState.isTransitioning(from = Shade, to = Gone) + layoutState.isTransitioning(from = Scenes.Shade, to = Scenes.Gone) ) { IntOffset(x = 0, y = (scrimOffset.value * expansionFraction).roundToInt()) } else { @@ -226,7 +225,7 @@ fun SceneScope.NotificationScrollingStack( calculateCornerRadius( screenCornerRadius, { expansionFraction }, - layoutState.isTransitioningBetween(Gone, Shade) + layoutState.isTransitioningBetween(Scenes.Gone, Scenes.Shade) ) .let { RoundedCornerShape( @@ -250,7 +249,7 @@ fun SceneScope.NotificationScrollingStack( Modifier.fillMaxSize() .graphicsLayer { alpha = - if (layoutState.isTransitioningBetween(Gone, Shade)) { + if (layoutState.isTransitioningBetween(Scenes.Gone, Scenes.Shade)) { (expansionFraction / EXPANSION_FOR_MAX_SCRIM_ALPHA).coerceAtMost(1f) } else 1f } 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 5d0b9ba2c736..91b737d33418 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 @@ -37,14 +37,13 @@ import com.android.systemui.qs.ui.adapter.QSSceneAdapter import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Companion.Collapsing import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Expanding import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Unsquishing -import com.android.systemui.scene.ui.composable.QuickSettings as QuickSettingsSceneKey -import com.android.systemui.scene.ui.composable.Shade +import com.android.systemui.scene.shared.model.Scenes object QuickSettings { private val SCENES = setOf( - QuickSettingsSceneKey, - Shade, + Scenes.QuickSettings, + Scenes.Shade, ) object Elements { @@ -69,18 +68,20 @@ private fun SceneScope.stateForQuickSettingsContent( return when (val transitionState = layoutState.transitionState) { is TransitionState.Idle -> { when (transitionState.currentScene) { - Shade -> QSSceneAdapter.State.QQS - QuickSettingsSceneKey -> QSSceneAdapter.State.QS + Scenes.Shade -> QSSceneAdapter.State.QQS + Scenes.QuickSettings -> QSSceneAdapter.State.QS else -> QSSceneAdapter.State.CLOSED } } is TransitionState.Transition -> with(transitionState) { when { - fromScene == Shade && toScene == QuickSettingsSceneKey -> Expanding(progress) - fromScene == QuickSettingsSceneKey && toScene == Shade -> Collapsing(progress) - fromScene == Shade || toScene == Shade -> Unsquishing(squishiness) - fromScene == QuickSettingsSceneKey || toScene == QuickSettingsSceneKey -> { + fromScene == Scenes.Shade && toScene == Scenes.QuickSettings -> + Expanding(progress) + fromScene == Scenes.QuickSettings && toScene == Scenes.Shade -> + Collapsing(progress) + fromScene == Scenes.Shade || toScene == Scenes.Shade -> Unsquishing(squishiness) + fromScene == Scenes.QuickSettings || toScene == Scenes.QuickSettings -> { QSSceneAdapter.State.QS } else -> diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt index 6875bc544a55..3b8b863fdde2 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt @@ -63,9 +63,8 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.qs.footer.ui.compose.FooterActions import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel import com.android.systemui.res.R -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.composable.ComposableScene -import com.android.systemui.scene.ui.composable.asComposeAware import com.android.systemui.shade.ui.composable.CollapsedShadeHeader import com.android.systemui.shade.ui.composable.ExpandedShadeHeader import com.android.systemui.shade.ui.composable.Shade @@ -89,7 +88,7 @@ constructor( private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory, private val statusBarIconController: StatusBarIconController, ) : ComposableScene { - override val key = SceneKey.QuickSettings + override val key = Scenes.QuickSettings override val destinationScenes = viewModel.destinationScenes.stateIn( @@ -140,9 +139,7 @@ private fun SceneScope.QuickSettingsScene( val isScrollable = when (val state = layoutState.transitionState) { is TransitionState.Idle -> true - is TransitionState.Transition -> { - state.fromScene == SceneKey.QuickSettings.asComposeAware() - } + is TransitionState.Transition -> state.fromScene == Scenes.QuickSettings } LaunchedEffect(isCustomizing, scrollState) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposeAwareExtensions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposeAwareExtensions.kt deleted file mode 100644 index 0de4650f1248..000000000000 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposeAwareExtensions.kt +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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.scene.ui.composable - -import com.android.compose.animation.scene.Back -import com.android.compose.animation.scene.Edge as ComposeAwareEdge -import com.android.compose.animation.scene.SceneKey as ComposeAwareSceneKey -import com.android.compose.animation.scene.Swipe -import com.android.compose.animation.scene.SwipeDirection -import com.android.compose.animation.scene.TransitionKey as ComposeAwareTransitionKey -import com.android.compose.animation.scene.UserAction as ComposeAwareUserAction -import com.android.compose.animation.scene.UserActionResult as ComposeAwareUserActionResult -import com.android.systemui.scene.shared.model.Direction -import com.android.systemui.scene.shared.model.Edge -import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.scene.shared.model.TransitionKey -import com.android.systemui.scene.shared.model.UserAction -import com.android.systemui.scene.shared.model.UserActionResult - -// TODO(b/293899074): remove this file once we can use the types from SceneTransitionLayout. - -fun SceneKey.asComposeAware(): ComposeAwareSceneKey { - return ComposeAwareSceneKey( - debugName = toString(), - identity = this, - ) -} - -fun TransitionKey.asComposeAware(): ComposeAwareTransitionKey { - return ComposeAwareTransitionKey( - debugName = debugName, - identity = this, - ) -} - -fun UserAction.asComposeAware(): ComposeAwareUserAction { - return when (this) { - is UserAction.Swipe -> - Swipe( - pointerCount = pointerCount, - fromSource = - when (this.fromEdge) { - null -> null - Edge.LEFT -> ComposeAwareEdge.Left - Edge.TOP -> ComposeAwareEdge.Top - Edge.RIGHT -> ComposeAwareEdge.Right - Edge.BOTTOM -> ComposeAwareEdge.Bottom - }, - direction = - when (this.direction) { - Direction.LEFT -> SwipeDirection.Left - Direction.UP -> SwipeDirection.Up - Direction.RIGHT -> SwipeDirection.Right - Direction.DOWN -> SwipeDirection.Down - } - ) - is UserAction.Back -> Back - } -} - -fun UserActionResult.asComposeAware(): ComposeAwareUserActionResult { - val composeUnaware = this - return ComposeAwareUserActionResult( - toScene = composeUnaware.toScene.asComposeAware(), - transitionKey = composeUnaware.transitionKey?.asComposeAware(), - ) -} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposeUnawareExtensions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposeUnawareExtensions.kt deleted file mode 100644 index 4c03664fc244..000000000000 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposeUnawareExtensions.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.scene.ui.composable - -import com.android.compose.animation.scene.ObservableTransitionState as ComposeAwareObservableTransitionState -import com.android.compose.animation.scene.SceneKey as ComposeAwareSceneKey -import com.android.systemui.scene.shared.model.ObservableTransitionState -import com.android.systemui.scene.shared.model.SceneKey - -fun ComposeAwareSceneKey.asComposeUnaware(): SceneKey { - return this.identity as SceneKey -} - -fun ComposeAwareObservableTransitionState.asComposeUnaware(): ObservableTransitionState { - return when (this) { - is ComposeAwareObservableTransitionState.Idle -> - ObservableTransitionState.Idle(scene.asComposeUnaware()) - is ComposeAwareObservableTransitionState.Transition -> - ObservableTransitionState.Transition( - fromScene = fromScene.asComposeUnaware(), - toScene = toScene.asComposeUnaware(), - progress = progress, - isInitiatedByUserInput = isInitiatedByUserInput, - isUserInputOngoing = isUserInputOngoing, - ) - } -} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt index 9ca751e81eed..82f56abbded6 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt @@ -19,17 +19,17 @@ package com.android.systemui.scene.ui.composable import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.Swipe +import com.android.compose.animation.scene.SwipeDirection +import com.android.compose.animation.scene.UserAction +import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.animateSceneFloatAsState import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.ui.composable.QuickSettings -import com.android.systemui.scene.shared.model.Direction -import com.android.systemui.scene.shared.model.Edge -import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.scene.shared.model.UserAction -import com.android.systemui.scene.shared.model.UserActionResult +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow @@ -46,18 +46,17 @@ class GoneScene constructor( private val notificationsViewModel: NotificationsPlaceholderViewModel, ) : ComposableScene { - override val key = SceneKey.Gone + override val key = Scenes.Gone override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> = MutableStateFlow<Map<UserAction, UserActionResult>>( mapOf( - UserAction.Swipe( + Swipe( pointerCount = 2, - fromEdge = Edge.TOP, - direction = Direction.DOWN, - ) to UserActionResult(SceneKey.QuickSettings), - UserAction.Swipe(direction = Direction.DOWN) to - UserActionResult(SceneKey.Shade), + fromSource = Edge.Top, + direction = SwipeDirection.Down, + ) to UserActionResult(Scenes.QuickSettings), + Swipe(direction = SwipeDirection.Down) to UserActionResult(Scenes.Shade), ) ) .asStateFlow() diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt index 9779d7170d0d..0fdaabe75306 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt @@ -35,15 +35,14 @@ import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.input.pointer.motionEventSpy import androidx.compose.ui.input.pointer.pointerInput import com.android.compose.animation.scene.MutableSceneTransitionLayoutState +import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.SceneTransitionLayout +import com.android.compose.animation.scene.UserAction +import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.observableTransitionState import com.android.systemui.ribbon.ui.composable.BottomRightCornerRibbon import com.android.systemui.scene.shared.model.SceneDataSourceDelegator -import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.scene.shared.model.UserAction -import com.android.systemui.scene.shared.model.UserActionResult import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel -import kotlinx.coroutines.flow.map /** * Renders a container of a collection of "scenes" that the user can switch between using certain @@ -77,8 +76,8 @@ fun SceneContainer( currentScene.destinationScenes.collectAsState() val state: MutableSceneTransitionLayoutState = remember { MutableSceneTransitionLayoutState( - initialScene = currentSceneKey.asComposeAware(), - canChangeScene = { toScene -> viewModel.canChangeScene(toScene.asComposeUnaware()) }, + initialScene = currentSceneKey, + canChangeScene = { toScene -> viewModel.canChangeScene(toScene) }, transitions = SceneContainerTransitions, ) } @@ -90,9 +89,7 @@ fun SceneContainer( } DisposableEffect(viewModel, state) { - viewModel.setTransitionState( - state.observableTransitionState().map { it.asComposeUnaware() } - ) + viewModel.setTransitionState(state.observableTransitionState()) onDispose { viewModel.setTransitionState(null) } } @@ -116,23 +113,17 @@ fun SceneContainer( ) { sceneByKey.forEach { (sceneKey, composableScene) -> scene( - key = sceneKey.asComposeAware(), + key = sceneKey, userActions = if (sceneKey == currentSceneKey) { - currentDestinations - } else { - composableScene.destinationScenes.value - } - .map { (userAction, userActionResult) -> - userAction.asComposeAware() to userActionResult.asComposeAware() - } - .toMap(), + currentDestinations + } else { + composableScene.destinationScenes.value + }, ) { with(composableScene) { this@scene.Content( - modifier = - Modifier.element(sceneKey.asComposeAware().rootElementKey) - .fillMaxSize(), + modifier = Modifier.element(sceneKey.rootElementKey).fillMaxSize(), ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt index 61f81209ad7e..dea9485e916c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt @@ -1,6 +1,7 @@ package com.android.systemui.scene.ui.composable import com.android.compose.animation.scene.transitions +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.TransitionKeys.CollapseShadeInstantly import com.android.systemui.scene.shared.model.TransitionKeys.SlightlyFasterShadeCollapse import com.android.systemui.scene.ui.composable.transitions.bouncerToGoneTransition @@ -26,41 +27,41 @@ import com.android.systemui.scene.ui.composable.transitions.shadeToQuickSettings * Please keep the list sorted alphabetically. */ val SceneContainerTransitions = transitions { - from(Bouncer, to = Gone) { bouncerToGoneTransition() } - from(Gone, to = Shade) { goneToShadeTransition() } + from(Scenes.Bouncer, to = Scenes.Gone) { bouncerToGoneTransition() } + from(Scenes.Gone, to = Scenes.Shade) { goneToShadeTransition() } from( - Gone, - to = Shade, - key = CollapseShadeInstantly.asComposeAware(), + Scenes.Gone, + to = Scenes.Shade, + key = CollapseShadeInstantly, ) { goneToShadeTransition(durationScale = 0.0) } from( - Gone, - to = Shade, - key = SlightlyFasterShadeCollapse.asComposeAware(), + Scenes.Gone, + to = Scenes.Shade, + key = SlightlyFasterShadeCollapse, ) { goneToShadeTransition(durationScale = 0.9) } - from(Gone, to = QuickSettings) { goneToQuickSettingsTransition() } - from(Lockscreen, to = Bouncer) { lockscreenToBouncerTransition() } - from(Lockscreen, to = Communal) { lockscreenToCommunalTransition() } - from(Lockscreen, to = Shade) { lockscreenToShadeTransition() } + from(Scenes.Gone, to = Scenes.QuickSettings) { goneToQuickSettingsTransition() } + from(Scenes.Lockscreen, to = Scenes.Bouncer) { lockscreenToBouncerTransition() } + from(Scenes.Lockscreen, to = Scenes.Communal) { lockscreenToCommunalTransition() } + from(Scenes.Lockscreen, to = Scenes.Shade) { lockscreenToShadeTransition() } from( - Lockscreen, - to = Shade, - key = CollapseShadeInstantly.asComposeAware(), + Scenes.Lockscreen, + to = Scenes.Shade, + key = CollapseShadeInstantly, ) { lockscreenToShadeTransition(durationScale = 0.0) } from( - Lockscreen, - to = Shade, - key = SlightlyFasterShadeCollapse.asComposeAware(), + Scenes.Lockscreen, + to = Scenes.Shade, + key = SlightlyFasterShadeCollapse, ) { lockscreenToShadeTransition(durationScale = 0.9) } - from(Lockscreen, to = QuickSettings) { lockscreenToQuickSettingsTransition() } - from(Lockscreen, to = Gone) { lockscreenToGoneTransition() } - from(Shade, to = QuickSettings) { shadeToQuickSettingsTransition() } + from(Scenes.Lockscreen, to = Scenes.QuickSettings) { lockscreenToQuickSettingsTransition() } + from(Scenes.Lockscreen, to = Scenes.Gone) { lockscreenToGoneTransition() } + from(Scenes.Shade, to = Scenes.QuickSettings) { shadeToQuickSettingsTransition() } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt index 60c0b7719a25..a54994df3dc9 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt @@ -20,10 +20,10 @@ package com.android.systemui.scene.ui.composable import com.android.compose.animation.scene.MutableSceneTransitionLayoutState import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.TransitionKey import com.android.compose.animation.scene.observableTransitionState import com.android.systemui.scene.shared.model.SceneDataSource -import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.scene.shared.model.TransitionKey import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted @@ -61,11 +61,10 @@ class SceneTransitionLayoutDataSource( } } } - .map { it.asComposeUnaware() } .stateIn( scope = coroutineScope, started = SharingStarted.WhileSubscribed(), - initialValue = state.transitionState.currentScene.asComposeUnaware(), + initialValue = state.transitionState.currentScene, ) override fun changeScene( @@ -73,8 +72,8 @@ class SceneTransitionLayoutDataSource( transitionKey: TransitionKey?, ) { state.setTargetScene( - targetScene = toScene.asComposeAware(), - transitionKey = transitionKey?.asComposeAware(), + targetScene = toScene, + transitionKey = transitionKey, coroutineScope = coroutineScope, ) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/TransitionSceneKeys.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/TransitionSceneKeys.kt deleted file mode 100644 index 5a9add1ad587..000000000000 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/TransitionSceneKeys.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.android.systemui.scene.ui.composable - -import com.android.systemui.scene.shared.model.SceneKey - -val Lockscreen = SceneKey.Lockscreen.asComposeAware() -val Bouncer = SceneKey.Bouncer.asComposeAware() -val Shade = SceneKey.Shade.asComposeAware() -val QuickSettings = SceneKey.QuickSettings.asComposeAware() -val Gone = SceneKey.Gone.asComposeAware() -val Communal = SceneKey.Communal.asComposeAware() diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromBouncerToGoneTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromBouncerToGoneTransition.kt index 1a9facea7518..5eefe490ab5b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromBouncerToGoneTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromBouncerToGoneTransition.kt @@ -2,10 +2,10 @@ package com.android.systemui.scene.ui.composable.transitions import androidx.compose.animation.core.tween import com.android.compose.animation.scene.TransitionBuilder -import com.android.systemui.scene.ui.composable.Bouncer +import com.android.systemui.scene.shared.model.Scenes fun TransitionBuilder.bouncerToGoneTransition() { spec = tween(durationMillis = 500) - fade(Bouncer.rootElementKey) + fade(Scenes.Bouncer.rootElementKey) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt index 291617f8edde..5bd158349f5e 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt @@ -3,10 +3,10 @@ package com.android.systemui.scene.ui.composable.transitions import androidx.compose.animation.core.tween import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.TransitionBuilder -import com.android.systemui.scene.ui.composable.QuickSettings +import com.android.systemui.scene.shared.model.Scenes fun TransitionBuilder.goneToQuickSettingsTransition() { spec = tween(durationMillis = 500) - translate(QuickSettings.rootElementKey, Edge.Top, true) + translate(Scenes.QuickSettings.rootElementKey, Edge.Top, true) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToCommunalTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToCommunalTransition.kt index ea8110ad8518..0021bf59d875 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToCommunalTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToCommunalTransition.kt @@ -19,15 +19,14 @@ package com.android.systemui.scene.ui.composable.transitions import androidx.compose.animation.core.tween import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.TransitionBuilder -import com.android.systemui.scene.ui.composable.Communal -import com.android.systemui.scene.ui.composable.Lockscreen +import com.android.systemui.scene.shared.model.Scenes fun TransitionBuilder.lockscreenToCommunalTransition() { spec = tween(durationMillis = 500) // Translate lockscreen to the left. - translate(Lockscreen.rootElementKey, Edge.Left) + translate(Scenes.Lockscreen.rootElementKey, Edge.Left) // Translate communal from the right. - translate(Communal.rootElementKey, Edge.Right) + translate(Scenes.Communal.rootElementKey, Edge.Right) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToGoneTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToGoneTransition.kt index da6306dc656d..3e576bc9d538 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToGoneTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToGoneTransition.kt @@ -2,10 +2,10 @@ package com.android.systemui.scene.ui.composable.transitions import androidx.compose.animation.core.tween import com.android.compose.animation.scene.TransitionBuilder -import com.android.systemui.scene.ui.composable.Lockscreen +import com.android.systemui.scene.shared.model.Scenes fun TransitionBuilder.lockscreenToGoneTransition() { spec = tween(durationMillis = 500) - fade(Lockscreen.rootElementKey) + fade(Scenes.Lockscreen.rootElementKey) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt index e63bc4e458eb..962d8227a016 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt @@ -3,10 +3,10 @@ package com.android.systemui.scene.ui.composable.transitions import androidx.compose.animation.core.tween import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.TransitionBuilder -import com.android.systemui.scene.ui.composable.QuickSettings +import com.android.systemui.scene.shared.model.Scenes fun TransitionBuilder.lockscreenToQuickSettingsTransition() { spec = tween(durationMillis = 500) - translate(QuickSettings.rootElementKey, Edge.Top, true) + translate(Scenes.QuickSettings.rootElementKey, Edge.Top, true) } 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 d7911eac8a61..12b07a3a69c2 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 @@ -63,8 +63,7 @@ import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout import com.android.systemui.privacy.OngoingPrivacyChip import com.android.systemui.res.R -import com.android.systemui.scene.ui.composable.QuickSettings -import com.android.systemui.scene.ui.composable.Shade as ShadeKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.ui.composable.ShadeHeader.Dimensions.CollapsedHeight import com.android.systemui.shade.ui.composable.ShadeHeader.Values.ClockScale import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel @@ -443,7 +442,7 @@ private fun SceneScope.StatusIcons( }, update = { iconContainer -> iconContainer.setQsExpansionTransitioning( - layoutState.isTransitioningBetween(ShadeKey, QuickSettings) + layoutState.isTransitioningBetween(Scenes.Shade, Scenes.QuickSettings) ) if (isSingleCarrier || !useExpandedFormat) { iconContainer.removeIgnoredSlots(carrierIconSlots) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt index 8484b7f5273f..3620cc570c66 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt @@ -41,7 +41,12 @@ import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.LowestZIndexScenePicker +import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.Swipe +import com.android.compose.animation.scene.SwipeDirection +import com.android.compose.animation.scene.UserAction +import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.animateSceneFloatAsState import com.android.compose.modifiers.thenIf import com.android.systemui.battery.BatteryMeterViewController @@ -56,10 +61,7 @@ import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL import com.android.systemui.notifications.ui.composable.NotificationScrollingStack import com.android.systemui.qs.ui.composable.QuickSettings import com.android.systemui.res.R -import com.android.systemui.scene.shared.model.Direction -import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.scene.shared.model.UserAction -import com.android.systemui.scene.shared.model.UserActionResult +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.composable.ComposableScene import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel import com.android.systemui.statusbar.phone.StatusBarIconController @@ -109,7 +111,7 @@ constructor( private val mediaCarouselController: MediaCarouselController, @Named(QUICK_QS_PANEL) private val mediaHost: MediaHost, ) : ComposableScene { - override val key = SceneKey.Shade + override val key = Scenes.Shade override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> = viewModel.upDestinationSceneKey @@ -144,8 +146,8 @@ constructor( up: SceneKey, ): Map<UserAction, UserActionResult> { return mapOf( - UserAction.Swipe(Direction.UP) to UserActionResult(up), - UserAction.Swipe(Direction.DOWN) to UserActionResult(SceneKey.QuickSettings), + Swipe(SwipeDirection.Up) to UserActionResult(up), + Swipe(SwipeDirection.Down) to UserActionResult(Scenes.QuickSettings), ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt index b3fcc305e6b5..53de5bc3a36d 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt @@ -27,12 +27,14 @@ import androidx.compose.animation.scaleOut import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.animation.togetherWith +import androidx.compose.foundation.background import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize @@ -137,12 +139,14 @@ constructor( } } ) { targetViewModel -> - Expandable( - modifier = Modifier.fillMaxSize(), - color = targetViewModel.backgroundColor.toColor(), - shape = RoundedCornerShape(12.dp), - onClick = { viewModel.onDeviceClick(it) }, - ) {} + Spacer( + modifier = + Modifier.fillMaxSize() + .background( + color = targetViewModel.backgroundColor.toColor(), + shape = RoundedCornerShape(12.dp), + ), + ) } transition.AnimatedContent( contentKey = { it.icon }, diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index 1e3842a1de68..b7e2dd13f321 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -391,7 +391,7 @@ interface SwipeSourceDetector { } /** The result of performing a [UserAction]. */ -class UserActionResult( +data class UserActionResult( /** The scene we should be transitioning to during the [UserAction]. */ val toScene: SceneKey, diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index 38dc24ed2f5f..9dbeeda42986 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -31,6 +31,7 @@ import android.view.WindowInsetsController import android.widget.FrameLayout import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState import com.android.internal.logging.MetricsLogger import com.android.internal.logging.UiEventLogger import com.android.internal.widget.LockPatternUtils @@ -65,8 +66,7 @@ import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.FakeSceneDataSource -import com.android.systemui.scene.shared.model.ObservableTransitionState -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR import com.android.systemui.statusbar.policy.ConfigurationController @@ -244,7 +244,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { sceneInteractor = kosmos.sceneInteractor keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor sceneTransitionStateFlow = - MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen)) + MutableStateFlow(ObservableTransitionState.Idle(Scenes.Lockscreen)) sceneInteractor.setTransitionState(sceneTransitionStateFlow) deviceEntryInteractor = kosmos.deviceEntryInteractor @@ -815,18 +815,18 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { // not enough to trigger a dismissal of the keyguard. underTest.onViewAttached() fakeSceneDataSource.pause() - sceneInteractor.changeScene(SceneKey.Bouncer, "reason") + sceneInteractor.changeScene(Scenes.Bouncer, "reason") sceneTransitionStateFlow.value = ObservableTransitionState.Transition( - SceneKey.Lockscreen, - SceneKey.Bouncer, + Scenes.Lockscreen, + Scenes.Bouncer, flowOf(.5f), false, isUserInputOngoing = flowOf(false), ) runCurrent() - fakeSceneDataSource.unpause(expectedScene = SceneKey.Bouncer) - sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer) + fakeSceneDataSource.unpause(expectedScene = Scenes.Bouncer) + sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Bouncer) runCurrent() verify(viewMediatorCallback, never()).keyguardDone(anyInt()) @@ -835,18 +835,18 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { kosmos.fakeDeviceEntryRepository.setUnlocked(true) runCurrent() fakeSceneDataSource.pause() - sceneInteractor.changeScene(SceneKey.Gone, "reason") + sceneInteractor.changeScene(Scenes.Gone, "reason") sceneTransitionStateFlow.value = ObservableTransitionState.Transition( - SceneKey.Bouncer, - SceneKey.Gone, + Scenes.Bouncer, + Scenes.Gone, flowOf(.5f), false, isUserInputOngoing = flowOf(false), ) runCurrent() - fakeSceneDataSource.unpause(expectedScene = SceneKey.Gone) - sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone) + fakeSceneDataSource.unpause(expectedScene = Scenes.Gone) + sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Gone) runCurrent() verify(viewMediatorCallback).keyguardDone(anyInt()) @@ -854,18 +854,18 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { // again. clearInvocations(viewMediatorCallback) fakeSceneDataSource.pause() - sceneInteractor.changeScene(SceneKey.Bouncer, "reason") + sceneInteractor.changeScene(Scenes.Bouncer, "reason") sceneTransitionStateFlow.value = ObservableTransitionState.Transition( - SceneKey.Gone, - SceneKey.Bouncer, + Scenes.Gone, + Scenes.Bouncer, flowOf(.5f), false, isUserInputOngoing = flowOf(false), ) runCurrent() - fakeSceneDataSource.unpause(expectedScene = SceneKey.Bouncer) - sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer) + fakeSceneDataSource.unpause(expectedScene = Scenes.Bouncer) + sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Bouncer) runCurrent() verify(viewMediatorCallback, never()).keyguardDone(anyInt()) @@ -874,35 +874,35 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { // does not dismiss the keyguard while we're not listening. underTest.onViewDetached() fakeSceneDataSource.pause() - sceneInteractor.changeScene(SceneKey.Gone, "reason") + sceneInteractor.changeScene(Scenes.Gone, "reason") sceneTransitionStateFlow.value = ObservableTransitionState.Transition( - SceneKey.Bouncer, - SceneKey.Gone, + Scenes.Bouncer, + Scenes.Gone, flowOf(.5f), false, isUserInputOngoing = flowOf(false), ) runCurrent() - fakeSceneDataSource.unpause(expectedScene = SceneKey.Gone) - sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone) + fakeSceneDataSource.unpause(expectedScene = Scenes.Gone) + sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Gone) runCurrent() verify(viewMediatorCallback, never()).keyguardDone(anyInt()) // While not listening, moving to the lockscreen does not dismiss the keyguard. fakeSceneDataSource.pause() - sceneInteractor.changeScene(SceneKey.Lockscreen, "reason") + sceneInteractor.changeScene(Scenes.Lockscreen, "reason") sceneTransitionStateFlow.value = ObservableTransitionState.Transition( - SceneKey.Gone, - SceneKey.Lockscreen, + Scenes.Gone, + Scenes.Lockscreen, flowOf(.5f), false, isUserInputOngoing = flowOf(false), ) runCurrent() - fakeSceneDataSource.unpause(expectedScene = SceneKey.Lockscreen) - sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Lockscreen) + fakeSceneDataSource.unpause(expectedScene = Scenes.Lockscreen) + sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Lockscreen) runCurrent() verify(viewMediatorCallback, never()).keyguardDone(anyInt()) @@ -910,18 +910,18 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { // gone scene now does dismiss the keyguard again, this time from lockscreen. underTest.onViewAttached() fakeSceneDataSource.pause() - sceneInteractor.changeScene(SceneKey.Gone, "reason") + sceneInteractor.changeScene(Scenes.Gone, "reason") sceneTransitionStateFlow.value = ObservableTransitionState.Transition( - SceneKey.Lockscreen, - SceneKey.Gone, + Scenes.Lockscreen, + Scenes.Gone, flowOf(.5f), false, isUserInputOngoing = flowOf(false), ) runCurrent() - fakeSceneDataSource.unpause(expectedScene = SceneKey.Gone) - sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone) + fakeSceneDataSource.unpause(expectedScene = Scenes.Gone) + sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Gone) runCurrent() verify(viewMediatorCallback).keyguardDone(anyInt()) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt index ad29e68f1bbf..df50eb64f8b6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.bouncer.ui.viewmodel import android.content.pm.UserInfo import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.SceneKey import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository import com.android.systemui.authentication.domain.interactor.authenticationInteractor @@ -33,7 +34,7 @@ import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor import com.android.systemui.kosmos.testScope import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.android.systemui.user.data.model.SelectedUserModel import com.android.systemui.user.data.model.SelectionStatus @@ -93,7 +94,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { assertThat(message?.text).isEqualTo(ENTER_YOUR_PASSWORD) assertThat(password).isEmpty() - assertThat(currentScene).isEqualTo(SceneKey.Bouncer) + assertThat(currentScene).isEqualTo(Scenes.Bouncer) assertThat(underTest.authenticationMethod).isEqualTo(AuthenticationMethodModel.Password) } @@ -125,7 +126,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { assertThat(message?.text).isEmpty() assertThat(password).isEqualTo("password") - assertThat(currentScene).isEqualTo(SceneKey.Bouncer) + assertThat(currentScene).isEqualTo(Scenes.Bouncer) } @Test @@ -163,7 +164,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { AuthenticationMethodModel.Password ) kosmos.fakeDeviceEntryRepository.setUnlocked(false) - switchToScene(SceneKey.Bouncer) + switchToScene(Scenes.Bouncer) // No input entered. @@ -209,14 +210,14 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { assertThat(password).isEqualTo("password") // The user doesn't confirm the password, but navigates back to the lockscreen instead. - switchToScene(SceneKey.Lockscreen) + switchToScene(Scenes.Lockscreen) // The user navigates to the bouncer again. - switchToScene(SceneKey.Bouncer) + switchToScene(Scenes.Bouncer) // Ensure the previously-entered password is not shown. assertThat(password).isEmpty() - assertThat(currentScene).isEqualTo(SceneKey.Bouncer) + assertThat(currentScene).isEqualTo(Scenes.Bouncer) } @Test @@ -330,8 +331,8 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { private fun TestScope.switchToScene(toScene: SceneKey) { val currentScene by collectLastValue(sceneInteractor.currentScene) - val bouncerShown = currentScene != SceneKey.Bouncer && toScene == SceneKey.Bouncer - val bouncerHidden = currentScene == SceneKey.Bouncer && toScene != SceneKey.Bouncer + val bouncerShown = currentScene != Scenes.Bouncer && toScene == Scenes.Bouncer + val bouncerHidden = currentScene == Scenes.Bouncer && toScene != Scenes.Bouncer sceneInteractor.changeScene(toScene, "reason") if (bouncerShown) underTest.onShown() if (bouncerHidden) underTest.onHidden() @@ -345,7 +346,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { AuthenticationMethodModel.Password ) kosmos.fakeDeviceEntryRepository.setUnlocked(false) - switchToScene(SceneKey.Bouncer) + switchToScene(Scenes.Bouncer) } private suspend fun TestScope.setLockout( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt index 32de1f2a892c..91a056ddd685 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.bouncer.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.SceneKey import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository import com.android.systemui.authentication.data.repository.authenticationRepository @@ -31,7 +32,7 @@ import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepositor import com.android.systemui.kosmos.testScope import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage @@ -86,7 +87,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { assertThat(message?.text).isEqualTo(ENTER_YOUR_PATTERN) assertThat(selectedDots).isEmpty() assertThat(currentDot).isNull() - assertThat(currentScene).isEqualTo(SceneKey.Bouncer) + assertThat(currentScene).isEqualTo(Scenes.Bouncer) assertThat(underTest.authenticationMethod).isEqualTo(AuthenticationMethodModel.Pattern) } @@ -104,7 +105,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { assertThat(message?.text).isEmpty() assertThat(selectedDots).isEmpty() assertThat(currentDot).isNull() - assertThat(currentScene).isEqualTo(SceneKey.Bouncer) + assertThat(currentScene).isEqualTo(Scenes.Bouncer) } @Test @@ -159,7 +160,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { assertThat(selectedDots).isEmpty() assertThat(currentDot).isNull() assertThat(message?.text).isEqualTo(WRONG_PATTERN) - assertThat(currentScene).isEqualTo(SceneKey.Bouncer) + assertThat(currentScene).isEqualTo(Scenes.Bouncer) } @Test @@ -369,8 +370,8 @@ class PatternBouncerViewModelTest : SysuiTestCase() { private fun TestScope.switchToScene(toScene: SceneKey) { val currentScene by collectLastValue(sceneInteractor.currentScene) - val bouncerShown = currentScene != SceneKey.Bouncer && toScene == SceneKey.Bouncer - val bouncerHidden = currentScene == SceneKey.Bouncer && toScene != SceneKey.Bouncer + val bouncerShown = currentScene != Scenes.Bouncer && toScene == Scenes.Bouncer + val bouncerHidden = currentScene == Scenes.Bouncer && toScene != Scenes.Bouncer sceneInteractor.changeScene(toScene, "reason") if (bouncerShown) underTest.onShown() if (bouncerHidden) underTest.onHidden() @@ -384,7 +385,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { AuthenticationMethodModel.Pattern ) kosmos.fakeDeviceEntryRepository.setUnlocked(false) - switchToScene(SceneKey.Bouncer) + switchToScene(Scenes.Bouncer) } companion object { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt index ccf7094e2bf7..7b75a3715415 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.bouncer.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.SceneKey import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository @@ -31,7 +32,7 @@ import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepositor import com.android.systemui.kosmos.testScope import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -196,7 +197,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { assertThat(message?.text).isEmpty() assertThat(pin).isEmpty() - assertThat(currentScene).isEqualTo(SceneKey.Bouncer) + assertThat(currentScene).isEqualTo(Scenes.Bouncer) } @Test @@ -230,7 +231,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { assertThat(pin).isEmpty() assertThat(message?.text).ignoringCase().isEqualTo(WRONG_PIN) - assertThat(currentScene).isEqualTo(SceneKey.Bouncer) + assertThat(currentScene).isEqualTo(Scenes.Bouncer) } @Test @@ -290,7 +291,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { assertThat(pin).isEmpty() assertThat(message?.text).ignoringCase().isEqualTo(WRONG_PIN) - assertThat(currentScene).isEqualTo(SceneKey.Bouncer) + assertThat(currentScene).isEqualTo(Scenes.Bouncer) } @Test @@ -304,10 +305,10 @@ class PinBouncerViewModelTest : SysuiTestCase() { assertThat(pin).isNotEmpty() // The user doesn't confirm the PIN, but navigates back to the lockscreen instead. - switchToScene(SceneKey.Lockscreen) + switchToScene(Scenes.Lockscreen) // The user navigates to the bouncer again. - switchToScene(SceneKey.Bouncer) + switchToScene(Scenes.Bouncer) // Ensure the previously-entered PIN is not shown. assertThat(pin).isEmpty() @@ -389,8 +390,8 @@ class PinBouncerViewModelTest : SysuiTestCase() { private fun TestScope.switchToScene(toScene: SceneKey) { val currentScene by collectLastValue(sceneInteractor.currentScene) - val bouncerShown = currentScene != SceneKey.Bouncer && toScene == SceneKey.Bouncer - val bouncerHidden = currentScene == SceneKey.Bouncer && toScene != SceneKey.Bouncer + val bouncerShown = currentScene != Scenes.Bouncer && toScene == Scenes.Bouncer + val bouncerHidden = currentScene == Scenes.Bouncer && toScene != Scenes.Bouncer sceneInteractor.changeScene(toScene, "reason") if (bouncerShown) underTest.onShown() if (bouncerHidden) underTest.onHidden() @@ -402,7 +403,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { private fun TestScope.lockDeviceAndOpenPinBouncer() { kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) kosmos.fakeDeviceEntryRepository.setUnlocked(false) - switchToScene(SceneKey.Bouncer) + switchToScene(Scenes.Bouncer) } companion object { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepositoryImplTest.kt new file mode 100644 index 000000000000..9cfa57257053 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepositoryImplTest.kt @@ -0,0 +1,125 @@ +/* + * 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.camera.data.repository + +import android.os.UserHandle +import android.provider.Settings +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectValues +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.util.settings.fakeSettings +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +@android.platform.test.annotations.EnabledOnRavenwood +class CameraAutoRotateRepositoryImplTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val testScope = kosmos.testScope + private val settings = kosmos.fakeSettings + private val testUser = UserHandle.of(1) + + private val underTest = + CameraAutoRotateRepositoryImpl(settings, testScope.testScheduler, testScope.backgroundScope) + + /** 3 changes => 3 change signals + 1 signal emitted at start => 4 signals */ + @Test + fun isCameraAutoRotateSettingEnabled_3times() = + testScope.runTest { + settings.putIntForUser(SETTING_NAME, DISABLE, testUser.identifier) + val isCameraAutoRotateSettingEnabled by + collectValues(underTest.isCameraAutoRotateSettingEnabled(testUser)) + runCurrent() + assertThat(isCameraAutoRotateSettingEnabled.last()).isFalse() + + settings.putIntForUser(SETTING_NAME, ENABLE, testUser.identifier) + runCurrent() + assertThat(isCameraAutoRotateSettingEnabled.last()).isTrue() + + settings.putIntForUser(SETTING_NAME, DISABLE, testUser.identifier) + runCurrent() + assertThat(isCameraAutoRotateSettingEnabled.last()).isFalse() + + settings.putIntForUser(SETTING_NAME, ENABLE, testUser.identifier) + runCurrent() + assertThat(isCameraAutoRotateSettingEnabled.last()).isTrue() + + assertThat(isCameraAutoRotateSettingEnabled).hasSize(4) + } + + @Test + fun isCameraAutoRotateSettingEnabled_emitsOnStart() = + testScope.runTest { + val isCameraAutoRotateSettingEnabled: List<Boolean> by + collectValues(underTest.isCameraAutoRotateSettingEnabled(testUser)) + + runCurrent() + + assertThat(isCameraAutoRotateSettingEnabled).hasSize(1) + } + + /** 0 for 0 changes + 1 signal emitted on start => 1 signal */ + @Test + fun isCameraAutoRotateSettingEnabled_0Times() = + testScope.runTest { + settings.putIntForUser(SETTING_NAME, DISABLE, testUser.identifier) + val isCameraAutoRotateSettingEnabled: List<Boolean> by + collectValues(underTest.isCameraAutoRotateSettingEnabled(testUser)) + runCurrent() + + settings.putIntForUser(SETTING_NAME, DISABLE, testUser.identifier) + runCurrent() + + assertThat(isCameraAutoRotateSettingEnabled).hasSize(1) + assertThat(isCameraAutoRotateSettingEnabled[0]).isFalse() + } + + /** Maintain that flows are cached by user */ + @Test + fun sameUserCallsIsCameraAutoRotateSettingEnabledTwice_getsSameFlow() = + testScope.runTest { + val flow1 = underTest.isCameraAutoRotateSettingEnabled(testUser) + val flow2 = underTest.isCameraAutoRotateSettingEnabled(testUser) + + assertThat(flow1).isEqualTo(flow2) + } + + @Test + fun differentUsersCallIsCameraAutoRotateSettingEnabled_getDifferentFlow() = + testScope.runTest { + val user2 = UserHandle.of(2) + val flow1 = underTest.isCameraAutoRotateSettingEnabled(testUser) + val flow2 = underTest.isCameraAutoRotateSettingEnabled(user2) + + assertThat(flow1).isNotEqualTo(flow2) + } + + private companion object { + private const val SETTING_NAME = Settings.Secure.CAMERA_AUTOROTATE + private const val DISABLE = 0 + private const val ENABLE = 1 + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepositoryImplTest.kt new file mode 100644 index 000000000000..29de58e2b28f --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepositoryImplTest.kt @@ -0,0 +1,104 @@ +/* + * 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.camera.data.repository + +import android.hardware.SensorPrivacyManager +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.coroutines.collectLastValue +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers +import org.mockito.Mockito + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +@android.platform.test.annotations.EnabledOnRavenwood +class CameraSensorPrivacyRepositoryImplTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val testScope = kosmos.testScope + private val testUser = UserHandle.of(1) + private val privacyManager = mock<SensorPrivacyManager>() + private val underTest = + CameraSensorPrivacyRepositoryImpl( + testScope.testScheduler, + testScope.backgroundScope, + privacyManager + ) + + @Test + fun isEnabled_2TimesForSameUserReturnsCachedFlow() = + testScope.runTest { + val flow1 = underTest.isEnabled(testUser) + val flow2 = underTest.isEnabled(testUser) + runCurrent() + + assertThat(flow1).isEqualTo(flow2) + } + + @Test + fun isEnabled_2TimesForDifferentUsersReturnsTwoDifferentFlows() = + testScope.runTest { + val user2 = UserHandle.of(2) + + val flow1 = underTest.isEnabled(testUser) + val flow2 = underTest.isEnabled(user2) + runCurrent() + + assertThat(flow1).isNotEqualTo(flow2) + } + + @Test + fun isEnabled_dataMatchesSensorPrivacyManager() = + testScope.runTest { + val isEnabled = collectLastValue(underTest.isEnabled(testUser)) + + val captor = + ArgumentCaptor.forClass( + SensorPrivacyManager.OnSensorPrivacyChangedListener::class.java + ) + runCurrent() + assertThat(isEnabled()).isEqualTo(false) + + Mockito.verify(privacyManager) + .addSensorPrivacyListener( + ArgumentMatchers.eq(SensorPrivacyManager.Sensors.CAMERA), + ArgumentMatchers.eq(testUser.identifier), + captor.capture() + ) + val sensorPrivacyCallback = captor.value!! + + sensorPrivacyCallback.onSensorPrivacyChanged(SensorPrivacyManager.Sensors.CAMERA, true) + runCurrent() + assertThat(isEnabled()).isEqualTo(true) + + sensorPrivacyCallback.onSensorPrivacyChanged(SensorPrivacyManager.Sensors.CAMERA, false) + runCurrent() + assertThat(isEnabled()).isEqualTo(false) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepositoryTest.kt new file mode 100644 index 000000000000..f75e036d78d4 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepositoryTest.kt @@ -0,0 +1,78 @@ +/* + * 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.camera.data.repository + +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.coroutines.collectValues +import com.android.systemui.kosmos.Kosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +@android.platform.test.annotations.EnabledOnRavenwood +class FakeCameraAutoRotateRepositoryTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val underTest = kosmos.fakeCameraAutoRotateRepository + private val testUser = UserHandle.of(1) + + @Test + fun isCameraAutoRotateSettingEnabled_emitsFalseOnStart() = runTest { + val isCameraAutoRotateSettingEnabled by + collectValues(underTest.isCameraAutoRotateSettingEnabled(testUser)) + + assertThat(isCameraAutoRotateSettingEnabled).hasSize(1) + assertThat(isCameraAutoRotateSettingEnabled.first()).isFalse() + } + + /** + * The value explicitly set in this test is not distinct, therefore only 1 value is collected. + */ + @Test + fun isCameraAutoRotateSettingEnabled_emitsDistinctValueOnly() = runTest { + val isCameraAutoRotateSettingEnabled by + collectValues(underTest.isCameraAutoRotateSettingEnabled(testUser)) + underTest.setEnabled(testUser, false) + runCurrent() + + assertThat(isCameraAutoRotateSettingEnabled).hasSize(1) + assertThat(isCameraAutoRotateSettingEnabled.first()).isFalse() + } + + @Test + fun isCameraAutoRotateSettingEnabled_canSetValue3Times() = runTest { + val isCameraAutoRotateSettingEnabled by + collectValues(underTest.isCameraAutoRotateSettingEnabled(testUser)) + runCurrent() + underTest.setEnabled(testUser, true) + runCurrent() + underTest.setEnabled(testUser, false) + runCurrent() + underTest.setEnabled(testUser, true) + runCurrent() + assertThat(isCameraAutoRotateSettingEnabled).hasSize(4) + assertThat(isCameraAutoRotateSettingEnabled.last()).isTrue() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepositoryTest.kt new file mode 100644 index 000000000000..7fa1be3d20ff --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepositoryTest.kt @@ -0,0 +1,75 @@ +/* + * 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.camera.data.repository + +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.coroutines.collectValues +import com.android.systemui.kosmos.Kosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +@android.platform.test.annotations.EnabledOnRavenwood +class FakeCameraSensorPrivacyRepositoryTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val underTest = kosmos.fakeCameraSensorPrivacyRepository + private val testUser = UserHandle.of(1) + + @Test + fun isCameraSensorPrivacyEnabled_emitsFalseOnStart() = runTest { + val isCameraSensorPrivacySettingEnabled by collectValues(underTest.isEnabled(testUser)) + + assertThat(isCameraSensorPrivacySettingEnabled).hasSize(1) + assertThat(isCameraSensorPrivacySettingEnabled.first()).isFalse() + } + + /** + * The value explicitly set in this test is not distinct, therefore only 1 value is collected. + */ + @Test + fun isCameraSensorPrivacyEnabled_emitsDistinctValueOnly() = runTest { + val isCameraSensorPrivacySettingEnabled by collectValues(underTest.isEnabled(testUser)) + underTest.setEnabled(testUser, false) + runCurrent() + + assertThat(isCameraSensorPrivacySettingEnabled).hasSize(1) + assertThat(isCameraSensorPrivacySettingEnabled.first()).isFalse() + } + + @Test + fun isCameraSensorPrivacyEnabled_canSetValue3Times() = runTest { + val isCameraSensorPrivacySettingEnabled by collectValues(underTest.isEnabled(testUser)) + runCurrent() + underTest.setEnabled(testUser, true) + runCurrent() + underTest.setEnabled(testUser, false) + runCurrent() + underTest.setEnabled(testUser, true) + runCurrent() + assertThat(isCameraSensorPrivacySettingEnabled).hasSize(4) + assertThat(isCameraSensorPrivacySettingEnabled.last()).isTrue() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt index 92396e0bcdef..ce6445b75fb9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt @@ -21,9 +21,8 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.interactor.setCommunalAvailable -import com.android.systemui.communal.shared.model.CommunalSceneKey +import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.dock.DockManager import com.android.systemui.dock.dockManager import com.android.systemui.dock.fakeDockManager import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository @@ -40,6 +39,7 @@ import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith @@ -79,8 +79,8 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope.runTest { val scene by collectLastValue(communalInteractor.desiredScene) - communalInteractor.onSceneChanged(CommunalSceneKey.Communal) - assertThat(scene).isEqualTo(CommunalSceneKey.Communal) + communalInteractor.onSceneChanged(CommunalScenes.Communal) + assertThat(scene).isEqualTo(CommunalScenes.Communal) fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.PRIMARY_BOUNCER, @@ -88,16 +88,17 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope = this ) - assertThat(scene).isEqualTo(CommunalSceneKey.Blank) + assertThat(scene).isEqualTo(CommunalScenes.Blank) } } + @Ignore("Ignored until custom animations are implemented in b/322787129") @Test fun deviceDocked_forceCommunalScene() = with(kosmos) { testScope.runTest { val scene by collectLastValue(communalInteractor.desiredScene) - assertThat(scene).isEqualTo(CommunalSceneKey.Blank) + assertThat(scene).isEqualTo(CommunalScenes.Blank) updateDocked(true) fakeKeyguardTransitionRepository.sendTransitionSteps( @@ -105,7 +106,24 @@ class CommunalSceneStartableTest : SysuiTestCase() { to = KeyguardState.LOCKSCREEN, testScope = this ) - assertThat(scene).isEqualTo(CommunalSceneKey.Communal) + assertThat(scene).isEqualTo(CommunalScenes.Communal) + } + } + + @Test + fun exitingDream_forceCommunalScene() = + with(kosmos) { + testScope.runTest { + val scene by collectLastValue(communalInteractor.desiredScene) + assertThat(scene).isEqualTo(CommunalScenes.Blank) + + updateDocked(true) + fakeKeyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.DREAMING, + to = KeyguardState.LOCKSCREEN, + testScope = this + ) + assertThat(scene).isEqualTo(CommunalScenes.Communal) } } @@ -114,7 +132,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { testScope.runTest { val scene by collectLastValue(communalInteractor.desiredScene) - assertThat(scene).isEqualTo(CommunalSceneKey.Blank) + assertThat(scene).isEqualTo(CommunalScenes.Blank) updateDocked(true) fakeKeyguardTransitionRepository.sendTransitionSteps( @@ -122,7 +140,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { to = KeyguardState.LOCKSCREEN, testScope = this ) - assertThat(scene).isEqualTo(CommunalSceneKey.Blank) + assertThat(scene).isEqualTo(CommunalScenes.Blank) } } @@ -131,19 +149,19 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { testScope.runTest { val scene by collectLastValue(communalInteractor.desiredScene) - communalInteractor.onSceneChanged(CommunalSceneKey.Communal) - assertThat(scene).isEqualTo(CommunalSceneKey.Communal) + communalInteractor.onSceneChanged(CommunalScenes.Communal) + assertThat(scene).isEqualTo(CommunalScenes.Communal) fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.OFF, testScope = this ) - assertThat(scene).isEqualTo(CommunalSceneKey.Communal) + assertThat(scene).isEqualTo(CommunalScenes.Communal) advanceTimeBy(CommunalSceneStartable.AWAKE_DEBOUNCE_DELAY) - assertThat(scene).isEqualTo(CommunalSceneKey.Blank) + assertThat(scene).isEqualTo(CommunalScenes.Blank) } } @@ -152,17 +170,17 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { testScope.runTest { val scene by collectLastValue(communalInteractor.desiredScene) - communalInteractor.onSceneChanged(CommunalSceneKey.Communal) - assertThat(scene).isEqualTo(CommunalSceneKey.Communal) + communalInteractor.onSceneChanged(CommunalScenes.Communal) + assertThat(scene).isEqualTo(CommunalScenes.Communal) fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.OFF, testScope = this ) - assertThat(scene).isEqualTo(CommunalSceneKey.Communal) + assertThat(scene).isEqualTo(CommunalScenes.Communal) advanceTimeBy(CommunalSceneStartable.AWAKE_DEBOUNCE_DELAY / 2) - assertThat(scene).isEqualTo(CommunalSceneKey.Communal) + assertThat(scene).isEqualTo(CommunalScenes.Communal) fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.OFF, @@ -171,15 +189,16 @@ class CommunalSceneStartableTest : SysuiTestCase() { ) advanceTimeBy(CommunalSceneStartable.AWAKE_DEBOUNCE_DELAY) - assertThat(scene).isEqualTo(CommunalSceneKey.Communal) + assertThat(scene).isEqualTo(CommunalScenes.Communal) } } + @Ignore("Ignored until custom animations are implemented in b/322787129") @Test fun dockingOnLockscreen_forcesCommunal() = with(kosmos) { testScope.runTest { - communalInteractor.onSceneChanged(CommunalSceneKey.Blank) + communalInteractor.onSceneChanged(CommunalScenes.Blank) val scene by collectLastValue(communalInteractor.desiredScene) // device is docked while on the lockscreen @@ -190,17 +209,18 @@ class CommunalSceneStartableTest : SysuiTestCase() { ) updateDocked(true) - assertThat(scene).isEqualTo(CommunalSceneKey.Blank) + assertThat(scene).isEqualTo(CommunalScenes.Blank) advanceTimeBy(CommunalSceneStartable.DOCK_DEBOUNCE_DELAY) - assertThat(scene).isEqualTo(CommunalSceneKey.Communal) + assertThat(scene).isEqualTo(CommunalScenes.Communal) } } + @Ignore("Ignored until custom animations are implemented in b/322787129") @Test fun dockingOnLockscreen_doesNotForceCommunalIfDreamStarts() = with(kosmos) { testScope.runTest { - communalInteractor.onSceneChanged(CommunalSceneKey.Blank) + communalInteractor.onSceneChanged(CommunalScenes.Blank) val scene by collectLastValue(communalInteractor.desiredScene) // device is docked while on the lockscreen @@ -211,9 +231,9 @@ class CommunalSceneStartableTest : SysuiTestCase() { ) updateDocked(true) - assertThat(scene).isEqualTo(CommunalSceneKey.Blank) + assertThat(scene).isEqualTo(CommunalScenes.Blank) advanceTimeBy(CommunalSceneStartable.DOCK_DEBOUNCE_DELAY / 2) - assertThat(scene).isEqualTo(CommunalSceneKey.Blank) + assertThat(scene).isEqualTo(CommunalScenes.Blank) // dream starts shortly after docking fakeKeyguardTransitionRepository.sendTransitionSteps( @@ -222,7 +242,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope = this ) advanceTimeBy(CommunalSceneStartable.DOCK_DEBOUNCE_DELAY) - assertThat(scene).isEqualTo(CommunalSceneKey.Blank) + assertThat(scene).isEqualTo(CommunalScenes.Blank) } } @@ -230,7 +250,8 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { runCurrent() fakeDockManager.setIsDocked(docked) - fakeDockManager.setDockEvent(DockManager.STATE_DOCKED) + // TODO(b/322787129): uncomment once custom animations are in place + // fakeDockManager.setDockEvent(DockManager.STATE_DOCKED) runCurrent() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt index 06b3806cb382..43acf3197fb1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt @@ -18,9 +18,9 @@ package com.android.systemui.communal.data.repository import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.SysuiTestCase -import com.android.systemui.communal.shared.model.CommunalSceneKey -import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState +import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.scene.data.repository.sceneContainerRepository @@ -60,20 +60,17 @@ class CommunalRepositoryImplTest : SysuiTestCase() { testScope.runTest { val transitionState by collectLastValue(underTest.transitionState) assertThat(transitionState) - .isEqualTo(ObservableCommunalTransitionState.Idle(CommunalSceneKey.DEFAULT)) + .isEqualTo(ObservableTransitionState.Idle(CommunalScenes.Default)) } @Test fun transitionState_setTransitionState_returnsNewValue() = testScope.runTest { - val expectedSceneKey = CommunalSceneKey.Communal - underTest.setTransitionState( - flowOf(ObservableCommunalTransitionState.Idle(expectedSceneKey)) - ) + val expectedSceneKey = CommunalScenes.Communal + underTest.setTransitionState(flowOf(ObservableTransitionState.Idle(expectedSceneKey))) val transitionState by collectLastValue(underTest.transitionState) - assertThat(transitionState) - .isEqualTo(ObservableCommunalTransitionState.Idle(expectedSceneKey)) + assertThat(transitionState).isEqualTo(ObservableTransitionState.Idle(expectedSceneKey)) } @Test @@ -81,7 +78,7 @@ class CommunalRepositoryImplTest : SysuiTestCase() { testScope.runTest { // Set a value for the transition state flow. underTest.setTransitionState( - flowOf(ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)) + flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal)) ) // Set the transition state flow back to null. @@ -90,6 +87,6 @@ class CommunalRepositoryImplTest : SysuiTestCase() { // Flow returns default scene key. val transitionState by collectLastValue(underTest.transitionState) assertThat(transitionState) - .isEqualTo(ObservableCommunalTransitionState.Idle(CommunalSceneKey.DEFAULT)) + .isEqualTo(ObservableTransitionState.Idle(CommunalScenes.Default)) } } 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 6e3573b64f9a..eafd5038759c 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 @@ -25,6 +25,7 @@ import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED import android.widget.RemoteViews import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl @@ -40,9 +41,8 @@ import com.android.systemui.communal.data.repository.fakeCommunalTutorialReposit import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.shared.model.CommunalContentSize -import com.android.systemui.communal.shared.model.CommunalSceneKey +import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.CommunalWidgetContentModel -import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState import com.android.systemui.communal.widgets.EditWidgetsActivityStarter import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags @@ -53,7 +53,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.settings.FakeUserTracker import com.android.systemui.settings.fakeUserTracker import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository @@ -462,9 +462,9 @@ class CommunalInteractorTest : SysuiTestCase() { var desiredScene = collectLastValue(underTest.desiredScene) runCurrent() - assertThat(desiredScene()).isEqualTo(CommunalSceneKey.Blank) + assertThat(desiredScene()).isEqualTo(CommunalScenes.Blank) - val targetScene = CommunalSceneKey.Communal + val targetScene = CommunalScenes.Communal communalRepository.setDesiredScene(targetScene) desiredScene = collectLastValue(underTest.desiredScene) runCurrent() @@ -474,7 +474,7 @@ class CommunalInteractorTest : SysuiTestCase() { @Test fun updatesScene() = testScope.runTest { - val targetScene = CommunalSceneKey.Communal + val targetScene = CommunalScenes.Communal underTest.onSceneChanged(targetScene) @@ -491,32 +491,32 @@ class CommunalInteractorTest : SysuiTestCase() { val desiredScene by collectLastValue(underTest.desiredScene) - underTest.onSceneChanged(CommunalSceneKey.Communal) - assertThat(desiredScene).isEqualTo(CommunalSceneKey.Communal) + underTest.onSceneChanged(CommunalScenes.Communal) + assertThat(desiredScene).isEqualTo(CommunalScenes.Communal) kosmos.setCommunalAvailable(false) runCurrent() // Scene returns blank when communal is not available. - assertThat(desiredScene).isEqualTo(CommunalSceneKey.Blank) + assertThat(desiredScene).isEqualTo(CommunalScenes.Blank) kosmos.setCommunalAvailable(true) runCurrent() // After re-enabling, scene goes back to Communal. - assertThat(desiredScene).isEqualTo(CommunalSceneKey.Communal) + assertThat(desiredScene).isEqualTo(CommunalScenes.Communal) } @Test fun transitionProgress_onTargetScene_fullProgress() = testScope.runTest { - val targetScene = CommunalSceneKey.Blank + val targetScene = CommunalScenes.Blank val transitionProgressFlow = underTest.transitionProgressToScene(targetScene) val transitionProgress by collectLastValue(transitionProgressFlow) val transitionState = - MutableStateFlow<ObservableCommunalTransitionState>( - ObservableCommunalTransitionState.Idle(targetScene) + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(targetScene) ) underTest.setTransitionState(transitionState) @@ -527,14 +527,14 @@ class CommunalInteractorTest : SysuiTestCase() { @Test fun transitionProgress_notOnTargetScene_noProgress() = testScope.runTest { - val targetScene = CommunalSceneKey.Blank - val currentScene = CommunalSceneKey.Communal + val targetScene = CommunalScenes.Blank + val currentScene = CommunalScenes.Communal val transitionProgressFlow = underTest.transitionProgressToScene(targetScene) val transitionProgress by collectLastValue(transitionProgressFlow) val transitionState = - MutableStateFlow<ObservableCommunalTransitionState>( - ObservableCommunalTransitionState.Idle(currentScene) + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(currentScene) ) underTest.setTransitionState(transitionState) @@ -545,14 +545,14 @@ class CommunalInteractorTest : SysuiTestCase() { @Test fun transitionProgress_transitioningToTrackedScene() = testScope.runTest { - val currentScene = CommunalSceneKey.Communal - val targetScene = CommunalSceneKey.Blank + val currentScene = CommunalScenes.Communal + val targetScene = CommunalScenes.Blank val transitionProgressFlow = underTest.transitionProgressToScene(targetScene) val transitionProgress by collectLastValue(transitionProgressFlow) var transitionState = - MutableStateFlow<ObservableCommunalTransitionState>( - ObservableCommunalTransitionState.Idle(currentScene) + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(currentScene) ) underTest.setTransitionState(transitionState) @@ -562,7 +562,7 @@ class CommunalInteractorTest : SysuiTestCase() { val progress = MutableStateFlow(0f) transitionState = MutableStateFlow( - ObservableCommunalTransitionState.Transition( + ObservableTransitionState.Transition( fromScene = currentScene, toScene = targetScene, progress = progress, @@ -581,7 +581,7 @@ class CommunalInteractorTest : SysuiTestCase() { assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Transition(1f)) // Transition finishes. - transitionState = MutableStateFlow(ObservableCommunalTransitionState.Idle(targetScene)) + transitionState = MutableStateFlow(ObservableTransitionState.Idle(targetScene)) underTest.setTransitionState(transitionState) assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(targetScene)) } @@ -589,14 +589,14 @@ class CommunalInteractorTest : SysuiTestCase() { @Test fun transitionProgress_transitioningAwayFromTrackedScene() = testScope.runTest { - val currentScene = CommunalSceneKey.Blank - val targetScene = CommunalSceneKey.Communal + val currentScene = CommunalScenes.Blank + val targetScene = CommunalScenes.Communal val transitionProgressFlow = underTest.transitionProgressToScene(currentScene) val transitionProgress by collectLastValue(transitionProgressFlow) var transitionState = - MutableStateFlow<ObservableCommunalTransitionState>( - ObservableCommunalTransitionState.Idle(currentScene) + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(currentScene) ) underTest.setTransitionState(transitionState) @@ -606,7 +606,7 @@ class CommunalInteractorTest : SysuiTestCase() { val progress = MutableStateFlow(0f) transitionState = MutableStateFlow( - ObservableCommunalTransitionState.Transition( + ObservableTransitionState.Transition( fromScene = currentScene, toScene = targetScene, progress = progress, @@ -627,7 +627,7 @@ class CommunalInteractorTest : SysuiTestCase() { assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.OtherTransition) // Transition finishes. - transitionState = MutableStateFlow(ObservableCommunalTransitionState.Idle(targetScene)) + transitionState = MutableStateFlow(ObservableTransitionState.Idle(targetScene)) underTest.setTransitionState(transitionState) assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(targetScene)) } @@ -642,7 +642,7 @@ class CommunalInteractorTest : SysuiTestCase() { runCurrent() assertThat(isCommunalShowing()).isEqualTo(false) - underTest.onSceneChanged(CommunalSceneKey.Communal) + underTest.onSceneChanged(CommunalScenes.Communal) isCommunalShowing = collectLastValue(underTest.isCommunalShowing) runCurrent() @@ -661,17 +661,17 @@ class CommunalInteractorTest : SysuiTestCase() { assertThat(isCommunalShowing).isFalse() // Verify scene changes with the flag doesn't have any impact - sceneInteractor.changeScene(SceneKey.Communal, loggingReason = "") + sceneInteractor.changeScene(Scenes.Communal, loggingReason = "") runCurrent() assertThat(isCommunalShowing).isFalse() // Verify scene changes (without the flag) to communal sets the value to true - underTest.onSceneChanged(CommunalSceneKey.Communal) + underTest.onSceneChanged(CommunalScenes.Communal) runCurrent() assertThat(isCommunalShowing).isTrue() // Verify scene changes (without the flag) to blank sets the value back to false - underTest.onSceneChanged(CommunalSceneKey.Blank) + underTest.onSceneChanged(CommunalScenes.Blank) runCurrent() assertThat(isCommunalShowing).isFalse() } @@ -687,17 +687,17 @@ class CommunalInteractorTest : SysuiTestCase() { assertThat(isCommunalShowing).isFalse() // Verify scene changes without the flag doesn't have any impact - underTest.onSceneChanged(CommunalSceneKey.Communal) + underTest.onSceneChanged(CommunalScenes.Communal) runCurrent() assertThat(isCommunalShowing).isFalse() // Verify scene changes (with the flag) to communal sets the value to true - sceneInteractor.changeScene(SceneKey.Communal, loggingReason = "") + sceneInteractor.changeScene(Scenes.Communal, loggingReason = "") runCurrent() assertThat(isCommunalShowing).isTrue() // Verify scene changes (with the flag) to lockscreen sets the value to false - sceneInteractor.changeScene(SceneKey.Lockscreen, loggingReason = "") + sceneInteractor.changeScene(Scenes.Lockscreen, loggingReason = "") runCurrent() assertThat(isCommunalShowing).isFalse() } @@ -706,8 +706,8 @@ class CommunalInteractorTest : SysuiTestCase() { fun isIdleOnCommunal() = testScope.runTest { val transitionState = - MutableStateFlow<ObservableCommunalTransitionState>( - ObservableCommunalTransitionState.Idle(CommunalSceneKey.Blank) + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(CommunalScenes.Blank) ) communalRepository.setTransitionState(transitionState) @@ -717,8 +717,7 @@ class CommunalInteractorTest : SysuiTestCase() { assertThat(isIdleOnCommunal).isEqualTo(false) // Transition to communal. - transitionState.value = - ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal) + transitionState.value = ObservableTransitionState.Idle(CommunalScenes.Communal) runCurrent() // isIdleOnCommunal is now true since we're on communal. @@ -726,9 +725,9 @@ class CommunalInteractorTest : SysuiTestCase() { // Start transition away from communal. transitionState.value = - ObservableCommunalTransitionState.Transition( - fromScene = CommunalSceneKey.Communal, - toScene = CommunalSceneKey.Blank, + ObservableTransitionState.Transition( + fromScene = CommunalScenes.Communal, + toScene = CommunalScenes.Blank, progress = flowOf(0f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -743,8 +742,8 @@ class CommunalInteractorTest : SysuiTestCase() { fun isCommunalVisible() = testScope.runTest { val transitionState = - MutableStateFlow<ObservableCommunalTransitionState>( - ObservableCommunalTransitionState.Idle(CommunalSceneKey.Blank) + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(CommunalScenes.Blank) ) communalRepository.setTransitionState(transitionState) @@ -754,9 +753,9 @@ class CommunalInteractorTest : SysuiTestCase() { // Start transition to communal. transitionState.value = - ObservableCommunalTransitionState.Transition( - fromScene = CommunalSceneKey.Blank, - toScene = CommunalSceneKey.Communal, + ObservableTransitionState.Transition( + fromScene = CommunalScenes.Blank, + toScene = CommunalScenes.Communal, progress = flowOf(0f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -766,17 +765,16 @@ class CommunalInteractorTest : SysuiTestCase() { assertThat(isCommunalVisible).isEqualTo(true) // Finish transition to communal - transitionState.value = - ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal) + transitionState.value = ObservableTransitionState.Idle(CommunalScenes.Communal) // isCommunalVisible is true since we're on communal. assertThat(isCommunalVisible).isEqualTo(true) // Start transition away from communal. transitionState.value = - ObservableCommunalTransitionState.Transition( - fromScene = CommunalSceneKey.Communal, - toScene = CommunalSceneKey.Blank, + ObservableTransitionState.Transition( + fromScene = CommunalScenes.Communal, + toScene = CommunalScenes.Blank, progress = flowOf(1.0f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt index 8b785927ba5e..50b8da62b3f0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt @@ -25,7 +25,7 @@ import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository -import com.android.systemui.communal.shared.model.CommunalSceneKey +import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic @@ -158,7 +158,7 @@ class CommunalTutorialInteractorTest : SysuiTestCase() { kosmos.setCommunalAvailable(true) communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED) - communalInteractor.onSceneChanged(CommunalSceneKey.Blank) + communalInteractor.onSceneChanged(CommunalScenes.Blank) assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_NOT_STARTED) } @@ -171,7 +171,7 @@ class CommunalTutorialInteractorTest : SysuiTestCase() { goToCommunal() communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED) - communalInteractor.onSceneChanged(CommunalSceneKey.Blank) + communalInteractor.onSceneChanged(CommunalScenes.Blank) assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED) } @@ -184,13 +184,13 @@ class CommunalTutorialInteractorTest : SysuiTestCase() { goToCommunal() communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) - communalInteractor.onSceneChanged(CommunalSceneKey.Blank) + communalInteractor.onSceneChanged(CommunalScenes.Blank) assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED) } private suspend fun goToCommunal() { kosmos.setCommunalAvailable(true) - communalInteractor.onSceneChanged(CommunalSceneKey.Communal) + communalInteractor.onSceneChanged(CommunalScenes.Communal) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt index 6b1b93777fbc..a51315bd96b8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt @@ -18,13 +18,14 @@ package com.android.systemui.communal.log import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.shared.log.CommunalUiEvent -import com.android.systemui.communal.shared.model.CommunalSceneKey -import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState +import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -73,7 +74,7 @@ class CommunalLoggerStartableTest : SysuiTestCase() { testScope.runTest { // Transition state is default (non-communal) val transitionState = - MutableStateFlow<ObservableCommunalTransitionState>(idle(CommunalSceneKey.DEFAULT)) + MutableStateFlow<ObservableTransitionState>(idle(CommunalScenes.Default)) communalInteractor.setTransitionState(transitionState) runCurrent() @@ -81,14 +82,14 @@ class CommunalLoggerStartableTest : SysuiTestCase() { verify(uiEventLogger, never()).log(any()) // Start transition to communal - transitionState.value = transition(to = CommunalSceneKey.Communal) + transitionState.value = transition(to = CommunalScenes.Communal) runCurrent() // Verify UiEvent logged verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_START) // Finish transition to communal - transitionState.value = idle(CommunalSceneKey.Communal) + transitionState.value = idle(CommunalScenes.Communal) runCurrent() // Verify UiEvent logged @@ -101,7 +102,7 @@ class CommunalLoggerStartableTest : SysuiTestCase() { testScope.runTest { // Transition state is default (non-communal) val transitionState = - MutableStateFlow<ObservableCommunalTransitionState>(idle(CommunalSceneKey.DEFAULT)) + MutableStateFlow<ObservableTransitionState>(idle(CommunalScenes.Default)) communalInteractor.setTransitionState(transitionState) runCurrent() @@ -109,14 +110,14 @@ class CommunalLoggerStartableTest : SysuiTestCase() { verify(uiEventLogger, never()).log(any()) // Start transition to communal - transitionState.value = transition(to = CommunalSceneKey.Communal) + transitionState.value = transition(to = CommunalScenes.Communal) runCurrent() // Verify UiEvent logged verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_START) // Cancel the transition - transitionState.value = idle(CommunalSceneKey.DEFAULT) + transitionState.value = idle(CommunalScenes.Default) runCurrent() // Verify UiEvent logged @@ -132,7 +133,7 @@ class CommunalLoggerStartableTest : SysuiTestCase() { testScope.runTest { // Transition state is communal val transitionState = - MutableStateFlow<ObservableCommunalTransitionState>(idle(CommunalSceneKey.Communal)) + MutableStateFlow<ObservableTransitionState>(idle(CommunalScenes.Communal)) communalInteractor.setTransitionState(transitionState) runCurrent() @@ -140,14 +141,14 @@ class CommunalLoggerStartableTest : SysuiTestCase() { verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SHOWN) // Start transition from communal - transitionState.value = transition(from = CommunalSceneKey.Communal) + transitionState.value = transition(from = CommunalScenes.Communal) runCurrent() // Verify UiEvent logged verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_START) // Finish transition to communal - transitionState.value = idle(CommunalSceneKey.DEFAULT) + transitionState.value = idle(CommunalScenes.Default) runCurrent() // Verify UiEvent logged @@ -160,7 +161,7 @@ class CommunalLoggerStartableTest : SysuiTestCase() { testScope.runTest { // Transition state is communal val transitionState = - MutableStateFlow<ObservableCommunalTransitionState>(idle(CommunalSceneKey.Communal)) + MutableStateFlow<ObservableTransitionState>(idle(CommunalScenes.Communal)) communalInteractor.setTransitionState(transitionState) runCurrent() @@ -168,14 +169,14 @@ class CommunalLoggerStartableTest : SysuiTestCase() { clearInvocations(uiEventLogger) // Start transition from communal - transitionState.value = transition(from = CommunalSceneKey.Communal) + transitionState.value = transition(from = CommunalScenes.Communal) runCurrent() // Verify UiEvent logged verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_START) // Cancel the transition - transitionState.value = idle(CommunalSceneKey.Communal) + transitionState.value = idle(CommunalScenes.Communal) runCurrent() // Verify UiEvent logged @@ -187,10 +188,10 @@ class CommunalLoggerStartableTest : SysuiTestCase() { } private fun transition( - from: CommunalSceneKey = CommunalSceneKey.DEFAULT, - to: CommunalSceneKey = CommunalSceneKey.DEFAULT, - ): ObservableCommunalTransitionState.Transition { - return ObservableCommunalTransitionState.Transition( + from: SceneKey = CommunalScenes.Default, + to: SceneKey = CommunalScenes.Default, + ): ObservableTransitionState.Transition { + return ObservableTransitionState.Transition( fromScene = from, toScene = to, progress = emptyFlow(), @@ -199,7 +200,7 @@ class CommunalLoggerStartableTest : SysuiTestCase() { ) } - private fun idle(sceneKey: CommunalSceneKey): ObservableCommunalTransitionState.Idle { - return ObservableCommunalTransitionState.Idle(sceneKey) + private fun idle(sceneKey: SceneKey): ObservableTransitionState.Idle { + return ObservableTransitionState.Idle(sceneKey) } } 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 563aad1920f7..8f802b80781f 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 @@ -39,6 +39,7 @@ import com.android.systemui.communal.shared.model.CommunalWidgetContentModel import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository @@ -113,6 +114,7 @@ class CommunalViewModelTest : SysuiTestCase() { kosmos.communalInteractor, kosmos.communalTutorialInteractor, kosmos.shadeInteractor, + kosmos.deviceEntryInteractor, mediaHost, logcatLogBuffer("CommunalViewModelTest"), ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt index 98719dd32e5a..4f44705b7e72 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.deviceentry.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.SceneKey import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository @@ -31,7 +32,7 @@ import com.android.systemui.keyguard.data.repository.fakeTrustRepository import com.android.systemui.kosmos.testScope import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -120,7 +121,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() { testScope.runTest { val isDeviceEntered by collectLastValue(underTest.isDeviceEntered) setupSwipeDeviceEntryMethod() - switchToScene(SceneKey.Lockscreen) + switchToScene(Scenes.Lockscreen) assertThat(isDeviceEntered).isFalse() } @@ -130,9 +131,9 @@ class DeviceEntryInteractorTest : SysuiTestCase() { testScope.runTest { val isDeviceEntered by collectLastValue(underTest.isDeviceEntered) setupSwipeDeviceEntryMethod() - switchToScene(SceneKey.Lockscreen) + switchToScene(Scenes.Lockscreen) runCurrent() - switchToScene(SceneKey.Shade) + switchToScene(Scenes.Shade) assertThat(isDeviceEntered).isFalse() } @@ -142,9 +143,9 @@ class DeviceEntryInteractorTest : SysuiTestCase() { testScope.runTest { val isDeviceEntered by collectLastValue(underTest.isDeviceEntered) setupSwipeDeviceEntryMethod() - switchToScene(SceneKey.Lockscreen) + switchToScene(Scenes.Lockscreen) runCurrent() - switchToScene(SceneKey.Gone) + switchToScene(Scenes.Gone) assertThat(isDeviceEntered).isTrue() } @@ -154,11 +155,11 @@ class DeviceEntryInteractorTest : SysuiTestCase() { testScope.runTest { val isDeviceEntered by collectLastValue(underTest.isDeviceEntered) setupSwipeDeviceEntryMethod() - switchToScene(SceneKey.Lockscreen) + switchToScene(Scenes.Lockscreen) runCurrent() - switchToScene(SceneKey.Gone) + switchToScene(Scenes.Gone) runCurrent() - switchToScene(SceneKey.Shade) + switchToScene(Scenes.Shade) assertThat(isDeviceEntered).isTrue() } @@ -170,9 +171,9 @@ class DeviceEntryInteractorTest : SysuiTestCase() { AuthenticationMethodModel.Pattern ) kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) - switchToScene(SceneKey.Lockscreen) + switchToScene(Scenes.Lockscreen) runCurrent() - switchToScene(SceneKey.Bouncer) + switchToScene(Scenes.Bouncer) val isDeviceEntered by collectLastValue(underTest.isDeviceEntered) assertThat(isDeviceEntered).isFalse() @@ -182,7 +183,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() { fun canSwipeToEnter_onLockscreenWithSwipe_isTrue() = testScope.runTest { setupSwipeDeviceEntryMethod() - switchToScene(SceneKey.Lockscreen) + switchToScene(Scenes.Lockscreen) val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter) assertThat(canSwipeToEnter).isTrue() @@ -195,7 +196,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() { AuthenticationMethodModel.Pin ) kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) - switchToScene(SceneKey.Lockscreen) + switchToScene(Scenes.Lockscreen) val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter) assertThat(canSwipeToEnter).isFalse() @@ -205,9 +206,9 @@ class DeviceEntryInteractorTest : SysuiTestCase() { fun canSwipeToEnter_afterLockscreenDismissedInSwipeMode_isFalse() = testScope.runTest { setupSwipeDeviceEntryMethod() - switchToScene(SceneKey.Lockscreen) + switchToScene(Scenes.Lockscreen) runCurrent() - switchToScene(SceneKey.Gone) + switchToScene(Scenes.Gone) val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter) assertThat(canSwipeToEnter).isFalse() @@ -225,7 +226,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() { kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) - switchToScene(SceneKey.Lockscreen) + switchToScene(Scenes.Lockscreen) assertThat(canSwipeToEnter).isFalse() trustRepository.setCurrentUserTrusted(true) @@ -242,7 +243,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() { kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) - switchToScene(SceneKey.Lockscreen) + switchToScene(Scenes.Lockscreen) assertThat(canSwipeToEnter).isFalse() faceAuthRepository.isAuthenticated.value = true @@ -311,8 +312,8 @@ class DeviceEntryInteractorTest : SysuiTestCase() { fun showOrUnlockDevice_notLocked_switchesToGoneScene() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene) - switchToScene(SceneKey.Lockscreen) - assertThat(currentScene).isEqualTo(SceneKey.Lockscreen) + switchToScene(Scenes.Lockscreen) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pin @@ -322,15 +323,15 @@ class DeviceEntryInteractorTest : SysuiTestCase() { underTest.attemptDeviceEntry() - assertThat(currentScene).isEqualTo(SceneKey.Gone) + assertThat(currentScene).isEqualTo(Scenes.Gone) } @Test fun showOrUnlockDevice_authMethodNotSecure_switchesToGoneScene() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene) - switchToScene(SceneKey.Lockscreen) - assertThat(currentScene).isEqualTo(SceneKey.Lockscreen) + switchToScene(Scenes.Lockscreen) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.None @@ -339,15 +340,15 @@ class DeviceEntryInteractorTest : SysuiTestCase() { underTest.attemptDeviceEntry() - assertThat(currentScene).isEqualTo(SceneKey.Gone) + assertThat(currentScene).isEqualTo(Scenes.Gone) } @Test fun showOrUnlockDevice_authMethodSwipe_switchesToGoneScene() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene) - switchToScene(SceneKey.Lockscreen) - assertThat(currentScene).isEqualTo(SceneKey.Lockscreen) + switchToScene(Scenes.Lockscreen) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) kosmos.fakeAuthenticationRepository.setAuthenticationMethod( @@ -357,7 +358,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() { underTest.attemptDeviceEntry() - assertThat(currentScene).isEqualTo(SceneKey.Gone) + assertThat(currentScene).isEqualTo(Scenes.Gone) } @Test 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 19b80da62dc7..128b46533a8b 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 @@ -25,6 +25,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 import com.android.systemui.doze.DozeTransitionCallback @@ -151,6 +152,24 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { } @Test + fun clockPosition() = + testScope.runTest { + assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 0)) + + underTest.setClockPosition(0, 1) + assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 1)) + + underTest.setClockPosition(1, 9) + assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 9)) + + underTest.setClockPosition(1, 0) + assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 0)) + + underTest.setClockPosition(3, 1) + assertThat(underTest.clockPosition.value).isEqualTo(Position(3, 1)) + } + + @Test fun dozeTimeTick() = testScope.runTest { val lastDozeTimeTick by collectLastValue(underTest.dozeTimeTick) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt index ef2b6f0805d6..f9ec3d161bb0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt @@ -20,6 +20,7 @@ package com.android.systemui.keyguard.domain.interactor import android.app.StatusBarManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository @@ -35,8 +36,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.PowerInteractorFactory import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags -import com.android.systemui.scene.shared.model.ObservableTransitionState -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat @@ -64,7 +64,7 @@ class KeyguardInteractorTest : SysuiTestCase() { private val shadeRepository = FakeShadeRepository() private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository private val transitionState: MutableStateFlow<ObservableTransitionState> = - MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Gone)) + MutableStateFlow(ObservableTransitionState.Idle(Scenes.Gone)) private val underTest by lazy { KeyguardInteractor( @@ -250,8 +250,8 @@ class KeyguardInteractorTest : SysuiTestCase() { underTest.setAnimateDozingTransitions(true) transitionState.value = ObservableTransitionState.Transition( - fromScene = SceneKey.Gone, - toScene = SceneKey.Lockscreen, + fromScene = Scenes.Gone, + toScene = Scenes.Lockscreen, progress = flowOf(0f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt index 225b5b1408e3..b0f59fe68f11 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt @@ -71,7 +71,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) MockitoAnnotations.initMocks(this) - whenever(burnInInteractor.burnIn(anyInt(), anyInt())).thenReturn(burnInFlow) + whenever(burnInInteractor.keyguardBurnIn).thenReturn(burnInFlow) kosmos.burnInInteractor = burnInInteractor whenever(goneToAodTransitionViewModel.enterFromTopTranslationY(anyInt())) .thenReturn(emptyFlow()) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt index 4c972e9195e9..a3371d3d24f4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt @@ -42,6 +42,25 @@ class AodToGoneTransitionViewModelTest : SysuiTestCase() { val underTest = kosmos.aodToGoneTransitionViewModel @Test + fun lockscreenAlpha() = + testScope.runTest { + val viewState = ViewStateAccessor(alpha = { 0.5f }) + val alpha by collectValues(underTest.lockscreenAlpha(viewState)) + + repository.sendTransitionSteps( + from = KeyguardState.AOD, + to = KeyguardState.GONE, + testScope + ) + + assertThat(alpha[0]).isEqualTo(0.5f) + // Fades out just prior to halfway + assertThat(alpha[1]).isEqualTo(0f) + // Must finish at 0 + assertThat(alpha[2]).isEqualTo(0f) + } + + @Test fun deviceEntryParentViewHides() = testScope.runTest { val deviceEntryParentViewAlpha by collectValues(underTest.deviceEntryParentViewAlpha) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModelTest.kt index 7e937db842ff..79671b885c56 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModelTest.kt @@ -51,6 +51,25 @@ class DozingToGoneTransitionViewModelTest : SysuiTestCase() { } @Test + fun lockscreenAlpha() = + testScope.runTest { + val viewState = ViewStateAccessor(alpha = { 0.6f }) + val alpha by collectValues(underTest.lockscreenAlpha(viewState)) + + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.DOZING, + to = KeyguardState.GONE, + testScope + ) + + assertThat(alpha[0]).isEqualTo(0.6f) + // Fades out just prior to halfway + assertThat(alpha[1]).isEqualTo(0f) + // Must finish at 0 + assertThat(alpha[2]).isEqualTo(0f) + } + + @Test fun deviceEntryParentViewDisappear() = testScope.runTest { val values by collectValues(underTest.deviceEntryParentViewAlpha) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt index 503fd34ce2c2..979d50463a04 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt @@ -22,12 +22,12 @@ package com.android.systemui.keyguard.ui.viewmodel import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.Flags as AConfigFlags import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.communalRepository -import com.android.systemui.communal.shared.model.CommunalSceneKey -import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState +import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.flags.Flags @@ -262,7 +262,7 @@ class KeyguardRootViewModelTest : SysuiTestCase() { // Hub transition state is idle with hub open. communalRepository.setTransitionState( - flowOf(ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)) + flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal)) ) runCurrent() @@ -328,4 +328,42 @@ class KeyguardRootViewModelTest : SysuiTestCase() { shadeRepository.setQsExpansion(0.5f) assertThat(alpha).isEqualTo(0f) } + + @Test + fun alpha_idleOnOccluded_isZero() = + testScope.runTest { + val alpha by collectLastValue(underTest.alpha(viewState)) + assertThat(alpha).isEqualTo(1f) + + // Go to OCCLUDED state + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.OCCLUDED, + testScope = testScope, + ) + assertThat(alpha).isEqualTo(0f) + + // Try pulling down shade and ensure the value doesn't change + shadeRepository.setQsExpansion(0.5f) + assertThat(alpha).isEqualTo(0f) + } + + @Test + fun alpha_idleOnGone_isZero() = + testScope.runTest { + val alpha by collectLastValue(underTest.alpha(viewState)) + assertThat(alpha).isEqualTo(1f) + + // Go to GONE state + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + testScope = testScope, + ) + assertThat(alpha).isEqualTo(0f) + + // Try pulling down shade and ensure the value doesn't change + shadeRepository.setQsExpansion(0.5f) + assertThat(alpha).isEqualTo(0f) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt index 3104842a9c2a..9ff76be30f79 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt @@ -32,7 +32,7 @@ import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepositor import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.kosmos.testScope import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock @@ -62,9 +62,9 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { ) kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) kosmos.fakeDeviceEntryRepository.setUnlocked(true) - sceneInteractor.changeScene(SceneKey.Lockscreen, "reason") + sceneInteractor.changeScene(Scenes.Lockscreen, "reason") - assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone) + assertThat(upTransitionSceneKey).isEqualTo(Scenes.Gone) } @Test @@ -75,9 +75,9 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { AuthenticationMethodModel.Pin ) kosmos.fakeDeviceEntryRepository.setUnlocked(false) - sceneInteractor.changeScene(SceneKey.Lockscreen, "reason") + sceneInteractor.changeScene(Scenes.Lockscreen, "reason") - assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer) + assertThat(upTransitionSceneKey).isEqualTo(Scenes.Bouncer) } @EnableFlags(FLAG_COMMUNAL_HUB) @@ -89,7 +89,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { kosmos.setCommunalAvailable(true) runCurrent() - assertThat(leftDestinationSceneKey).isEqualTo(SceneKey.Communal) + assertThat(leftDestinationSceneKey).isEqualTo(Scenes.Communal) } private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractorTest.kt new file mode 100644 index 000000000000..266875e9e12a --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractorTest.kt @@ -0,0 +1,227 @@ +/* + * 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.qs.tiles.impl.rotation.domain.interactor + +import android.Manifest +import android.content.packageManager +import android.content.pm.PackageManager +import android.os.UserHandle +import android.platform.test.annotations.EnabledOnRavenwood +import android.testing.LeakCheck +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.camera.data.repository.fakeCameraAutoRotateRepository +import com.android.systemui.camera.data.repository.fakeCameraSensorPrivacyRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.whenever +import com.android.systemui.utils.leaks.FakeBatteryController +import com.android.systemui.utils.leaks.FakeRotationLockController +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.toCollection +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@EnabledOnRavenwood +@RunWith(AndroidJUnit4::class) +class RotationLockTileDataInteractorTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val testScope = kosmos.testScope + private val batteryController = FakeBatteryController(LeakCheck()) + private val rotationController = FakeRotationLockController(LeakCheck()) + private val fakeCameraAutoRotateRepository = kosmos.fakeCameraAutoRotateRepository + private val fakeCameraSensorPrivacyRepository = kosmos.fakeCameraSensorPrivacyRepository + private val packageManager = kosmos.packageManager + + private val testUser = UserHandle.of(1) + private lateinit var underTest: RotationLockTileDataInteractor + + @Before + fun setup() { + whenever(packageManager.rotationResolverPackageName).thenReturn(TEST_PACKAGE_NAME) + whenever( + packageManager.checkPermission( + eq(Manifest.permission.CAMERA), + eq(TEST_PACKAGE_NAME) + ) + ) + .thenReturn(PackageManager.PERMISSION_GRANTED) + + underTest = + RotationLockTileDataInteractor( + rotationController, + batteryController, + fakeCameraAutoRotateRepository, + fakeCameraSensorPrivacyRepository, + packageManager, + context.orCreateTestableResources + .apply { + addOverride(com.android.internal.R.bool.config_allowRotationResolver, true) + } + .resources + ) + } + + @Test + fun availability_isTrue() = + testScope.runTest { + val availability = underTest.availability(testUser).toCollection(mutableListOf()) + + assertThat(availability).hasSize(1) + assertThat(availability.last()).isTrue() + } + + @Test + fun tileData_isRotationLockedMatchesRotationController() = + testScope.runTest { + val data by + collectLastValue( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + + runCurrent() + assertThat(data!!.isRotationLocked).isEqualTo(false) + + rotationController.setRotationLocked(true, CALLER) + runCurrent() + assertThat(data!!.isRotationLocked).isEqualTo(true) + + rotationController.setRotationLocked(false, CALLER) + runCurrent() + assertThat(data!!.isRotationLocked).isEqualTo(false) + } + + @Test + fun tileData_cameraRotationMatchesBatteryController() = + testScope.runTest { + setupControllersToEnableCameraRotation() + val data by + collectLastValue( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + + runCurrent() + assertThat(data!!.isCameraRotationEnabled).isTrue() + + batteryController.setPowerSaveMode(true) + runCurrent() + assertThat(data!!.isCameraRotationEnabled).isFalse() + + batteryController.setPowerSaveMode(false) + runCurrent() + assertThat(data!!.isCameraRotationEnabled).isTrue() + } + + @Test + fun tileData_cameraRotationMatchesSensorPrivacyRepository() = + testScope.runTest { + setupControllersToEnableCameraRotation() + val lastValue by + this.collectLastValue( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + runCurrent() + assertThat(lastValue!!.isCameraRotationEnabled).isTrue() + + fakeCameraSensorPrivacyRepository.setEnabled(testUser, true) + runCurrent() + assertThat(lastValue!!.isCameraRotationEnabled).isFalse() + + fakeCameraSensorPrivacyRepository.setEnabled(testUser, false) + runCurrent() + assertThat(lastValue!!.isCameraRotationEnabled).isTrue() + } + + @Test + fun tileData_cameraRotationMatchesAutoRotateRepository() = + testScope.runTest { + setupControllersToEnableCameraRotation() + + val lastValue by + collectLastValue( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + runCurrent() + assertThat(lastValue!!.isCameraRotationEnabled).isTrue() + + fakeCameraAutoRotateRepository.setEnabled(testUser, false) + runCurrent() + assertThat(lastValue!!.isCameraRotationEnabled).isFalse() + + fakeCameraAutoRotateRepository.setEnabled(testUser, true) + runCurrent() + assertThat(lastValue!!.isCameraRotationEnabled).isTrue() + } + + @Test + fun tileData_matchesPackageManagerPermissionDenied() = + testScope.runTest { + whenever( + packageManager.checkPermission( + eq(Manifest.permission.CAMERA), + eq(TEST_PACKAGE_NAME) + ) + ) + .thenReturn(PackageManager.PERMISSION_DENIED) + + val lastValue by + collectLastValue( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + runCurrent() + assertThat(lastValue!!.isCameraRotationEnabled).isEqualTo(false) + } + + @Test + fun tileData_setConfigAllowRotationResolverToFalse_cameraRotationIsNotEnabled() = + testScope.runTest { + underTest.apply { + overrideResource(com.android.internal.R.bool.config_allowRotationResolver, false) + } + setupControllersToEnableCameraRotation() + val lastValue by + collectLastValue( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + runCurrent() + + assertThat(lastValue!!.isCameraRotationEnabled).isEqualTo(false) + } + + private fun setupControllersToEnableCameraRotation() { + rotationController.setRotationLocked(true, CALLER) + batteryController.setPowerSaveMode(false) + fakeCameraSensorPrivacyRepository.setEnabled(testUser, false) + fakeCameraAutoRotateRepository.setEnabled(testUser, true) + } + + private companion object { + private const val CALLER = "RotationLockTileDataInteractorTest" + private const val TEST_PACKAGE_NAME = "com.test" + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileUserActionInteractorTest.kt new file mode 100644 index 000000000000..1653ce369ea1 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileUserActionInteractorTest.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.systemui.qs.tiles.impl.rotation.domain.interactor + +import android.platform.test.annotations.EnabledOnRavenwood +import android.provider.Settings +import android.testing.LeakCheck +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject +import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx +import com.android.systemui.qs.tiles.impl.rotation.domain.model.RotationLockTileModel +import com.android.systemui.utils.leaks.FakeRotationLockController +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@EnabledOnRavenwood +@RunWith(AndroidJUnit4::class) +class RotationLockTileUserActionInteractorTest : SysuiTestCase() { + private val controller = FakeRotationLockController(LeakCheck()) + private val inputHandler = FakeQSTileIntentUserInputHandler() + + private val underTest = + RotationLockTileUserActionInteractor( + controller, + inputHandler, + ) + + @Test + fun handleClickWhenEnabled() = runTest { + val wasEnabled = true + controller.setRotationLocked(wasEnabled, null) + + underTest.handleInput(QSTileInputTestKtx.click(RotationLockTileModel(wasEnabled, false))) + + assertThat(controller.isRotationLocked).isEqualTo(!wasEnabled) + } + + @Test + fun handleClickWhenDisabled() = runTest { + val wasEnabled = false + controller.setRotationLocked(wasEnabled, null) + + underTest.handleInput(QSTileInputTestKtx.click(RotationLockTileModel(wasEnabled, false))) + + assertThat(controller.isRotationLocked).isEqualTo(!wasEnabled) + } + + @Test + fun handleLongClickWhenDisabled() = runTest { + val enabled = false + + underTest.handleInput( + QSTileInputTestKtx.longClick( + RotationLockTileModel( + enabled, + false, + ) + ) + ) + + QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput { + assertThat(it.intent.action).isEqualTo(Settings.ACTION_AUTO_ROTATE_SETTINGS) + } + } + + @Test + fun handleLongClickWhenEnabled() = runTest { + val enabled = true + + underTest.handleInput(QSTileInputTestKtx.longClick(RotationLockTileModel(enabled, false))) + + QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput { + assertThat(it.intent.action).isEqualTo(Settings.ACTION_AUTO_ROTATE_SETTINGS) + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt new file mode 100644 index 000000000000..60c69f427ef3 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt @@ -0,0 +1,190 @@ +/* + * 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.qs.tiles.impl.rotation.ui.mapper + +import android.graphics.drawable.TestStubDrawable +import android.widget.Switch +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject +import com.android.systemui.qs.tiles.impl.rotation.domain.model.RotationLockTileModel +import com.android.systemui.qs.tiles.impl.rotation.qsRotationLockTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import com.android.systemui.statusbar.policy.DevicePostureController +import com.android.systemui.statusbar.policy.devicePostureController +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class RotationLockTileMapperTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val rotationLockTileConfig = kosmos.qsRotationLockTileConfig + private val devicePostureController = kosmos.devicePostureController + + private lateinit var mapper: RotationLockTileMapper + + @Before + fun setup() { + whenever(devicePostureController.devicePosture) + .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED) + + mapper = + RotationLockTileMapper( + context.orCreateTestableResources + .apply { + addOverride(R.drawable.qs_auto_rotate_icon_off, TestStubDrawable()) + addOverride(R.drawable.qs_auto_rotate_icon_on, TestStubDrawable()) + addOverride(com.android.internal.R.bool.config_allowRotationResolver, true) + addOverride( + com.android.internal.R.array.config_foldedDeviceStates, + intArrayOf() // empty array <=> device is not foldable + ) + } + .resources, + context.theme, + devicePostureController + ) + } + + @Test + fun rotationNotLocked_cameraRotationDisabled() { + val inputModel = RotationLockTileModel(false, false) + + val outputState = mapper.map(rotationLockTileConfig, inputModel) + + val expectedState = + createRotationLockTileState( + QSTileState.ActivationState.ACTIVE, + EMPTY_SECONDARY_STRING, + R.drawable.qs_auto_rotate_icon_on + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun rotationNotLocked_cameraRotationEnabled() { + val inputModel = RotationLockTileModel(false, true) + + val outputState = mapper.map(rotationLockTileConfig, inputModel) + + val expectedState = + createRotationLockTileState( + QSTileState.ActivationState.ACTIVE, + context.getString(R.string.rotation_lock_camera_rotation_on), + R.drawable.qs_auto_rotate_icon_on + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun rotationLocked_cameraRotationNotEnabled() { + val inputModel = RotationLockTileModel(true, false) + + val outputState = mapper.map(rotationLockTileConfig, inputModel) + + val expectedState = + createRotationLockTileState( + QSTileState.ActivationState.INACTIVE, + EMPTY_SECONDARY_STRING, + R.drawable.qs_auto_rotate_icon_off + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun deviceFoldableAndClosed_secondaryLabelIsFoldableSpecific() { + setDeviceFoldable() + val inputModel = RotationLockTileModel(false, false) + + val outputState = mapper.map(rotationLockTileConfig, inputModel) + + val expectedSecondaryLabelEnding = + context.getString(R.string.quick_settings_rotation_posture_folded) + assertThat( + context.resources.getIntArray( + com.android.internal.R.array.config_foldedDeviceStates + ) + ) + .isNotEmpty() + val actualSecondaryLabel = outputState.secondaryLabel + assertThat(actualSecondaryLabel).isNotNull() + assertThat(actualSecondaryLabel!!.endsWith(expectedSecondaryLabelEnding)).isTrue() + } + + @Test + fun deviceFoldableAndNotClosed_secondaryLabelIsFoldableSpecific() { + setDeviceFoldable() + whenever(devicePostureController.devicePosture) + .thenReturn(DevicePostureController.DEVICE_POSTURE_OPENED) + val inputModel = RotationLockTileModel(false, false) + + val outputState = mapper.map(rotationLockTileConfig, inputModel) + + val expectedSecondaryLabelEnding = + context.getString(R.string.quick_settings_rotation_posture_unfolded) + assertThat( + context.orCreateTestableResources.resources.getIntArray( + com.android.internal.R.array.config_foldedDeviceStates + ) + ) + .isNotEmpty() + val actualSecondaryLabel = outputState.secondaryLabel + assertThat(actualSecondaryLabel).isNotNull() + assertThat(actualSecondaryLabel!!.endsWith(expectedSecondaryLabelEnding)).isTrue() + } + + private fun setDeviceFoldable() { + mapper.apply { + overrideResource( + com.android.internal.R.array.config_foldedDeviceStates, + intArrayOf(1, 2, 3) + ) + } + } + + private fun createRotationLockTileState( + activationState: QSTileState.ActivationState, + secondaryLabel: String, + iconRes: Int + ): QSTileState { + val label = context.getString(R.string.quick_settings_rotation_unlocked_label) + return QSTileState( + { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, + label, + activationState, + secondaryLabel, + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK), + context.getString(R.string.accessibility_quick_settings_rotation), + secondaryLabel, + QSTileState.SideViewIcon.None, + QSTileState.EnabledState.ENABLED, + Switch::class.qualifiedName + ) + } + + private companion object { + private const val EMPTY_SECONDARY_STRING = "" + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index 1eb9adb8c004..63f00c1356e8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt @@ -18,6 +18,10 @@ package com.android.systemui.qs.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.Back +import com.android.compose.animation.scene.Swipe +import com.android.compose.animation.scene.SwipeDirection +import com.android.compose.animation.scene.UserActionResult import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlagsClassic @@ -26,10 +30,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.qs.FooterActionsController import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter -import com.android.systemui.scene.shared.model.Direction -import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.scene.shared.model.UserAction -import com.android.systemui.scene.shared.model.UserActionResult +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.domain.interactor.privacyChipInteractor import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel @@ -121,8 +122,8 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { assertThat(destinations) .isEqualTo( mapOf( - UserAction.Back to UserActionResult(SceneKey.Shade), - UserAction.Swipe(Direction.UP) to UserActionResult(SceneKey.Shade), + Back to UserActionResult(Scenes.Shade), + Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Shade), ) ) } @@ -136,7 +137,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { assertThat(destinations) .isEqualTo( mapOf( - UserAction.Back to UserActionResult(SceneKey.QuickSettings), + Back to UserActionResult(Scenes.QuickSettings), ) ) } 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 667f516317be..a2c4f4e63c19 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -22,6 +22,8 @@ import android.telecom.TelecomManager import android.telephony.TelephonyManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey import com.android.internal.R import com.android.internal.util.EmergencyAffordanceManager import com.android.internal.util.emergencyAffordanceManager @@ -61,8 +63,7 @@ import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.domain.startable.SceneContainerStartable import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags -import com.android.systemui.scene.shared.model.ObservableTransitionState -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel import com.android.systemui.settings.FakeDisplayTracker @@ -287,19 +288,19 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { } @Test - fun startsInLockscreenScene() = testScope.runTest { assertCurrentScene(SceneKey.Lockscreen) } + fun startsInLockscreenScene() = testScope.runTest { assertCurrentScene(Scenes.Lockscreen) } @Test fun clickLockButtonAndEnterCorrectPin_unlocksDevice() = testScope.runTest { - emulateUserDrivenTransition(SceneKey.Bouncer) + emulateUserDrivenTransition(Scenes.Bouncer) fakeSceneDataSource.pause() enterPin() emulatePendingTransitionProgress( expectedVisible = false, ) - assertCurrentScene(SceneKey.Gone) + assertCurrentScene(Scenes.Gone) } @Test @@ -307,7 +308,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { testScope.runTest { val upDestinationSceneKey by collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey) - assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Bouncer) + assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer) emulateUserDrivenTransition( to = upDestinationSceneKey, ) @@ -317,7 +318,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { emulatePendingTransitionProgress( expectedVisible = false, ) - assertCurrentScene(SceneKey.Gone) + assertCurrentScene(Scenes.Gone) } @Test @@ -327,7 +328,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { val upDestinationSceneKey by collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey) - assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Gone) + assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone) emulateUserDrivenTransition( to = upDestinationSceneKey, ) @@ -338,13 +339,13 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { testScope.runTest { val upDestinationSceneKey by collectLastValue(shadeSceneViewModel.upDestinationSceneKey) setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true) - assertCurrentScene(SceneKey.Lockscreen) + assertCurrentScene(Scenes.Lockscreen) // Emulate a user swipe to the shade scene. - emulateUserDrivenTransition(to = SceneKey.Shade) - assertCurrentScene(SceneKey.Shade) + emulateUserDrivenTransition(to = Scenes.Shade) + assertCurrentScene(Scenes.Shade) - assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Lockscreen) + assertThat(upDestinationSceneKey).isEqualTo(Scenes.Lockscreen) emulateUserDrivenTransition( to = upDestinationSceneKey, ) @@ -356,17 +357,17 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { val upDestinationSceneKey by collectLastValue(shadeSceneViewModel.upDestinationSceneKey) setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true) assertThat(deviceEntryInteractor.canSwipeToEnter.value).isTrue() - assertCurrentScene(SceneKey.Lockscreen) + assertCurrentScene(Scenes.Lockscreen) // Emulate a user swipe to dismiss the lockscreen. - emulateUserDrivenTransition(to = SceneKey.Gone) - assertCurrentScene(SceneKey.Gone) + emulateUserDrivenTransition(to = Scenes.Gone) + assertCurrentScene(Scenes.Gone) // Emulate a user swipe to the shade scene. - emulateUserDrivenTransition(to = SceneKey.Shade) - assertCurrentScene(SceneKey.Shade) + emulateUserDrivenTransition(to = Scenes.Shade) + assertCurrentScene(Scenes.Shade) - assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Gone) + assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone) emulateUserDrivenTransition( to = upDestinationSceneKey, ) @@ -377,10 +378,10 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { testScope.runTest { setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = false) putDeviceToSleep(instantlyLockDevice = false) - assertCurrentScene(SceneKey.Lockscreen) + assertCurrentScene(Scenes.Lockscreen) wakeUpDevice() - assertCurrentScene(SceneKey.Gone) + assertCurrentScene(Scenes.Gone) } @Test @@ -388,45 +389,45 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { testScope.runTest { setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true) putDeviceToSleep(instantlyLockDevice = false) - assertCurrentScene(SceneKey.Lockscreen) + assertCurrentScene(Scenes.Lockscreen) wakeUpDevice() - assertCurrentScene(SceneKey.Lockscreen) + assertCurrentScene(Scenes.Lockscreen) } @Test fun deviceGoesToSleep_switchesToLockscreen() = testScope.runTest { unlockDevice() - assertCurrentScene(SceneKey.Gone) + assertCurrentScene(Scenes.Gone) putDeviceToSleep() - assertCurrentScene(SceneKey.Lockscreen) + assertCurrentScene(Scenes.Lockscreen) } @Test fun deviceGoesToSleep_wakeUp_unlock() = testScope.runTest { unlockDevice() - assertCurrentScene(SceneKey.Gone) + assertCurrentScene(Scenes.Gone) putDeviceToSleep() - assertCurrentScene(SceneKey.Lockscreen) + assertCurrentScene(Scenes.Lockscreen) wakeUpDevice() - assertCurrentScene(SceneKey.Lockscreen) + assertCurrentScene(Scenes.Lockscreen) unlockDevice() - assertCurrentScene(SceneKey.Gone) + assertCurrentScene(Scenes.Gone) } @Test fun deviceWakesUpWhileUnlocked_dismissesLockscreen() = testScope.runTest { unlockDevice() - assertCurrentScene(SceneKey.Gone) + assertCurrentScene(Scenes.Gone) putDeviceToSleep(instantlyLockDevice = false) - assertCurrentScene(SceneKey.Lockscreen) + assertCurrentScene(Scenes.Lockscreen) wakeUpDevice() - assertCurrentScene(SceneKey.Gone) + assertCurrentScene(Scenes.Gone) } @Test @@ -435,20 +436,20 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { unlockDevice() val upDestinationSceneKey by collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey) - assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Gone) + assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone) } @Test fun deviceGoesToSleep_withLockTimeout_staysOnLockscreen() = testScope.runTest { unlockDevice() - assertCurrentScene(SceneKey.Gone) + assertCurrentScene(Scenes.Gone) putDeviceToSleep(instantlyLockDevice = false) - assertCurrentScene(SceneKey.Lockscreen) + assertCurrentScene(Scenes.Lockscreen) // Pretend like the timeout elapsed and now lock the device. lockDevice() - assertCurrentScene(SceneKey.Lockscreen) + assertCurrentScene(Scenes.Lockscreen) } @Test @@ -457,7 +458,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { setAuthMethod(AuthenticationMethodModel.Password) val upDestinationSceneKey by collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey) - assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Bouncer) + assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer) emulateUserDrivenTransition( to = upDestinationSceneKey, ) @@ -466,7 +467,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { dismissIme() emulatePendingTransitionProgress() - assertCurrentScene(SceneKey.Lockscreen) + assertCurrentScene(Scenes.Lockscreen) } @Test @@ -475,7 +476,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { setAuthMethod(AuthenticationMethodModel.Password) val upDestinationSceneKey by collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey) - assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Bouncer) + assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer) emulateUserDrivenTransition(to = upDestinationSceneKey) val bouncerActionButton by collectLastValue(bouncerViewModel.actionButton) @@ -495,7 +496,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { startPhoneCall() val upDestinationSceneKey by collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey) - assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Bouncer) + assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer) emulateUserDrivenTransition(to = upDestinationSceneKey) val bouncerActionButton by collectLastValue(bouncerViewModel.actionButton) @@ -513,7 +514,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { testScope.runTest { setAuthMethod(AuthenticationMethodModel.None) introduceLockedSim() - assertCurrentScene(SceneKey.Bouncer) + assertCurrentScene(Scenes.Bouncer) } @Test @@ -523,7 +524,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { introduceLockedSim() emulatePendingTransitionProgress(expectedVisible = true) enterSimPin(authMethodAfterSimUnlock = AuthenticationMethodModel.None) - assertCurrentScene(SceneKey.Gone) + assertCurrentScene(Scenes.Gone) } @Test @@ -533,7 +534,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { introduceLockedSim() emulatePendingTransitionProgress(expectedVisible = true) enterSimPin(authMethodAfterSimUnlock = AuthenticationMethodModel.Pin) - assertCurrentScene(SceneKey.Lockscreen) + assertCurrentScene(Scenes.Lockscreen) } @Test @@ -657,7 +658,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { assertThat(sceneContainerViewModel.currentScene.value).isEqualTo(to) bouncerSceneJob = - if (to == SceneKey.Bouncer) { + if (to == Scenes.Bouncer) { testScope.backgroundScope.launch { bouncerViewModel.authMethodViewModel.collect { // Do nothing. Need this to turn this otherwise cold flow, hot. @@ -688,7 +689,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { sceneInteractor.changeScene(to, "reason") emulatePendingTransitionProgress( - expectedVisible = to != SceneKey.Gone, + expectedVisible = to != Scenes.Gone, ) } @@ -715,7 +716,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { .that(deviceEntryInteractor.isUnlocked.value) .isFalse() - emulateUserDrivenTransition(SceneKey.Bouncer) + emulateUserDrivenTransition(Scenes.Bouncer) fakeSceneDataSource.pause() enterPin() // This repository state is not changed by the AuthInteractor, it relies on @@ -729,7 +730,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { /** * Enters the correct PIN in the bouncer UI. * - * Asserts that the current scene is [SceneKey.Bouncer] and that the current bouncer UI is a PIN + * Asserts that the current scene is [Scenes.Bouncer] and that the current bouncer UI is a PIN * before proceeding. * * Does not assert that the device is locked or unlocked. @@ -737,7 +738,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { private fun TestScope.enterPin() { assertWithMessage("Cannot enter PIN when not on the Bouncer scene!") .that(getCurrentSceneInUi()) - .isEqualTo(SceneKey.Bouncer) + .isEqualTo(Scenes.Bouncer) val authMethodViewModel by collectLastValue(bouncerViewModel.authMethodViewModel) assertWithMessage("Cannot enter PIN when not using a PIN authentication method!") .that(authMethodViewModel) @@ -754,7 +755,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { /** * Enters the correct PIN in the sim bouncer UI. * - * Asserts that the current scene is [SceneKey.Bouncer] and that the current bouncer UI is a PIN + * Asserts that the current scene is [Scenes.Bouncer] and that the current bouncer UI is a PIN * before proceeding. * * Does not assert that the device is locked or unlocked. @@ -764,7 +765,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { ) { assertWithMessage("Cannot enter PIN when not on the Bouncer scene!") .that(getCurrentSceneInUi()) - .isEqualTo(SceneKey.Bouncer) + .isEqualTo(Scenes.Bouncer) val authMethodViewModel by collectLastValue(bouncerViewModel.authMethodViewModel) assertWithMessage("Cannot enter PIN when not using a PIN authentication method!") .that(authMethodViewModel) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt index 1da3bc1aeda8..3d6619272dbe 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt @@ -20,14 +20,14 @@ package com.android.systemui.scene.data.repository import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.scene.sceneContainerConfig import com.android.systemui.scene.sceneKeys import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags -import com.android.systemui.scene.shared.model.ObservableTransitionState -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -51,12 +51,12 @@ class SceneContainerRepositoryTest : SysuiTestCase() { assertThat(underTest.allSceneKeys()) .isEqualTo( listOf( - SceneKey.QuickSettings, - SceneKey.Shade, - SceneKey.Lockscreen, - SceneKey.Bouncer, - SceneKey.Gone, - SceneKey.Communal, + Scenes.QuickSettings, + Scenes.Shade, + Scenes.Lockscreen, + Scenes.Bouncer, + Scenes.Gone, + Scenes.Communal, ) ) } @@ -66,17 +66,17 @@ class SceneContainerRepositoryTest : SysuiTestCase() { testScope.runTest { val underTest = kosmos.sceneContainerRepository val currentScene by collectLastValue(underTest.currentScene) - assertThat(currentScene).isEqualTo(SceneKey.Lockscreen) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) - underTest.changeScene(SceneKey.Shade) - assertThat(currentScene).isEqualTo(SceneKey.Shade) + underTest.changeScene(Scenes.Shade) + assertThat(currentScene).isEqualTo(Scenes.Shade) } @Test(expected = IllegalStateException::class) fun changeScene_noSuchSceneInContainer_throws() { - kosmos.sceneKeys = listOf(SceneKey.QuickSettings, SceneKey.Lockscreen) + kosmos.sceneKeys = listOf(Scenes.QuickSettings, Scenes.Lockscreen) val underTest = kosmos.sceneContainerRepository - underTest.changeScene(SceneKey.Shade) + underTest.changeScene(Scenes.Shade) } @Test @@ -111,7 +111,7 @@ class SceneContainerRepositoryTest : SysuiTestCase() { val underTest = kosmos.sceneContainerRepository val transitionState = MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(SceneKey.Lockscreen) + ObservableTransitionState.Idle(Scenes.Lockscreen) ) underTest.setTransitionState(transitionState) val reflectedTransitionState by collectLastValue(underTest.transitionState) @@ -120,8 +120,8 @@ class SceneContainerRepositoryTest : SysuiTestCase() { val progress = MutableStateFlow(1f) transitionState.value = ObservableTransitionState.Transition( - fromScene = SceneKey.Lockscreen, - toScene = SceneKey.Shade, + fromScene = Scenes.Lockscreen, + toScene = Scenes.Shade, progress = progress, isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/PanelExpansionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/PanelExpansionInteractorTest.kt index 9b0adb172e8d..6b5997fc21c4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/PanelExpansionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/PanelExpansionInteractorTest.kt @@ -21,6 +21,8 @@ package com.android.systemui.scene.domain.interactor import android.platform.test.annotations.DisableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey import com.android.systemui.Flags.FLAG_SCENE_CONTAINER import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -28,8 +30,7 @@ import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepositor import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.kosmos.testScope -import com.android.systemui.scene.shared.model.ObservableTransitionState -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource import com.android.systemui.shade.data.repository.fakeShadeRepository import com.android.systemui.statusbar.notification.stack.ui.viewmodel.panelExpansionInteractor @@ -56,7 +57,7 @@ class PanelExpansionInteractorTest : SysuiTestCase() { private val sceneInteractor = kosmos.sceneInteractor private val transitionState = MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(SceneKey.Lockscreen) + ObservableTransitionState.Idle(Scenes.Lockscreen) ) private val fakeSceneDataSource = kosmos.fakeSceneDataSource private val fakeShadeRepository = kosmos.fakeShadeRepository @@ -76,19 +77,19 @@ class PanelExpansionInteractorTest : SysuiTestCase() { setUnlocked(false) val panelExpansion by collectLastValue(underTest.legacyPanelExpansion) - changeScene(SceneKey.Lockscreen) { assertThat(panelExpansion).isEqualTo(1f) } + changeScene(Scenes.Lockscreen) { assertThat(panelExpansion).isEqualTo(1f) } assertThat(panelExpansion).isEqualTo(1f) - changeScene(SceneKey.Bouncer) { assertThat(panelExpansion).isEqualTo(1f) } + changeScene(Scenes.Bouncer) { assertThat(panelExpansion).isEqualTo(1f) } assertThat(panelExpansion).isEqualTo(1f) - changeScene(SceneKey.Shade) { assertThat(panelExpansion).isEqualTo(1f) } + changeScene(Scenes.Shade) { assertThat(panelExpansion).isEqualTo(1f) } assertThat(panelExpansion).isEqualTo(1f) - changeScene(SceneKey.QuickSettings) { assertThat(panelExpansion).isEqualTo(1f) } + changeScene(Scenes.QuickSettings) { assertThat(panelExpansion).isEqualTo(1f) } assertThat(panelExpansion).isEqualTo(1f) - changeScene(SceneKey.Communal) { assertThat(panelExpansion).isEqualTo(1f) } + changeScene(Scenes.Communal) { assertThat(panelExpansion).isEqualTo(1f) } assertThat(panelExpansion).isEqualTo(1f) } @@ -100,21 +101,19 @@ class PanelExpansionInteractorTest : SysuiTestCase() { setUnlocked(true) val panelExpansion by collectLastValue(underTest.legacyPanelExpansion) - changeScene(SceneKey.Gone) { assertThat(panelExpansion).isEqualTo(0f) } + changeScene(Scenes.Gone) { assertThat(panelExpansion).isEqualTo(0f) } assertThat(panelExpansion).isEqualTo(0f) - changeScene(SceneKey.Shade) { progress -> - assertThat(panelExpansion).isEqualTo(progress) - } + changeScene(Scenes.Shade) { progress -> assertThat(panelExpansion).isEqualTo(progress) } assertThat(panelExpansion).isEqualTo(1f) - changeScene(SceneKey.QuickSettings) { + changeScene(Scenes.QuickSettings) { // Shade's already expanded, so moving to QS should also be 1f. assertThat(panelExpansion).isEqualTo(1f) } assertThat(panelExpansion).isEqualTo(1f) - changeScene(SceneKey.Communal) { assertThat(panelExpansion).isEqualTo(1f) } + changeScene(Scenes.Communal) { assertThat(panelExpansion).isEqualTo(1f) } assertThat(panelExpansion).isEqualTo(1f) } @@ -128,19 +127,19 @@ class PanelExpansionInteractorTest : SysuiTestCase() { setUnlocked(false) val panelExpansion by collectLastValue(underTest.legacyPanelExpansion) - changeScene(SceneKey.Lockscreen) + changeScene(Scenes.Lockscreen) assertThat(panelExpansion).isEqualTo(leet) - changeScene(SceneKey.Bouncer) + changeScene(Scenes.Bouncer) assertThat(panelExpansion).isEqualTo(leet) - changeScene(SceneKey.Shade) + changeScene(Scenes.Shade) assertThat(panelExpansion).isEqualTo(leet) - changeScene(SceneKey.QuickSettings) + changeScene(Scenes.QuickSettings) assertThat(panelExpansion).isEqualTo(leet) - changeScene(SceneKey.Communal) + changeScene(Scenes.Communal) assertThat(panelExpansion).isEqualTo(leet) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt index db94c39e1cb1..f645f1cc4369 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt @@ -20,6 +20,7 @@ package com.android.systemui.scene.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository @@ -28,8 +29,7 @@ import com.android.systemui.scene.data.repository.sceneContainerRepository import com.android.systemui.scene.sceneContainerConfig import com.android.systemui.scene.sceneKeys import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags -import com.android.systemui.scene.shared.model.ObservableTransitionState -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat @@ -67,23 +67,23 @@ class SceneInteractorTest : SysuiTestCase() { fun changeScene() = testScope.runTest { val currentScene by collectLastValue(underTest.currentScene) - assertThat(currentScene).isEqualTo(SceneKey.Lockscreen) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) - underTest.changeScene(SceneKey.Shade, "reason") - assertThat(currentScene).isEqualTo(SceneKey.Shade) + underTest.changeScene(Scenes.Shade, "reason") + assertThat(currentScene).isEqualTo(Scenes.Shade) } @Test fun changeScene_toGoneWhenUnl_doesNotThrow() = testScope.runTest { val currentScene by collectLastValue(underTest.currentScene) - assertThat(currentScene).isEqualTo(SceneKey.Lockscreen) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) kosmos.fakeDeviceEntryRepository.setUnlocked(true) runCurrent() - underTest.changeScene(SceneKey.Gone, "reason") - assertThat(currentScene).isEqualTo(SceneKey.Gone) + underTest.changeScene(Scenes.Gone, "reason") + assertThat(currentScene).isEqualTo(Scenes.Gone) } @Test(expected = IllegalStateException::class) @@ -91,18 +91,18 @@ class SceneInteractorTest : SysuiTestCase() { testScope.runTest { kosmos.fakeDeviceEntryRepository.setUnlocked(false) - underTest.changeScene(SceneKey.Gone, "reason") + underTest.changeScene(Scenes.Gone, "reason") } @Test fun sceneChanged_inDataSource() = testScope.runTest { val currentScene by collectLastValue(underTest.currentScene) - assertThat(currentScene).isEqualTo(SceneKey.Lockscreen) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) - fakeSceneDataSource.changeScene(SceneKey.Shade) + fakeSceneDataSource.changeScene(Scenes.Shade) - assertThat(currentScene).isEqualTo(SceneKey.Shade) + assertThat(currentScene).isEqualTo(Scenes.Shade) } @Test @@ -111,7 +111,7 @@ class SceneInteractorTest : SysuiTestCase() { val underTest = kosmos.sceneContainerRepository val transitionState = MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(SceneKey.Lockscreen) + ObservableTransitionState.Idle(Scenes.Lockscreen) ) underTest.setTransitionState(transitionState) val reflectedTransitionState by collectLastValue(underTest.transitionState) @@ -120,8 +120,8 @@ class SceneInteractorTest : SysuiTestCase() { val progress = MutableStateFlow(1f) transitionState.value = ObservableTransitionState.Transition( - fromScene = SceneKey.Lockscreen, - toScene = SceneKey.Shade, + fromScene = Scenes.Lockscreen, + toScene = Scenes.Shade, progress = progress, isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -153,27 +153,27 @@ class SceneInteractorTest : SysuiTestCase() { val transitionTo by collectLastValue(underTest.transitioningTo) assertThat(transitionTo).isNull() - underTest.changeScene(SceneKey.Shade, "reason") + underTest.changeScene(Scenes.Shade, "reason") assertThat(transitionTo).isNull() val progress = MutableStateFlow(0f) transitionState.value = ObservableTransitionState.Transition( fromScene = underTest.currentScene.value, - toScene = SceneKey.Shade, + toScene = Scenes.Shade, progress = progress, isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), ) - assertThat(transitionTo).isEqualTo(SceneKey.Shade) + assertThat(transitionTo).isEqualTo(Scenes.Shade) progress.value = 0.5f - assertThat(transitionTo).isEqualTo(SceneKey.Shade) + assertThat(transitionTo).isEqualTo(Scenes.Shade) progress.value = 1f - assertThat(transitionTo).isEqualTo(SceneKey.Shade) + assertThat(transitionTo).isEqualTo(Scenes.Shade) - transitionState.value = ObservableTransitionState.Idle(SceneKey.Shade) + transitionState.value = ObservableTransitionState.Idle(Scenes.Shade) assertThat(transitionTo).isNull() } @@ -182,7 +182,7 @@ class SceneInteractorTest : SysuiTestCase() { testScope.runTest { val transitionState = MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(SceneKey.Shade) + ObservableTransitionState.Idle(Scenes.Shade) ) val isTransitionUserInputOngoing by collectLastValue(underTest.isTransitionUserInputOngoing) @@ -197,8 +197,8 @@ class SceneInteractorTest : SysuiTestCase() { val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Transition( - fromScene = SceneKey.Shade, - toScene = SceneKey.Lockscreen, + fromScene = Scenes.Shade, + toScene = Scenes.Lockscreen, progress = flowOf(0.5f), isInitiatedByUserInput = true, isUserInputOngoing = flowOf(true), @@ -217,8 +217,8 @@ class SceneInteractorTest : SysuiTestCase() { val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Transition( - fromScene = SceneKey.Shade, - toScene = SceneKey.Lockscreen, + fromScene = Scenes.Shade, + toScene = Scenes.Lockscreen, progress = flowOf(0.5f), isInitiatedByUserInput = true, isUserInputOngoing = flowOf(true), @@ -232,8 +232,8 @@ class SceneInteractorTest : SysuiTestCase() { transitionState.value = ObservableTransitionState.Transition( - fromScene = SceneKey.Shade, - toScene = SceneKey.Lockscreen, + fromScene = Scenes.Shade, + toScene = Scenes.Lockscreen, progress = flowOf(0.6f), isInitiatedByUserInput = true, isUserInputOngoing = flowOf(false), @@ -248,8 +248,8 @@ class SceneInteractorTest : SysuiTestCase() { val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Transition( - fromScene = SceneKey.Shade, - toScene = SceneKey.Lockscreen, + fromScene = Scenes.Shade, + toScene = Scenes.Lockscreen, progress = flowOf(0.5f), isInitiatedByUserInput = true, isUserInputOngoing = flowOf(true), @@ -261,7 +261,7 @@ class SceneInteractorTest : SysuiTestCase() { assertThat(isTransitionUserInputOngoing).isTrue() - transitionState.value = ObservableTransitionState.Idle(scene = SceneKey.Lockscreen) + transitionState.value = ObservableTransitionState.Idle(scene = Scenes.Lockscreen) assertThat(isTransitionUserInputOngoing).isFalse() } 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 4e1623661a58..cc66f8b2f387 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 @@ -24,6 +24,8 @@ import android.platform.test.annotations.EnableFlags import android.view.Display import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey import com.android.systemui.Flags as AconfigFlags import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository @@ -46,8 +48,7 @@ import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.se import com.android.systemui.power.domain.interactor.PowerInteractorFactory import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags -import com.android.systemui.scene.shared.model.ObservableTransitionState -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository @@ -136,42 +137,42 @@ class SceneContainerStartableTest : SysuiTestCase() { val transitionStateFlow = prepareState( isDeviceUnlocked = true, - initialSceneKey = SceneKey.Gone, + initialSceneKey = Scenes.Gone, ) - assertThat(currentDesiredSceneKey).isEqualTo(SceneKey.Gone) + assertThat(currentDesiredSceneKey).isEqualTo(Scenes.Gone) assertThat(isVisible).isTrue() underTest.start() assertThat(isVisible).isFalse() fakeSceneDataSource.pause() - sceneInteractor.changeScene(SceneKey.Shade, "reason") + sceneInteractor.changeScene(Scenes.Shade, "reason") transitionStateFlow.value = ObservableTransitionState.Transition( - fromScene = SceneKey.Gone, - toScene = SceneKey.Shade, + fromScene = Scenes.Gone, + toScene = Scenes.Shade, progress = flowOf(0.5f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), ) assertThat(isVisible).isTrue() - fakeSceneDataSource.unpause(expectedScene = SceneKey.Shade) - transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Shade) + fakeSceneDataSource.unpause(expectedScene = Scenes.Shade) + transitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Shade) assertThat(isVisible).isTrue() fakeSceneDataSource.pause() - sceneInteractor.changeScene(SceneKey.Gone, "reason") + sceneInteractor.changeScene(Scenes.Gone, "reason") transitionStateFlow.value = ObservableTransitionState.Transition( - fromScene = SceneKey.Shade, - toScene = SceneKey.Gone, + fromScene = Scenes.Shade, + toScene = Scenes.Gone, progress = flowOf(0.5f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), ) assertThat(isVisible).isTrue() - fakeSceneDataSource.unpause(expectedScene = SceneKey.Gone) - transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone) + fakeSceneDataSource.unpause(expectedScene = Scenes.Gone) + transitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Gone) assertThat(isVisible).isFalse() kosmos.headsUpNotificationRepository.hasPinnedHeadsUp.value = true @@ -187,7 +188,7 @@ class SceneContainerStartableTest : SysuiTestCase() { val isVisible by collectLastValue(sceneInteractor.isVisible) prepareState( isDeviceUnlocked = true, - initialSceneKey = SceneKey.Lockscreen, + initialSceneKey = Scenes.Lockscreen, isDeviceProvisioned = false, isFrpActive = true, ) @@ -214,7 +215,7 @@ class SceneContainerStartableTest : SysuiTestCase() { underTest.start() runCurrent() - assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) } @Test @@ -223,14 +224,14 @@ class SceneContainerStartableTest : SysuiTestCase() { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) prepareState( isDeviceUnlocked = true, - initialSceneKey = SceneKey.Gone, + initialSceneKey = Scenes.Gone, ) - assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) + assertThat(currentSceneKey).isEqualTo(Scenes.Gone) underTest.start() kosmos.fakeDeviceEntryRepository.setUnlocked(false) - assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) } @Test @@ -239,14 +240,14 @@ class SceneContainerStartableTest : SysuiTestCase() { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) prepareState( isDeviceUnlocked = false, - initialSceneKey = SceneKey.Bouncer, + initialSceneKey = Scenes.Bouncer, ) - assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer) + assertThat(currentSceneKey).isEqualTo(Scenes.Bouncer) underTest.start() kosmos.fakeDeviceEntryRepository.setUnlocked(true) - assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) + assertThat(currentSceneKey).isEqualTo(Scenes.Gone) } @Test @@ -255,14 +256,14 @@ class SceneContainerStartableTest : SysuiTestCase() { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) prepareState( isBypassEnabled = true, - initialSceneKey = SceneKey.Lockscreen, + initialSceneKey = Scenes.Lockscreen, ) - assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) underTest.start() kosmos.fakeDeviceEntryRepository.setUnlocked(true) - assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) + assertThat(currentSceneKey).isEqualTo(Scenes.Gone) } @Test @@ -271,16 +272,16 @@ class SceneContainerStartableTest : SysuiTestCase() { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) prepareState( isBypassEnabled = false, - initialSceneKey = SceneKey.Lockscreen, + initialSceneKey = Scenes.Lockscreen, ) - assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) underTest.start() // Authenticate using a passive auth method like face auth while bypass is disabled. faceAuthRepository.isAuthenticated.value = true kosmos.fakeDeviceEntryRepository.setUnlocked(true) - assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) } @Test @@ -291,19 +292,19 @@ class SceneContainerStartableTest : SysuiTestCase() { prepareState( isBypassEnabled = true, authenticationMethod = AuthenticationMethodModel.Pin, - initialSceneKey = SceneKey.Lockscreen, + initialSceneKey = Scenes.Lockscreen, ) underTest.start() runCurrent() - sceneInteractor.changeScene(SceneKey.Shade, "switch to shade") - transitionStateFlowValue.value = ObservableTransitionState.Idle(SceneKey.Shade) - assertThat(currentSceneKey).isEqualTo(SceneKey.Shade) + sceneInteractor.changeScene(Scenes.Shade, "switch to shade") + transitionStateFlowValue.value = ObservableTransitionState.Idle(Scenes.Shade) + assertThat(currentSceneKey).isEqualTo(Scenes.Shade) kosmos.fakeDeviceEntryRepository.setUnlocked(true) runCurrent() - assertThat(currentSceneKey).isEqualTo(SceneKey.Shade) + assertThat(currentSceneKey).isEqualTo(Scenes.Shade) } @Test @@ -312,16 +313,16 @@ class SceneContainerStartableTest : SysuiTestCase() { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) prepareState( isBypassEnabled = false, - initialSceneKey = SceneKey.Bouncer, + initialSceneKey = Scenes.Bouncer, ) - assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer) + assertThat(currentSceneKey).isEqualTo(Scenes.Bouncer) underTest.start() // Authenticate using a passive auth method like face auth while bypass is disabled. faceAuthRepository.isAuthenticated.value = true kosmos.fakeDeviceEntryRepository.setUnlocked(true) - assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) + assertThat(currentSceneKey).isEqualTo(Scenes.Gone) } @Test @@ -330,13 +331,13 @@ class SceneContainerStartableTest : SysuiTestCase() { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) prepareState( isDeviceUnlocked = false, - initialSceneKey = SceneKey.Shade, + initialSceneKey = Scenes.Shade, ) - assertThat(currentSceneKey).isEqualTo(SceneKey.Shade) + assertThat(currentSceneKey).isEqualTo(Scenes.Shade) underTest.start() powerInteractor.setAsleepForTest() - assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) } @Test @@ -348,14 +349,14 @@ class SceneContainerStartableTest : SysuiTestCase() { clearInvocations(sysUiState) listOf( - SceneKey.Gone, - SceneKey.Lockscreen, - SceneKey.Bouncer, - SceneKey.Shade, - SceneKey.QuickSettings, + Scenes.Gone, + Scenes.Lockscreen, + Scenes.Bouncer, + Scenes.Shade, + Scenes.QuickSettings, ) .forEachIndexed { index, sceneKey -> - if (sceneKey == SceneKey.Gone) { + if (sceneKey == Scenes.Gone) { kosmos.fakeDeviceEntryRepository.setUnlocked(true) runCurrent() } @@ -379,15 +380,15 @@ class SceneContainerStartableTest : SysuiTestCase() { testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) prepareState( - initialSceneKey = SceneKey.Lockscreen, + initialSceneKey = Scenes.Lockscreen, authenticationMethod = AuthenticationMethodModel.None, isLockscreenEnabled = false, ) - assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) underTest.start() powerInteractor.setAwakeForTest() - assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) + assertThat(currentSceneKey).isEqualTo(Scenes.Gone) } @Test @@ -395,15 +396,15 @@ class SceneContainerStartableTest : SysuiTestCase() { testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) prepareState( - initialSceneKey = SceneKey.Lockscreen, + initialSceneKey = Scenes.Lockscreen, authenticationMethod = AuthenticationMethodModel.None, isLockscreenEnabled = true, ) - assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) underTest.start() powerInteractor.setAwakeForTest() - assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) } @Test @@ -411,14 +412,14 @@ class SceneContainerStartableTest : SysuiTestCase() { testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) prepareState( - initialSceneKey = SceneKey.Lockscreen, + initialSceneKey = Scenes.Lockscreen, authenticationMethod = AuthenticationMethodModel.Pin, ) - assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) underTest.start() powerInteractor.setAwakeForTest() - assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) } @Test @@ -426,12 +427,12 @@ class SceneContainerStartableTest : SysuiTestCase() { testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) prepareState( - initialSceneKey = SceneKey.Lockscreen, + initialSceneKey = Scenes.Lockscreen, authenticationMethod = AuthenticationMethodModel.Pin, isDeviceUnlocked = false, startsAwake = false ) - assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) underTest.start() kosmos.fakeDeviceEntryRepository.setUnlocked(true) @@ -439,14 +440,14 @@ class SceneContainerStartableTest : SysuiTestCase() { powerInteractor.setAwakeForTest() runCurrent() - assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) + assertThat(currentSceneKey).isEqualTo(Scenes.Gone) } @Test fun collectFalsingSignals_onSuccessfulUnlock() = testScope.runTest { prepareState( - initialSceneKey = SceneKey.Lockscreen, + initialSceneKey = Scenes.Lockscreen, authenticationMethod = AuthenticationMethodModel.Pin, isDeviceUnlocked = false, ) @@ -456,11 +457,11 @@ class SceneContainerStartableTest : SysuiTestCase() { // Move around scenes without unlocking. listOf( - SceneKey.Shade, - SceneKey.QuickSettings, - SceneKey.Shade, - SceneKey.Lockscreen, - SceneKey.Bouncer, + Scenes.Shade, + Scenes.QuickSettings, + Scenes.Shade, + Scenes.Lockscreen, + Scenes.Bouncer, ) .forEach { sceneKey -> sceneInteractor.changeScene(sceneKey, "reason") @@ -471,17 +472,17 @@ class SceneContainerStartableTest : SysuiTestCase() { // Changing to the Gone scene should report a successful unlock. kosmos.fakeDeviceEntryRepository.setUnlocked(true) runCurrent() - sceneInteractor.changeScene(SceneKey.Gone, "reason") + sceneInteractor.changeScene(Scenes.Gone, "reason") runCurrent() verify(falsingCollector).onSuccessfulUnlock() // Move around scenes without changing back to Lockscreen, shouldn't report another // unlock. listOf( - SceneKey.Shade, - SceneKey.QuickSettings, - SceneKey.Shade, - SceneKey.Gone, + Scenes.Shade, + Scenes.QuickSettings, + Scenes.Shade, + Scenes.Gone, ) .forEach { sceneKey -> sceneInteractor.changeScene(sceneKey, "reason") @@ -490,17 +491,17 @@ class SceneContainerStartableTest : SysuiTestCase() { } // Changing to the Lockscreen scene shouldn't report a successful unlock. - sceneInteractor.changeScene(SceneKey.Lockscreen, "reason") + sceneInteractor.changeScene(Scenes.Lockscreen, "reason") runCurrent() verify(falsingCollector, times(1)).onSuccessfulUnlock() // Move around scenes without unlocking. listOf( - SceneKey.Shade, - SceneKey.QuickSettings, - SceneKey.Shade, - SceneKey.Lockscreen, - SceneKey.Bouncer, + Scenes.Shade, + Scenes.QuickSettings, + Scenes.Shade, + Scenes.Lockscreen, + Scenes.Bouncer, ) .forEach { sceneKey -> sceneInteractor.changeScene(sceneKey, "reason") @@ -509,7 +510,7 @@ class SceneContainerStartableTest : SysuiTestCase() { } // Changing to the Gone scene should report a second successful unlock. - sceneInteractor.changeScene(SceneKey.Gone, "reason") + sceneInteractor.changeScene(Scenes.Gone, "reason") runCurrent() verify(falsingCollector, times(2)).onSuccessfulUnlock() } @@ -518,7 +519,7 @@ class SceneContainerStartableTest : SysuiTestCase() { fun collectFalsingSignals_setShowingAod() = testScope.runTest { prepareState( - initialSceneKey = SceneKey.Lockscreen, + initialSceneKey = Scenes.Lockscreen, authenticationMethod = AuthenticationMethodModel.Pin, isDeviceUnlocked = false, ) @@ -540,7 +541,7 @@ class SceneContainerStartableTest : SysuiTestCase() { testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) prepareState( - initialSceneKey = SceneKey.Lockscreen, + initialSceneKey = Scenes.Lockscreen, authenticationMethod = AuthenticationMethodModel.Password, isDeviceUnlocked = false, ) @@ -550,7 +551,7 @@ class SceneContainerStartableTest : SysuiTestCase() { bouncerInteractor.onImeHiddenByUser() runCurrent() - assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) } @Test @@ -559,7 +560,7 @@ class SceneContainerStartableTest : SysuiTestCase() { kosmos.fakeKeyguardRepository.setAodAvailable(false) runCurrent() prepareState( - initialSceneKey = SceneKey.Lockscreen, + initialSceneKey = Scenes.Lockscreen, authenticationMethod = AuthenticationMethodModel.Pin, isDeviceUnlocked = false, startsAwake = false, @@ -607,7 +608,7 @@ class SceneContainerStartableTest : SysuiTestCase() { kosmos.fakeKeyguardRepository.setAodAvailable(true) runCurrent() prepareState( - initialSceneKey = SceneKey.Lockscreen, + initialSceneKey = Scenes.Lockscreen, authenticationMethod = AuthenticationMethodModel.Pin, isDeviceUnlocked = false, ) @@ -652,7 +653,7 @@ class SceneContainerStartableTest : SysuiTestCase() { fun collectFalsingSignals_bouncerVisibility() = testScope.runTest { prepareState( - initialSceneKey = SceneKey.Lockscreen, + initialSceneKey = Scenes.Lockscreen, authenticationMethod = AuthenticationMethodModel.Pin, isDeviceUnlocked = false, ) @@ -660,13 +661,13 @@ class SceneContainerStartableTest : SysuiTestCase() { runCurrent() verify(falsingCollector).onBouncerHidden() - sceneInteractor.changeScene(SceneKey.Bouncer, "reason") + sceneInteractor.changeScene(Scenes.Bouncer, "reason") runCurrent() verify(falsingCollector).onBouncerShown() kosmos.fakeDeviceEntryRepository.setUnlocked(true) runCurrent() - sceneInteractor.changeScene(SceneKey.Gone, "reason") + sceneInteractor.changeScene(Scenes.Gone, "reason") runCurrent() verify(falsingCollector, times(2)).onBouncerHidden() } @@ -677,7 +678,7 @@ class SceneContainerStartableTest : SysuiTestCase() { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) prepareState( - initialSceneKey = SceneKey.Lockscreen, + initialSceneKey = Scenes.Lockscreen, authenticationMethod = AuthenticationMethodModel.Pin, isDeviceUnlocked = false, ) @@ -687,7 +688,7 @@ class SceneContainerStartableTest : SysuiTestCase() { kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = true runCurrent() - assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer) + assertThat(currentSceneKey).isEqualTo(Scenes.Bouncer) } @Test @@ -697,7 +698,7 @@ class SceneContainerStartableTest : SysuiTestCase() { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) prepareState( - initialSceneKey = SceneKey.Bouncer, + initialSceneKey = Scenes.Bouncer, authenticationMethod = AuthenticationMethodModel.Pin, isDeviceUnlocked = false, ) @@ -706,7 +707,7 @@ class SceneContainerStartableTest : SysuiTestCase() { kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = false runCurrent() - assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) } @Test @@ -716,7 +717,7 @@ class SceneContainerStartableTest : SysuiTestCase() { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) prepareState( - initialSceneKey = SceneKey.Lockscreen, + initialSceneKey = Scenes.Lockscreen, authenticationMethod = AuthenticationMethodModel.None, isDeviceUnlocked = true, isLockscreenEnabled = false, @@ -726,7 +727,7 @@ class SceneContainerStartableTest : SysuiTestCase() { kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = false runCurrent() - assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) + assertThat(currentSceneKey).isEqualTo(Scenes.Gone) } @Test @@ -736,9 +737,9 @@ class SceneContainerStartableTest : SysuiTestCase() { val transitionStateFlow = prepareState( isDeviceUnlocked = true, - initialSceneKey = SceneKey.Gone, + initialSceneKey = Scenes.Gone, ) - assertThat(currentDesiredSceneKey).isEqualTo(SceneKey.Gone) + assertThat(currentDesiredSceneKey).isEqualTo(Scenes.Gone) verify(windowController, never()).setNotificationShadeFocusable(anyBoolean()) underTest.start() @@ -746,11 +747,11 @@ class SceneContainerStartableTest : SysuiTestCase() { verify(windowController, times(1)).setNotificationShadeFocusable(false) fakeSceneDataSource.pause() - sceneInteractor.changeScene(SceneKey.Shade, "reason") + sceneInteractor.changeScene(Scenes.Shade, "reason") transitionStateFlow.value = ObservableTransitionState.Transition( - fromScene = SceneKey.Gone, - toScene = SceneKey.Shade, + fromScene = Scenes.Gone, + toScene = Scenes.Shade, progress = flowOf(0.5f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -758,17 +759,17 @@ class SceneContainerStartableTest : SysuiTestCase() { runCurrent() verify(windowController, times(1)).setNotificationShadeFocusable(false) - fakeSceneDataSource.unpause(expectedScene = SceneKey.Shade) - transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Shade) + fakeSceneDataSource.unpause(expectedScene = Scenes.Shade) + transitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Shade) runCurrent() verify(windowController, times(1)).setNotificationShadeFocusable(true) fakeSceneDataSource.pause() - sceneInteractor.changeScene(SceneKey.Gone, "reason") + sceneInteractor.changeScene(Scenes.Gone, "reason") transitionStateFlow.value = ObservableTransitionState.Transition( - fromScene = SceneKey.Shade, - toScene = SceneKey.Gone, + fromScene = Scenes.Shade, + toScene = Scenes.Gone, progress = flowOf(0.5f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -776,8 +777,8 @@ class SceneContainerStartableTest : SysuiTestCase() { runCurrent() verify(windowController, times(1)).setNotificationShadeFocusable(true) - fakeSceneDataSource.unpause(expectedScene = SceneKey.Gone) - transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone) + fakeSceneDataSource.unpause(expectedScene = Scenes.Gone) + transitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Gone) runCurrent() verify(windowController, times(2)).setNotificationShadeFocusable(false) } @@ -787,7 +788,7 @@ class SceneContainerStartableTest : SysuiTestCase() { testScope.runTest { val transitionStateFlow = prepareState( - initialSceneKey = SceneKey.Lockscreen, + initialSceneKey = Scenes.Lockscreen, ) underTest.start() runCurrent() @@ -796,7 +797,7 @@ class SceneContainerStartableTest : SysuiTestCase() { clearInvocations(centralSurfaces) emulateSceneTransition( transitionStateFlow = transitionStateFlow, - toScene = SceneKey.Bouncer, + toScene = Scenes.Bouncer, verifyBeforeTransition = { verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) }, @@ -815,7 +816,7 @@ class SceneContainerStartableTest : SysuiTestCase() { clearInvocations(centralSurfaces) emulateSceneTransition( transitionStateFlow = transitionStateFlow, - toScene = SceneKey.Lockscreen, + toScene = Scenes.Lockscreen, verifyBeforeTransition = { verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) }, @@ -834,7 +835,7 @@ class SceneContainerStartableTest : SysuiTestCase() { clearInvocations(centralSurfaces) emulateSceneTransition( transitionStateFlow = transitionStateFlow, - toScene = SceneKey.Shade, + toScene = Scenes.Shade, verifyBeforeTransition = { verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) }, @@ -853,7 +854,7 @@ class SceneContainerStartableTest : SysuiTestCase() { clearInvocations(centralSurfaces) emulateSceneTransition( transitionStateFlow = transitionStateFlow, - toScene = SceneKey.Lockscreen, + toScene = Scenes.Lockscreen, verifyBeforeTransition = { verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) }, @@ -872,7 +873,7 @@ class SceneContainerStartableTest : SysuiTestCase() { clearInvocations(centralSurfaces) emulateSceneTransition( transitionStateFlow = transitionStateFlow, - toScene = SceneKey.QuickSettings, + toScene = Scenes.QuickSettings, verifyBeforeTransition = { verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) }, @@ -891,7 +892,7 @@ class SceneContainerStartableTest : SysuiTestCase() { val transitionStateFlow = prepareState( isDeviceUnlocked = true, - initialSceneKey = SceneKey.Gone, + initialSceneKey = Scenes.Gone, ) underTest.start() verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) @@ -899,7 +900,7 @@ class SceneContainerStartableTest : SysuiTestCase() { clearInvocations(centralSurfaces) emulateSceneTransition( transitionStateFlow = transitionStateFlow, - toScene = SceneKey.Bouncer, + toScene = Scenes.Bouncer, verifyBeforeTransition = { verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) }, @@ -914,7 +915,7 @@ class SceneContainerStartableTest : SysuiTestCase() { clearInvocations(centralSurfaces) emulateSceneTransition( transitionStateFlow = transitionStateFlow, - toScene = SceneKey.Lockscreen, + toScene = Scenes.Lockscreen, verifyBeforeTransition = { verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) }, @@ -929,7 +930,7 @@ class SceneContainerStartableTest : SysuiTestCase() { clearInvocations(centralSurfaces) emulateSceneTransition( transitionStateFlow = transitionStateFlow, - toScene = SceneKey.Shade, + toScene = Scenes.Shade, verifyBeforeTransition = { verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) }, @@ -944,7 +945,7 @@ class SceneContainerStartableTest : SysuiTestCase() { clearInvocations(centralSurfaces) emulateSceneTransition( transitionStateFlow = transitionStateFlow, - toScene = SceneKey.Lockscreen, + toScene = Scenes.Lockscreen, verifyBeforeTransition = { verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) }, @@ -959,7 +960,7 @@ class SceneContainerStartableTest : SysuiTestCase() { clearInvocations(centralSurfaces) emulateSceneTransition( transitionStateFlow = transitionStateFlow, - toScene = SceneKey.QuickSettings, + toScene = Scenes.QuickSettings, verifyBeforeTransition = { verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean()) }, @@ -978,12 +979,12 @@ class SceneContainerStartableTest : SysuiTestCase() { val currentScene by collectLastValue(sceneInteractor.currentScene) val transitionStateFlow = prepareState() underTest.start() - emulateSceneTransition(transitionStateFlow, toScene = SceneKey.Bouncer) - assertThat(currentScene).isNotEqualTo(SceneKey.Lockscreen) + emulateSceneTransition(transitionStateFlow, toScene = Scenes.Bouncer) + assertThat(currentScene).isNotEqualTo(Scenes.Lockscreen) kosmos.falsingManager.sendFalsingBelief() - assertThat(currentScene).isEqualTo(SceneKey.Lockscreen) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) } private fun TestScope.emulateSceneTransition( @@ -1033,7 +1034,7 @@ class SceneContainerStartableTest : SysuiTestCase() { } } - check(initialSceneKey != SceneKey.Gone || isDeviceUnlocked) { + check(initialSceneKey != Scenes.Gone || isDeviceUnlocked) { "Cannot start on the Gone scene and have the device be locked at the same time." } @@ -1043,7 +1044,7 @@ class SceneContainerStartableTest : SysuiTestCase() { runCurrent() val transitionStateFlow = MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(SceneKey.Lockscreen) + ObservableTransitionState.Idle(Scenes.Lockscreen) ) sceneInteractor.setTransitionState(transitionStateFlow) initialSceneKey?.let { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegatorTest.kt index ed4b1e6a43c9..32c0172071f6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegatorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegatorTest.kt @@ -53,9 +53,9 @@ class SceneDataSourceDelegatorTest : SysuiTestCase() { testScope.runTest { val currentScene by collectLastValue(underTest.currentScene) underTest.setDelegate(null) - assertThat(currentScene).isNotEqualTo(SceneKey.Bouncer) + assertThat(currentScene).isNotEqualTo(Scenes.Bouncer) - underTest.changeScene(toScene = SceneKey.Bouncer) + underTest.changeScene(toScene = Scenes.Bouncer) assertThat(currentScene).isEqualTo(initialSceneKey) } @@ -71,11 +71,11 @@ class SceneDataSourceDelegatorTest : SysuiTestCase() { fun currentScene_withDelegate_changesScenes() = testScope.runTest { val currentScene by collectLastValue(underTest.currentScene) - assertThat(currentScene).isNotEqualTo(SceneKey.Bouncer) + assertThat(currentScene).isNotEqualTo(Scenes.Bouncer) - underTest.changeScene(toScene = SceneKey.Bouncer) + underTest.changeScene(toScene = Scenes.Bouncer) - assertThat(currentScene).isEqualTo(SceneKey.Bouncer) + assertThat(currentScene).isEqualTo(Scenes.Bouncer) } @Test @@ -83,8 +83,8 @@ class SceneDataSourceDelegatorTest : SysuiTestCase() { testScope.runTest { val currentScene by collectLastValue(underTest.currentScene) - fakeSceneDataSource.changeScene(toScene = SceneKey.Bouncer) + fakeSceneDataSource.changeScene(toScene = Scenes.Bouncer) - assertThat(currentScene).isEqualTo(SceneKey.Bouncer) + assertThat(currentScene).isEqualTo(Scenes.Bouncer) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt index 27ae8b60009c..7b0127e94fb7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt @@ -30,7 +30,7 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.sceneContainerConfig import com.android.systemui.scene.sceneKeys import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock @@ -89,20 +89,20 @@ class SceneContainerViewModelTest : SysuiTestCase() { fun sceneTransition() = testScope.runTest { val currentScene by collectLastValue(underTest.currentScene) - assertThat(currentScene).isEqualTo(SceneKey.Lockscreen) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) - fakeSceneDataSource.changeScene(SceneKey.Shade) + fakeSceneDataSource.changeScene(Scenes.Shade) - assertThat(currentScene).isEqualTo(SceneKey.Shade) + assertThat(currentScene).isEqualTo(Scenes.Shade) } @Test fun canChangeScene_whenAllowed_switchingFromGone_returnsTrue() = testScope.runTest { val currentScene by collectLastValue(underTest.currentScene) - fakeSceneDataSource.changeScene(toScene = SceneKey.Gone) + fakeSceneDataSource.changeScene(toScene = Scenes.Gone) runCurrent() - assertThat(currentScene).isEqualTo(SceneKey.Gone) + assertThat(currentScene).isEqualTo(Scenes.Gone) sceneContainerConfig.sceneKeys .filter { it != currentScene } @@ -117,9 +117,9 @@ class SceneContainerViewModelTest : SysuiTestCase() { fun canChangeScene_whenAllowed_switchingFromLockscreen_returnsTrue() = testScope.runTest { val currentScene by collectLastValue(underTest.currentScene) - fakeSceneDataSource.changeScene(toScene = SceneKey.Lockscreen) + fakeSceneDataSource.changeScene(toScene = Scenes.Lockscreen) runCurrent() - assertThat(currentScene).isEqualTo(SceneKey.Lockscreen) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) sceneContainerConfig.sceneKeys .filter { it != currentScene } @@ -135,15 +135,15 @@ class SceneContainerViewModelTest : SysuiTestCase() { testScope.runTest { falsingManager.setIsFalseTouch(true) val currentScene by collectLastValue(underTest.currentScene) - fakeSceneDataSource.changeScene(toScene = SceneKey.Lockscreen) + fakeSceneDataSource.changeScene(toScene = Scenes.Lockscreen) runCurrent() - assertThat(currentScene).isEqualTo(SceneKey.Lockscreen) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) sceneContainerConfig.sceneKeys .filter { it != currentScene } .filter { // Moving to the Communal scene is not currently falsing protected. - it != SceneKey.Communal + it != Scenes.Communal } .forEach { toScene -> assertWithMessage("Protected scene $toScene not properly protected") @@ -157,14 +157,14 @@ class SceneContainerViewModelTest : SysuiTestCase() { testScope.runTest { falsingManager.setIsFalseTouch(true) val currentScene by collectLastValue(underTest.currentScene) - fakeSceneDataSource.changeScene(toScene = SceneKey.Lockscreen) + fakeSceneDataSource.changeScene(toScene = Scenes.Lockscreen) runCurrent() - assertThat(currentScene).isEqualTo(SceneKey.Lockscreen) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) sceneContainerConfig.sceneKeys .filter { // Moving to the Communal scene is not currently falsing protected. - it == SceneKey.Communal + it == Scenes.Communal } .forEach { toScene -> assertWithMessage("Unprotected scene $toScene is incorrectly protected") @@ -178,9 +178,9 @@ class SceneContainerViewModelTest : SysuiTestCase() { testScope.runTest { falsingManager.setIsFalseTouch(true) val currentScene by collectLastValue(underTest.currentScene) - fakeSceneDataSource.changeScene(toScene = SceneKey.Gone) + fakeSceneDataSource.changeScene(toScene = Scenes.Gone) runCurrent() - assertThat(currentScene).isEqualTo(SceneKey.Gone) + assertThat(currentScene).isEqualTo(Scenes.Gone) sceneContainerConfig.sceneKeys .filter { it != currentScene } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt index ec424b05fc06..d3fa3603d722 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt @@ -18,6 +18,8 @@ package com.android.systemui.shade import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey import com.android.systemui.SysuiTestCase import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor @@ -28,8 +30,7 @@ import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testScope import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags -import com.android.systemui.scene.shared.model.ObservableTransitionState -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.CommandQueue @@ -87,7 +88,7 @@ class ShadeControllerSceneImplTest : SysuiTestCase() { runCurrent() // THEN the shade remains collapsed and the post-collapse action ran - assertThat(sceneInteractor.currentScene.value).isEqualTo(SceneKey.Gone) + assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Gone) verify(testRunnable, times(1)).run() } @@ -105,7 +106,7 @@ class ShadeControllerSceneImplTest : SysuiTestCase() { runCurrent() // THEN the shade remains expanded and the post-collapse action did not run - assertThat(sceneInteractor.currentScene.value).isEqualTo(SceneKey.Shade) + assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Shade) assertThat(shadeInteractor.isAnyFullyExpanded.value).isTrue() verify(testRunnable, never()).run() } @@ -122,7 +123,7 @@ class ShadeControllerSceneImplTest : SysuiTestCase() { runCurrent() // THEN the shade collapses back to lockscreen and the post-collapse action ran - assertThat(sceneInteractor.currentScene.value).isEqualTo(SceneKey.Lockscreen) + assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Lockscreen) } @Test @@ -137,7 +138,7 @@ class ShadeControllerSceneImplTest : SysuiTestCase() { runCurrent() // THEN the shade collapses back to lockscreen and the post-collapse action ran - assertThat(sceneInteractor.currentScene.value).isEqualTo(SceneKey.Gone) + assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Gone) } @Test @@ -181,21 +182,21 @@ class ShadeControllerSceneImplTest : SysuiTestCase() { private fun setDeviceEntered(isEntered: Boolean) { setScene( if (isEntered) { - SceneKey.Gone + Scenes.Gone } else { - SceneKey.Lockscreen + Scenes.Lockscreen } ) assertThat(deviceEntryInteractor.isDeviceEntered.value).isEqualTo(isEntered) } private fun setCollapsed() { - setScene(SceneKey.Gone) + setScene(Scenes.Gone) assertThat(shadeInteractor.isAnyExpanded.value).isFalse() } private fun setShadeFullyExpanded() { - setScene(SceneKey.Shade) + setScene(Scenes.Shade) assertThat(shadeInteractor.isAnyFullyExpanded.value).isTrue() } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImplTest.kt index 1ef07facf8d1..bb40591335f4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImplTest.kt @@ -18,12 +18,12 @@ package com.android.systemui.shade.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.shared.model.ObservableTransitionState -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.google.common.truth.Truth import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -53,8 +53,8 @@ class ShadeAnimationInteractorSceneContainerImplTest : SysuiTestCase() { val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Transition( - fromScene = SceneKey.QuickSettings, - toScene = SceneKey.Shade, + fromScene = Scenes.QuickSettings, + toScene = Scenes.Shade, progress = MutableStateFlow(.1f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -76,8 +76,8 @@ class ShadeAnimationInteractorSceneContainerImplTest : SysuiTestCase() { val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Transition( - fromScene = SceneKey.QuickSettings, - toScene = SceneKey.Gone, + fromScene = Scenes.QuickSettings, + toScene = Scenes.Gone, progress = MutableStateFlow(.1f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -99,8 +99,8 @@ class ShadeAnimationInteractorSceneContainerImplTest : SysuiTestCase() { val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Transition( - fromScene = SceneKey.QuickSettings, - toScene = SceneKey.Gone, + fromScene = Scenes.QuickSettings, + toScene = Scenes.Gone, progress = MutableStateFlow(.1f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(true), diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt index ec4da0405b6d..b66213330496 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt @@ -19,13 +19,14 @@ package com.android.systemui.shade.domain.interactor import android.content.applicationContext import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey import com.android.systemui.SysuiTestCase import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.kosmos.testScope import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags -import com.android.systemui.scene.shared.model.ObservableTransitionState -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shared.recents.utilities.Utilities import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat @@ -56,45 +57,45 @@ class ShadeBackActionInteractorImplTest : SysuiTestCase() { @Test fun animateCollapseQs_notOnQs() = testScope.runTest { - setScene(SceneKey.Shade) + setScene(Scenes.Shade) underTest.animateCollapseQs(true) runCurrent() - assertThat(sceneInteractor.currentScene.value).isEqualTo(SceneKey.Shade) + assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Shade) } @Test fun animateCollapseQs_fullyCollapse_entered() = testScope.runTest { enterDevice() - setScene(SceneKey.QuickSettings) + setScene(Scenes.QuickSettings) underTest.animateCollapseQs(true) runCurrent() - assertThat(sceneInteractor.currentScene.value).isEqualTo(SceneKey.Gone) + assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Gone) } @Test fun animateCollapseQs_fullyCollapse_locked() = testScope.runTest { deviceEntryRepository.setUnlocked(false) - setScene(SceneKey.QuickSettings) + setScene(Scenes.QuickSettings) underTest.animateCollapseQs(true) runCurrent() - assertThat(sceneInteractor.currentScene.value).isEqualTo(SceneKey.Lockscreen) + assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Lockscreen) } @Test fun animateCollapseQs_notFullyCollapse() = testScope.runTest { - setScene(SceneKey.QuickSettings) + setScene(Scenes.QuickSettings) underTest.animateCollapseQs(false) runCurrent() - assertThat(sceneInteractor.currentScene.value).isEqualTo(SceneKey.Shade) + assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Shade) } private fun enterDevice() { deviceEntryRepository.setUnlocked(true) testScope.runCurrent() - setScene(SceneKey.Gone) + setScene(Scenes.Gone) } private fun setScene(key: SceneKey) { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt index bf136cdd817e..4cd2c301d6ee 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.shade.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.SysuiTestCase import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue @@ -27,8 +28,7 @@ import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.kosmos.testScope import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.shared.model.ObservableTransitionState -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.userRepository import com.google.common.truth.Truth @@ -67,8 +67,8 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Transition( - fromScene = SceneKey.QuickSettings, - toScene = SceneKey.Shade, + fromScene = Scenes.QuickSettings, + toScene = Scenes.Shade, progress = MutableStateFlow(.3f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -96,8 +96,8 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Transition( - fromScene = SceneKey.QuickSettings, - toScene = SceneKey.Shade, + fromScene = Scenes.QuickSettings, + toScene = Scenes.Shade, progress = progress, isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -120,8 +120,8 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Transition( - fromScene = SceneKey.QuickSettings, - toScene = SceneKey.Shade, + fromScene = Scenes.QuickSettings, + toScene = Scenes.Shade, progress = MutableStateFlow(.3f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -143,7 +143,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { keyguardRepository.setStatusBarState(StatusBarState.SHADE) val transitionState = MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(SceneKey.Shade) + ObservableTransitionState.Idle(Scenes.Shade) ) sceneInteractor.setTransitionState(transitionState) runCurrent() @@ -161,7 +161,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { keyguardRepository.setStatusBarState(StatusBarState.SHADE) val transitionState = MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(SceneKey.QuickSettings) + ObservableTransitionState.Idle(Scenes.QuickSettings) ) sceneInteractor.setTransitionState(transitionState) runCurrent() @@ -174,7 +174,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { fun lockscreenShadeExpansion_idle_onScene() = testComponent.runTest { // GIVEN an expansion flow based on transitions to and from a scene - val key = SceneKey.Shade + val key = Scenes.Shade val expansion = underTest.sceneBasedExpansion(sceneInteractor, key) val expansionAmount by collectLastValue(expansion) @@ -191,13 +191,13 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { fun lockscreenShadeExpansion_idle_onDifferentScene() = testComponent.runTest { // GIVEN an expansion flow based on transitions to and from a scene - val expansion = underTest.sceneBasedExpansion(sceneInteractor, SceneKey.Shade) + val expansion = underTest.sceneBasedExpansion(sceneInteractor, Scenes.Shade) val expansionAmount by collectLastValue(expansion) // WHEN transition state is idle on a different scene val transitionState = MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(SceneKey.Lockscreen) + ObservableTransitionState.Idle(Scenes.Lockscreen) ) sceneInteractor.setTransitionState(transitionState) @@ -209,7 +209,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { fun lockscreenShadeExpansion_transitioning_toScene() = testComponent.runTest { // GIVEN an expansion flow based on transitions to and from a scene - val key = SceneKey.QuickSettings + val key = Scenes.QuickSettings val expansion = underTest.sceneBasedExpansion(sceneInteractor, key) val expansionAmount by collectLastValue(expansion) @@ -218,7 +218,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Transition( - fromScene = SceneKey.Lockscreen, + fromScene = Scenes.Lockscreen, toScene = key, progress = progress, isInitiatedByUserInput = false, @@ -247,7 +247,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { fun lockscreenShadeExpansion_transitioning_fromScene() = testComponent.runTest { // GIVEN an expansion flow based on transitions to and from a scene - val key = SceneKey.QuickSettings + val key = Scenes.QuickSettings val expansion = underTest.sceneBasedExpansion(sceneInteractor, key) val expansionAmount by collectLastValue(expansion) @@ -257,7 +257,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Transition( fromScene = key, - toScene = SceneKey.Lockscreen, + toScene = Scenes.Lockscreen, progress = progress, isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -290,8 +290,8 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Transition( - fromScene = SceneKey.Gone, - toScene = SceneKey.QuickSettings, + fromScene = Scenes.Gone, + toScene = Scenes.QuickSettings, progress = MutableStateFlow(.1f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -313,8 +313,8 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Transition( - fromScene = SceneKey.Shade, - toScene = SceneKey.QuickSettings, + fromScene = Scenes.Shade, + toScene = Scenes.QuickSettings, progress = MutableStateFlow(.1f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -331,7 +331,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { fun lockscreenShadeExpansion_transitioning_toAndFromDifferentScenes() = testComponent.runTest { // GIVEN an expansion flow based on transitions to and from a scene - val expansion = underTest.sceneBasedExpansion(sceneInteractor, SceneKey.QuickSettings) + val expansion = underTest.sceneBasedExpansion(sceneInteractor, Scenes.QuickSettings) val expansionAmount by collectLastValue(expansion) // WHEN transition state is starting to between different scenes @@ -339,8 +339,8 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Transition( - fromScene = SceneKey.Lockscreen, - toScene = SceneKey.Shade, + fromScene = Scenes.Lockscreen, + toScene = Scenes.Shade, progress = progress, isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -368,7 +368,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { fun userInteracting_idle() = testComponent.runTest { // GIVEN an interacting flow based on transitions to and from a scene - val key = SceneKey.Shade + val key = Scenes.Shade val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key) val interacting by collectLastValue(interactingFlow) @@ -385,7 +385,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { fun userInteracting_transitioning_toScene_programmatic() = testComponent.runTest { // GIVEN an interacting flow based on transitions to and from a scene - val key = SceneKey.QuickSettings + val key = Scenes.QuickSettings val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key) val interacting by collectLastValue(interactingFlow) @@ -394,7 +394,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Transition( - fromScene = SceneKey.Lockscreen, + fromScene = Scenes.Lockscreen, toScene = key, progress = progress, isInitiatedByUserInput = false, @@ -423,7 +423,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { fun userInteracting_transitioning_toScene_userInputDriven() = testComponent.runTest { // GIVEN an interacting flow based on transitions to and from a scene - val key = SceneKey.QuickSettings + val key = Scenes.QuickSettings val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key) val interacting by collectLastValue(interactingFlow) @@ -432,7 +432,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Transition( - fromScene = SceneKey.Lockscreen, + fromScene = Scenes.Lockscreen, toScene = key, progress = progress, isInitiatedByUserInput = true, @@ -461,7 +461,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { fun userInteracting_transitioning_fromScene_programmatic() = testComponent.runTest { // GIVEN an interacting flow based on transitions to and from a scene - val key = SceneKey.QuickSettings + val key = Scenes.QuickSettings val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key) val interacting by collectLastValue(interactingFlow) @@ -471,7 +471,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Transition( fromScene = key, - toScene = SceneKey.Lockscreen, + toScene = Scenes.Lockscreen, progress = progress, isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -499,7 +499,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { fun userInteracting_transitioning_fromScene_userInputDriven() = testComponent.runTest { // GIVEN an interacting flow based on transitions to and from a scene - val key = SceneKey.QuickSettings + val key = Scenes.QuickSettings val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key) val interacting by collectLastValue(interactingFlow) @@ -509,7 +509,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Transition( fromScene = key, - toScene = SceneKey.Lockscreen, + toScene = Scenes.Lockscreen, progress = progress, isInitiatedByUserInput = true, isUserInputOngoing = flowOf(false), @@ -537,7 +537,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { fun userInteracting_transitioning_toAndFromDifferentScenes() = testComponent.runTest { // GIVEN an interacting flow based on transitions to and from a scene - val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, SceneKey.Shade) + val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, Scenes.Shade) val interacting by collectLastValue(interactingFlow) // WHEN transition state is starting to between different scenes @@ -545,8 +545,8 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Transition( - fromScene = SceneKey.Lockscreen, - toScene = SceneKey.QuickSettings, + fromScene = Scenes.Lockscreen, + toScene = Scenes.QuickSettings, progress = MutableStateFlow(0f), isInitiatedByUserInput = true, isUserInputOngoing = flowOf(false), diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt index d655ade5cf2c..853b00d345bc 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt @@ -30,7 +30,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.media.controls.domain.pipeline.MediaDataManager import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.domain.interactor.privacyChipInteractor import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel @@ -125,7 +125,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { ) kosmos.fakeDeviceEntryRepository.setUnlocked(false) - assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Lockscreen) + assertThat(upTransitionSceneKey).isEqualTo(Scenes.Lockscreen) } @Test @@ -137,7 +137,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { ) kosmos.fakeDeviceEntryRepository.setUnlocked(true) - assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone) + assertThat(upTransitionSceneKey).isEqualTo(Scenes.Gone) } @Test @@ -148,9 +148,9 @@ class ShadeSceneViewModelTest : SysuiTestCase() { kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.None ) - sceneInteractor.changeScene(SceneKey.Lockscreen, "reason") + sceneInteractor.changeScene(Scenes.Lockscreen, "reason") - assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Lockscreen) + assertThat(upTransitionSceneKey).isEqualTo(Scenes.Lockscreen) } @Test @@ -163,9 +163,9 @@ class ShadeSceneViewModelTest : SysuiTestCase() { AuthenticationMethodModel.None ) runCurrent() - sceneInteractor.changeScene(SceneKey.Gone, "reason") + sceneInteractor.changeScene(Scenes.Gone, "reason") - assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone) + assertThat(upTransitionSceneKey).isEqualTo(Scenes.Gone) } @Test @@ -206,7 +206,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { underTest.onContentClicked() - assertThat(currentScene).isEqualTo(SceneKey.Gone) + assertThat(currentScene).isEqualTo(Scenes.Gone) } @Test @@ -221,7 +221,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { underTest.onContentClicked() - assertThat(currentScene).isEqualTo(SceneKey.Bouncer) + assertThat(currentScene).isEqualTo(Scenes.Bouncer) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt index efd8f000df41..47918c8c1b3d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt @@ -20,6 +20,7 @@ package com.android.systemui.statusbar.notification import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.coroutines.collectLastValue @@ -28,8 +29,7 @@ import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.kosmos.testScope import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags -import com.android.systemui.scene.shared.model.ObservableTransitionState -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationStackAppearanceViewModel import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel @@ -92,19 +92,19 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() { testScope.runTest { val transitionState = MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(scene = SceneKey.Gone) + ObservableTransitionState.Idle(scene = Scenes.Gone) ) sceneInteractor.setTransitionState(transitionState) val expandFraction by collectLastValue(appearanceViewModel.expandFraction) assertThat(expandFraction).isEqualTo(0f) fakeSceneDataSource.pause() - sceneInteractor.changeScene(SceneKey.Shade, "reason") + sceneInteractor.changeScene(Scenes.Shade, "reason") val transitionProgress = MutableStateFlow(0f) transitionState.value = ObservableTransitionState.Transition( - fromScene = SceneKey.Gone, - toScene = SceneKey.Shade, + fromScene = Scenes.Gone, + toScene = Scenes.Shade, progress = transitionProgress, isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -117,7 +117,7 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() { assertThat(expandFraction).isWithin(0.01f).of(progress) } - fakeSceneDataSource.unpause(expectedScene = SceneKey.Shade) + fakeSceneDataSource.unpause(expectedScene = Scenes.Shade) assertThat(expandFraction).isWithin(0.01f).of(1f) } @@ -126,7 +126,7 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() { testScope.runTest { val transitionState = MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(scene = SceneKey.Lockscreen) + ObservableTransitionState.Idle(scene = Scenes.Lockscreen) ) sceneInteractor.setTransitionState(transitionState) val expandFraction by collectLastValue(appearanceViewModel.expandFraction) @@ -138,19 +138,19 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() { testScope.runTest { val transitionState = MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(scene = SceneKey.Shade) + ObservableTransitionState.Idle(scene = Scenes.Shade) ) sceneInteractor.setTransitionState(transitionState) val expandFraction by collectLastValue(appearanceViewModel.expandFraction) assertThat(expandFraction).isEqualTo(1f) fakeSceneDataSource.pause() - sceneInteractor.changeScene(SceneKey.QuickSettings, "reason") + sceneInteractor.changeScene(Scenes.QuickSettings, "reason") val transitionProgress = MutableStateFlow(0f) transitionState.value = ObservableTransitionState.Transition( - fromScene = SceneKey.Shade, - toScene = SceneKey.QuickSettings, + fromScene = Scenes.Shade, + toScene = Scenes.QuickSettings, progress = transitionProgress, isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -163,7 +163,7 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() { assertThat(expandFraction).isEqualTo(1f) } - fakeSceneDataSource.unpause(expectedScene = SceneKey.QuickSettings) + fakeSceneDataSource.unpause(expectedScene = Scenes.QuickSettings) assertThat(expandFraction).isEqualTo(1f) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index 7f5a658587f3..0de15b8db665 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -21,13 +21,13 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.communal.domain.interactor.communalInteractor -import com.android.systemui.communal.shared.model.CommunalSceneKey -import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState +import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic @@ -278,8 +278,8 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { ) ) val idleTransitionState = - MutableStateFlow<ObservableCommunalTransitionState>( - ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal) + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(CommunalScenes.Communal) ) communalInteractor.setTransitionState(idleTransitionState) runCurrent() @@ -391,8 +391,8 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { // Move to glanceable hub val idleTransitionState = - MutableStateFlow<ObservableCommunalTransitionState>( - ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal) + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(CommunalScenes.Communal) ) communalInteractor.setTransitionState(idleTransitionState) runCurrent() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt index c01f1c71fdd3..8aa0e3fc4d23 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt @@ -35,10 +35,10 @@ import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.plugins.ActivityStarter.OnDismissAction import com.android.systemui.settings.UserTracker import com.android.systemui.shade.ShadeController -import com.android.systemui.shade.ShadeViewController import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.shade.data.repository.ShadeAnimationRepository import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl +import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.SysuiStatusBarStateController @@ -76,7 +76,7 @@ class ActivityStarterImplTest : SysuiTestCase() { @Mock private lateinit var biometricUnlockController: BiometricUnlockController @Mock private lateinit var keyguardViewMediator: KeyguardViewMediator @Mock private lateinit var shadeController: ShadeController - @Mock private lateinit var shadeViewController: ShadeViewController + @Mock private lateinit var commandQueue: CommandQueue @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager @Mock private lateinit var mActivityTransitionAnimator: ActivityTransitionAnimator @Mock private lateinit var lockScreenUserManager: NotificationLockscreenUserManager @@ -105,7 +105,7 @@ class ActivityStarterImplTest : SysuiTestCase() { Lazy { biometricUnlockController }, Lazy { keyguardViewMediator }, Lazy { shadeController }, - Lazy { shadeViewController }, + commandQueue, shadeAnimationInteractor, Lazy { statusBarKeyguardViewManager }, Lazy { notifShadeWindowController }, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt index 243aab24b07d..dcf635e622f4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt @@ -32,8 +32,8 @@ import com.android.systemui.util.mockito.whenever import com.android.systemui.volume.localMediaRepository import com.android.systemui.volume.mediaController import com.android.systemui.volume.mediaControllerRepository +import com.android.systemui.volume.mediaOutputActionsInteractor import com.android.systemui.volume.mediaOutputInteractor -import com.android.systemui.volume.panel.mediaOutputActionsInteractor import com.android.systemui.volume.panel.volumePanelViewModel import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi diff --git a/packages/SystemUI/res/layout/notif_half_shelf.xml b/packages/SystemUI/res/layout/notif_half_shelf.xml index 68c8dd96d188..d8d298573d04 100644 --- a/packages/SystemUI/res/layout/notif_half_shelf.xml +++ b/packages/SystemUI/res/layout/notif_half_shelf.xml @@ -19,11 +19,11 @@ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:id="@+id/half_shelf_dialog" android:orientation="vertical" - android:layout_width="wrap_content" + android:layout_width="@dimen/large_dialog_width" android:layout_height="wrap_content" android:layout_gravity="center_horizontal|bottom" - android:paddingStart="4dp" - android:paddingEnd="4dp"> + android:paddingLeft="@dimen/dialog_side_padding" + android:paddingRight="@dimen/dialog_side_padding"> <LinearLayout android:id="@+id/half_shelf" diff --git a/packages/SystemUI/res/layout/scene_window_root.xml b/packages/SystemUI/res/layout/scene_window_root.xml index bb8de4c32e76..0dcd15b429c1 100644 --- a/packages/SystemUI/res/layout/scene_window_root.xml +++ b/packages/SystemUI/res/layout/scene_window_root.xml @@ -24,7 +24,7 @@ android:id="@+id/scene_window_root" android:layout_width="match_parent" android:layout_height="match_parent" - android:fitsSystemWindows="false"> + android:fitsSystemWindows="true"> <include layout="@layout/super_notification_shade" android:layout_width="match_parent" diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml index c4d7f8bbb349..515ef619141b 100644 --- a/packages/SystemUI/res/values-af/strings.xml +++ b/packages/SystemUI/res/values-af/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Lui"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibreer"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Demp"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Saai uit"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Onbeskikbaar omdat luitoon gedemp is"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Tik om te ontdemp."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Tik om op vibreer te stel. Toeganklikheidsdienste kan dalk gedemp wees."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Tik om te demp. Toeganklikheidsdienste kan dalk gedemp wees."</string> diff --git a/packages/SystemUI/res/values-af/tiles_states_strings.xml b/packages/SystemUI/res/values-af/tiles_states_strings.xml index 662aa717ffc1..1c9a79414ff1 100644 --- a/packages/SystemUI/res/values-af/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-af/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Af"</item> <item msgid="578444932039713369">"Aan"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Onbeskikbaar"</item> <item msgid="8707481475312432575">"Af"</item> diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index 37ba3c182422..9763ff2ae468 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"ጥሪ"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"ንዘር"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"ድምጸ-ከል አድርግ"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Cast"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"የጥሪ ድምጽ ስለተዘጋ አይገኝም"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s። ድምጸ-ከል ለማድረግ መታ ያድርጉ"</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s። ወደ ንዝረት ለማቀናበር መታ ያድርጉ። የተደራሽነት አገልግሎቶች ድምጸ-ከል ሊደረግባቸው ይችላል።"</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s። ድምጸ-ከል ለማድረግ መታ ያድርጉ። የተደራሽነት አገልግሎቶች ድምጸ-ከል ሊደረግባቸው ይችላል።"</string> diff --git a/packages/SystemUI/res/values-am/tiles_states_strings.xml b/packages/SystemUI/res/values-am/tiles_states_strings.xml index e5d68d985792..3fb24b983162 100644 --- a/packages/SystemUI/res/values-am/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-am/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"ጠፍቷል"</item> <item msgid="578444932039713369">"በርቷል"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"አይገኝም"</item> <item msgid="8707481475312432575">"ጠፍቷል"</item> diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index 1de96aea2210..1b7e3037103f 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"استصدار رنين"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"اهتزاز"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"كتم الصوت"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"البثّ"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"يتعذّر التغيير بسبب كتم صوت الرنين."</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. انقر لإلغاء التجاهل."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. انقر للتعيين على الاهتزاز. قد يتم تجاهل خدمات \"سهولة الاستخدام\"."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. انقر للتجاهل. قد يتم تجاهل خدمات \"سهولة الاستخدام\"."</string> diff --git a/packages/SystemUI/res/values-ar/tiles_states_strings.xml b/packages/SystemUI/res/values-ar/tiles_states_strings.xml index 856ae1db8fa1..cf050ac26473 100644 --- a/packages/SystemUI/res/values-ar/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ar/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"الميزة غير مفعّلة"</item> <item msgid="578444932039713369">"الميزة مفعّلة"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"الميزة غير متاحة"</item> <item msgid="8707481475312432575">"الميزة غير مفعّلة"</item> diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml index da69f77836a1..429f03ed3a63 100644 --- a/packages/SystemUI/res/values-as/strings.xml +++ b/packages/SystemUI/res/values-as/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"ৰিং"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"কম্পন"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"মিউট"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"কাষ্ট কৰক"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"ৰিং মিউট কৰি থোৱাৰ বাবে উপলব্ধ নহয়"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s। আনমিউট কৰিবৰ বাবে টিপক।"</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s। কম্পনৰ বাবে টিপক। দিব্য়াংগসকলৰ বাবে থকা সেৱা মিউট হৈ থাকিব পাৰে।"</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s। মিউট কৰিবলৈ টিপক। দিব্য়াংগসকলৰ বাবে থকা সেৱা মিউট হৈ থাকিব পাৰে।"</string> diff --git a/packages/SystemUI/res/values-as/tiles_states_strings.xml b/packages/SystemUI/res/values-as/tiles_states_strings.xml index a9c3e3b997b7..f4268ed17ff5 100644 --- a/packages/SystemUI/res/values-as/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-as/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"অফ আছে"</item> <item msgid="578444932039713369">"অন কৰা আছে"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"উপলব্ধ নহয়"</item> <item msgid="8707481475312432575">"অফ আছে"</item> diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml index 8857ddb72b47..639cbbc4cef1 100644 --- a/packages/SystemUI/res/values-az/strings.xml +++ b/packages/SystemUI/res/values-az/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Zəng"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrasiya"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Susdurun"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Yayım"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Zəng səssiz edildiyi üçün əlçatan deyil"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Səsli etmək üçün tıklayın."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Vibrasiyanı ayarlamaq üçün tıklayın. Əlçatımlılıq xidmətləri səssiz edilmiş ola bilər."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Səssiz etmək üçün tıklayın. Əlçatımlılıq xidmətləri səssiz edilmiş ola bilər."</string> diff --git a/packages/SystemUI/res/values-az/tiles_states_strings.xml b/packages/SystemUI/res/values-az/tiles_states_strings.xml index d973e4ead3c2..eeb81ccf42e1 100644 --- a/packages/SystemUI/res/values-az/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-az/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Deaktiv"</item> <item msgid="578444932039713369">"Aktiv"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Əlçatan deyil"</item> <item msgid="8707481475312432575">"Deaktiv"</item> diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml index 677f169f8bc4..e97dbec9a690 100644 --- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml +++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Aktiviraj zvono"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibriraj"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Isključi zvuk"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Prebacivanje"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Nedostupno jer je zvuk isključen"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Dodirnite da biste uključili zvuk."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Dodirnite da biste podesili na vibraciju. Zvuk usluga pristupačnosti će možda biti isključen."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Dodirnite da biste isključili zvuk. Zvuk usluga pristupačnosti će možda biti isključen."</string> diff --git a/packages/SystemUI/res/values-b+sr+Latn/tiles_states_strings.xml b/packages/SystemUI/res/values-b+sr+Latn/tiles_states_strings.xml index 32051ef19743..217d99975d3f 100644 --- a/packages/SystemUI/res/values-b+sr+Latn/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-b+sr+Latn/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Isključeno"</item> <item msgid="578444932039713369">"Uključeno"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Nedostupno"</item> <item msgid="8707481475312432575">"Isključeno"</item> diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml index 7feb1607be22..3b06d05e76f5 100644 --- a/packages/SystemUI/res/values-be/strings.xml +++ b/packages/SystemUI/res/values-be/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Званок"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Вібрацыя"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Гук выключаны"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Трансляцыя"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Недаступна, бо выключаны гук выклікаў"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Дакраніцеся, каб уключыць гук."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Дакраніцеся, каб уключыць вібрацыю. Можа быць адключаны гук службаў спецыяльных магчымасцей."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Дакраніцеся, каб адключыць гук. Можа быць адключаны гук службаў спецыяльных магчымасцей."</string> diff --git a/packages/SystemUI/res/values-be/tiles_states_strings.xml b/packages/SystemUI/res/values-be/tiles_states_strings.xml index e71c29bd15de..717e4c9a9c39 100644 --- a/packages/SystemUI/res/values-be/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-be/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Выключана"</item> <item msgid="578444932039713369">"Уключана"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Недаступна"</item> <item msgid="8707481475312432575">"Выключана"</item> diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml index d684d650af94..643ef9cecbf1 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Позвъняване"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Вибриране"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Без звук"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Предаване"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Не е налице, защото звъненето е спряно"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Докоснете, за да включите отново звука."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Докоснете, за да зададете вибриране. Възможно е звукът на услугите за достъпност да бъде заглушен."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Докоснете, за да заглушите звука. Възможно е звукът на услугите за достъпност да бъде заглушен."</string> diff --git a/packages/SystemUI/res/values-bg/tiles_states_strings.xml b/packages/SystemUI/res/values-bg/tiles_states_strings.xml index 24b41d23e900..58fa82bbc77a 100644 --- a/packages/SystemUI/res/values-bg/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-bg/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Изкл."</item> <item msgid="578444932039713369">"Вкл."</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Не е налице"</item> <item msgid="8707481475312432575">"Изкл."</item> diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml index db52b2ea1621..9a2f040c7e7e 100644 --- a/packages/SystemUI/res/values-bn/strings.xml +++ b/packages/SystemUI/res/values-bn/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"রিং"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"ভাইব্রেট"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"মিউট"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"কাস্ট করুন"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"রিং মিউট করা হয়েছে বলে উপলভ্য নেই"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s। সশব্দ করতে আলতো চাপুন।"</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s। কম্পন এ সেট করতে আলতো চাপুন। অ্যাক্সেসযোগ্যতার পরিষেবাগুলিকে মিউট করা হতে পারে।"</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s। মিউট করতে আলতো চাপুন। অ্যাক্সেসযোগ্যতার পরিষেবাগুলিকে মিউট করা হতে পারে।"</string> diff --git a/packages/SystemUI/res/values-bn/tiles_states_strings.xml b/packages/SystemUI/res/values-bn/tiles_states_strings.xml index 59061c223580..5c3c66c29461 100644 --- a/packages/SystemUI/res/values-bn/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-bn/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"বন্ধ আছে"</item> <item msgid="578444932039713369">"চালু আছে"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"উপলভ্য নেই"</item> <item msgid="8707481475312432575">"বন্ধ আছে"</item> diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml index 3d54f6cfb747..db102a0ed68b 100644 --- a/packages/SystemUI/res/values-bs/strings.xml +++ b/packages/SystemUI/res/values-bs/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Zvono"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibriranje"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Isključi zvuk"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Emitiraj"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Nedostupno zbog isključenog zvona"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Dodirnite da uključite zvukove."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Dodirnite za postavljanje vibracije. Zvukovi usluga pristupačnosti mogu biti isključeni."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Dodirnite da isključite zvuk. Zvukovi usluga pristupačnosti mogu biti isključeni."</string> diff --git a/packages/SystemUI/res/values-bs/tiles_states_strings.xml b/packages/SystemUI/res/values-bs/tiles_states_strings.xml index 32051ef19743..217d99975d3f 100644 --- a/packages/SystemUI/res/values-bs/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-bs/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Isključeno"</item> <item msgid="578444932039713369">"Uključeno"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Nedostupno"</item> <item msgid="8707481475312432575">"Isključeno"</item> diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index bcc451b60e16..c5287344c7c0 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Fes sonar"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibra"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Silencia"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Emet"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"No disponible perquè el so està silenciat"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Toca per activar el so."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Toca per activar la vibració. Pot ser que els serveis d\'accessibilitat se silenciïn."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Toca per silenciar el so. Pot ser que els serveis d\'accessibilitat se silenciïn."</string> diff --git a/packages/SystemUI/res/values-ca/tiles_states_strings.xml b/packages/SystemUI/res/values-ca/tiles_states_strings.xml index e99926c57324..c1ac5a356f05 100644 --- a/packages/SystemUI/res/values-ca/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ca/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Desactivat"</item> <item msgid="578444932039713369">"Activat"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"No disponible"</item> <item msgid="8707481475312432575">"Desactivat"</item> diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index bf7566a803ee..f29dabb388d8 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Vyzvánění"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrace"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Ztlumení"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Odesílání"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Nedostupné, protože vyzvánění je ztlumené"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Klepnutím zapnete zvuk."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Klepnutím aktivujete režim vibrací. Služby přístupnosti mohou být ztlumeny."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Klepnutím vypnete zvuk. Služby přístupnosti mohou být ztlumeny."</string> diff --git a/packages/SystemUI/res/values-cs/tiles_states_strings.xml b/packages/SystemUI/res/values-cs/tiles_states_strings.xml index 6359f94112bb..0a4d4d0cfeef 100644 --- a/packages/SystemUI/res/values-cs/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-cs/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Vypnuto"</item> <item msgid="578444932039713369">"Zapnuto"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Nedostupné"</item> <item msgid="8707481475312432575">"Vypnuto"</item> diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index 60cf76a2d5e7..ec899f2f2c20 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Ring"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibration"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Slå lyden fra"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Cast"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Ikke muligt, da ringetonen er slået fra"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Tryk for at slå lyden til."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Tryk for at konfigurere til at vibrere. Tilgængelighedstjenester kan blive deaktiveret."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Tryk for at slå lyden fra. Lyden i tilgængelighedstjenester kan blive slået fra."</string> diff --git a/packages/SystemUI/res/values-da/tiles_states_strings.xml b/packages/SystemUI/res/values-da/tiles_states_strings.xml index 1daed4cd6864..2391753f8c0b 100644 --- a/packages/SystemUI/res/values-da/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-da/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Fra"</item> <item msgid="578444932039713369">"Til"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Ikke tilgængelig"</item> <item msgid="8707481475312432575">"Fra"</item> diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index 60165d24a260..f7e74c906960 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Klingeln lassen"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrieren"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Stummschalten"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Stream"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Nicht verfügbar, da Klingelton stumm"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Zum Aufheben der Stummschaltung tippen."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Tippen, um Vibrieren festzulegen. Bedienungshilfen werden unter Umständen stummgeschaltet."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Zum Stummschalten tippen. Bedienungshilfen werden unter Umständen stummgeschaltet."</string> diff --git a/packages/SystemUI/res/values-de/tiles_states_strings.xml b/packages/SystemUI/res/values-de/tiles_states_strings.xml index 9a087472bd03..3aae04bbc931 100644 --- a/packages/SystemUI/res/values-de/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-de/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Aus"</item> <item msgid="578444932039713369">"An"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Nicht verfügbar"</item> <item msgid="8707481475312432575">"Aus"</item> diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml index 6c45adfbda4b..6b341fdf1d93 100644 --- a/packages/SystemUI/res/values-el/strings.xml +++ b/packages/SystemUI/res/values-el/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Κουδούνισμα"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Δόνηση"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Σίγαση"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Μετάδοση"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Μη διαθέσιμο λόγω σίγασης ήχου κλήσης"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Πατήστε για κατάργηση σίγασης."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Πατήστε για ενεργοποιήσετε τη δόνηση. Οι υπηρεσίες προσβασιμότητας ενδέχεται να τεθούν σε σίγαση."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Πατήστε για σίγαση. Οι υπηρεσίες προσβασιμότητας ενδέχεται να τεθούν σε σίγαση."</string> diff --git a/packages/SystemUI/res/values-el/tiles_states_strings.xml b/packages/SystemUI/res/values-el/tiles_states_strings.xml index 4d94515ac130..035f1171d3b4 100644 --- a/packages/SystemUI/res/values-el/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-el/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Ανενεργό"</item> <item msgid="578444932039713369">"Ενεργό"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Μη διαθέσιμο"</item> <item msgid="8707481475312432575">"Ανενεργό"</item> diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml index 3dbe2dc013f5..021f7db6c2b6 100644 --- a/packages/SystemUI/res/values-en-rAU/strings.xml +++ b/packages/SystemUI/res/values-en-rAU/strings.xml @@ -429,8 +429,7 @@ <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Add more widgets"</string> <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Long press to customise widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Customise widgets"</string> - <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) --> - <skip /> + <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App icon for disabled widget"</string> <string name="edit_widget" msgid="9030848101135393954">"Edit widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Remove"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Add widget"</string> @@ -575,10 +574,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Ring"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrate"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Mute"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Cast"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Unavailable because ring is muted"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Tap to unmute."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Tap to set to vibrate. Accessibility services may be muted."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Tap to mute. Accessibility services may be muted."</string> @@ -839,10 +836,8 @@ <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Power menu"</string> <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Page <xliff:g id="ID_1">%1$d</xliff:g> of <xliff:g id="ID_2">%2$d</xliff:g>"</string> <string name="tuner_lock_screen" msgid="2267383813241144544">"Lock screen"</string> - <!-- no translation found for finder_active (7907846989716941952) --> - <skip /> - <!-- no translation found for shutdown_progress (5464239146561542178) --> - <skip /> + <string name="finder_active" msgid="7907846989716941952">"You can locate this phone with Find My Device even when powered off"</string> + <string name="shutdown_progress" msgid="5464239146561542178">"Shutting down…"</string> <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"See care steps"</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"See care steps"</string> <string name="high_temp_alarm_title" msgid="8654754369605452169">"Unplug your device"</string> diff --git a/packages/SystemUI/res/values-en-rAU/tiles_states_strings.xml b/packages/SystemUI/res/values-en-rAU/tiles_states_strings.xml index 0cf28684aa48..2576b6080638 100644 --- a/packages/SystemUI/res/values-en-rAU/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-en-rAU/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Off"</item> <item msgid="578444932039713369">"On"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Unavailable"</item> <item msgid="8707481475312432575">"Off"</item> diff --git a/packages/SystemUI/res/values-en-rCA/tiles_states_strings.xml b/packages/SystemUI/res/values-en-rCA/tiles_states_strings.xml index 0cf28684aa48..2576b6080638 100644 --- a/packages/SystemUI/res/values-en-rCA/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-en-rCA/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Off"</item> <item msgid="578444932039713369">"On"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Unavailable"</item> <item msgid="8707481475312432575">"Off"</item> diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml index 3dbe2dc013f5..021f7db6c2b6 100644 --- a/packages/SystemUI/res/values-en-rGB/strings.xml +++ b/packages/SystemUI/res/values-en-rGB/strings.xml @@ -429,8 +429,7 @@ <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Add more widgets"</string> <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Long press to customise widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Customise widgets"</string> - <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) --> - <skip /> + <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App icon for disabled widget"</string> <string name="edit_widget" msgid="9030848101135393954">"Edit widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Remove"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Add widget"</string> @@ -575,10 +574,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Ring"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrate"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Mute"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Cast"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Unavailable because ring is muted"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Tap to unmute."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Tap to set to vibrate. Accessibility services may be muted."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Tap to mute. Accessibility services may be muted."</string> @@ -839,10 +836,8 @@ <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Power menu"</string> <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Page <xliff:g id="ID_1">%1$d</xliff:g> of <xliff:g id="ID_2">%2$d</xliff:g>"</string> <string name="tuner_lock_screen" msgid="2267383813241144544">"Lock screen"</string> - <!-- no translation found for finder_active (7907846989716941952) --> - <skip /> - <!-- no translation found for shutdown_progress (5464239146561542178) --> - <skip /> + <string name="finder_active" msgid="7907846989716941952">"You can locate this phone with Find My Device even when powered off"</string> + <string name="shutdown_progress" msgid="5464239146561542178">"Shutting down…"</string> <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"See care steps"</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"See care steps"</string> <string name="high_temp_alarm_title" msgid="8654754369605452169">"Unplug your device"</string> diff --git a/packages/SystemUI/res/values-en-rGB/tiles_states_strings.xml b/packages/SystemUI/res/values-en-rGB/tiles_states_strings.xml index 0cf28684aa48..2576b6080638 100644 --- a/packages/SystemUI/res/values-en-rGB/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-en-rGB/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Off"</item> <item msgid="578444932039713369">"On"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Unavailable"</item> <item msgid="8707481475312432575">"Off"</item> diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml index 3dbe2dc013f5..021f7db6c2b6 100644 --- a/packages/SystemUI/res/values-en-rIN/strings.xml +++ b/packages/SystemUI/res/values-en-rIN/strings.xml @@ -429,8 +429,7 @@ <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Add more widgets"</string> <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Long press to customise widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Customise widgets"</string> - <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) --> - <skip /> + <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App icon for disabled widget"</string> <string name="edit_widget" msgid="9030848101135393954">"Edit widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Remove"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Add widget"</string> @@ -575,10 +574,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Ring"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrate"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Mute"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Cast"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Unavailable because ring is muted"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Tap to unmute."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Tap to set to vibrate. Accessibility services may be muted."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Tap to mute. Accessibility services may be muted."</string> @@ -839,10 +836,8 @@ <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Power menu"</string> <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Page <xliff:g id="ID_1">%1$d</xliff:g> of <xliff:g id="ID_2">%2$d</xliff:g>"</string> <string name="tuner_lock_screen" msgid="2267383813241144544">"Lock screen"</string> - <!-- no translation found for finder_active (7907846989716941952) --> - <skip /> - <!-- no translation found for shutdown_progress (5464239146561542178) --> - <skip /> + <string name="finder_active" msgid="7907846989716941952">"You can locate this phone with Find My Device even when powered off"</string> + <string name="shutdown_progress" msgid="5464239146561542178">"Shutting down…"</string> <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"See care steps"</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"See care steps"</string> <string name="high_temp_alarm_title" msgid="8654754369605452169">"Unplug your device"</string> diff --git a/packages/SystemUI/res/values-en-rIN/tiles_states_strings.xml b/packages/SystemUI/res/values-en-rIN/tiles_states_strings.xml index 0cf28684aa48..2576b6080638 100644 --- a/packages/SystemUI/res/values-en-rIN/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-en-rIN/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Off"</item> <item msgid="578444932039713369">"On"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Unavailable"</item> <item msgid="8707481475312432575">"Off"</item> diff --git a/packages/SystemUI/res/values-en-rXC/tiles_states_strings.xml b/packages/SystemUI/res/values-en-rXC/tiles_states_strings.xml index b9c8e5fcdd6a..42daf8a6b23d 100644 --- a/packages/SystemUI/res/values-en-rXC/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-en-rXC/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Off"</item> <item msgid="578444932039713369">"On"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Unavailable"</item> <item msgid="8707481475312432575">"Off"</item> diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index 15de0d5bacaf..1798e9aa4b2d 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -429,8 +429,7 @@ <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Agregar más widgets"</string> <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantén presionado para personalizar los widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizar widgets"</string> - <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) --> - <skip /> + <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ícono de la app de widget inhabilitado"</string> <string name="edit_widget" msgid="9030848101135393954">"Modificar widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Quitar"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Agregar widget"</string> @@ -575,10 +574,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Timbre"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrar"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Silenciar"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Transmisión"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"No disponible por timbre silenciado"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Presiona para dejar de silenciar."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Presiona para establecer el modo vibración. Es posible que los servicios de accesibilidad estén silenciados."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Presiona para silenciar. Es posible que los servicios de accesibilidad estén silenciados."</string> @@ -839,10 +836,8 @@ <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Menú de encendido"</string> <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Página <xliff:g id="ID_1">%1$d</xliff:g> de <xliff:g id="ID_2">%2$d</xliff:g>"</string> <string name="tuner_lock_screen" msgid="2267383813241144544">"Pantalla de bloqueo"</string> - <!-- no translation found for finder_active (7907846989716941952) --> - <skip /> - <!-- no translation found for shutdown_progress (5464239146561542178) --> - <skip /> + <string name="finder_active" msgid="7907846989716941952">"Puedes ubicar este teléfono con Encontrar mi dispositivo, incluso si está apagado"</string> + <string name="shutdown_progress" msgid="5464239146561542178">"Apagando…"</string> <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Ver pasos de mantenimiento"</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Ver pasos de mantenimiento"</string> <string name="high_temp_alarm_title" msgid="8654754369605452169">"Desenchufa el dispositivo"</string> diff --git a/packages/SystemUI/res/values-es-rUS/tiles_states_strings.xml b/packages/SystemUI/res/values-es-rUS/tiles_states_strings.xml index bb3983b0bb09..09abc543dbcf 100644 --- a/packages/SystemUI/res/values-es-rUS/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-es-rUS/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Desactivado"</item> <item msgid="578444932039713369">"Activado"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"No disponible"</item> <item msgid="8707481475312432575">"Desactivado"</item> diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml index 3edc603034f1..73c913fe9a6d 100644 --- a/packages/SystemUI/res/values-es/strings.xml +++ b/packages/SystemUI/res/values-es/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Hacer sonar"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrar"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Silenciar"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Enviar"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"No disponible (el tono está silenciado)"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Toca para activar el sonido."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Toca para poner el dispositivo en vibración. Los servicios de accesibilidad pueden silenciarse."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Toca para silenciar. Los servicios de accesibilidad pueden silenciarse."</string> diff --git a/packages/SystemUI/res/values-es/tiles_states_strings.xml b/packages/SystemUI/res/values-es/tiles_states_strings.xml index 66c7ee5efe6c..83b4627897da 100644 --- a/packages/SystemUI/res/values-es/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-es/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Desactivado"</item> <item msgid="578444932039713369">"Activado"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"No disponible"</item> <item msgid="8707481475312432575">"Desactivado"</item> diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml index 7ae5219795a9..6fa704490367 100644 --- a/packages/SystemUI/res/values-et/strings.xml +++ b/packages/SystemUI/res/values-et/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Helisemine"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibreerimine"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Vaigistatud"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Ülekandmine"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Pole saadaval, kuna helin on vaigistatud"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Puudutage vaigistuse tühistamiseks."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Puudutage värinarežiimi määramiseks. Juurdepääsetavuse teenused võidakse vaigistada."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Puudutage vaigistamiseks. Juurdepääsetavuse teenused võidakse vaigistada."</string> diff --git a/packages/SystemUI/res/values-et/tiles_states_strings.xml b/packages/SystemUI/res/values-et/tiles_states_strings.xml index 6a9edbbe812a..4f0551d7af74 100644 --- a/packages/SystemUI/res/values-et/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-et/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Väljas"</item> <item msgid="578444932039713369">"Sees"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Pole saadaval"</item> <item msgid="8707481475312432575">"Väljas"</item> diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml index d0b10a0db21d..564fbb395aeb 100644 --- a/packages/SystemUI/res/values-eu/strings.xml +++ b/packages/SystemUI/res/values-eu/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Jo tonua"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Dardara"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Ez jo tonua"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Igorri"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Ez dago erabilgarri, tonua desaktibatuta dagoelako"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Sakatu audioa aktibatzeko."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Sakatu dardara ezartzeko. Baliteke erabilerraztasun-eginbideen audioa desaktibatzea."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Sakatu audioa desaktibatzeko. Baliteke erabilerraztasun-eginbideen audioa desaktibatzea."</string> @@ -732,7 +730,7 @@ <string name="group_system_full_screenshot" msgid="5742204844232667785">"Atera pantaila-argazki bat"</string> <string name="group_system_access_system_app_shortcuts" msgid="8562482996626694026">"Erakutsi lasterbideak"</string> <string name="group_system_go_back" msgid="2730322046244918816">"Egin atzera"</string> - <string name="group_system_access_home_screen" msgid="4130366993484706483">"Joan hasierako pantailara"</string> + <string name="group_system_access_home_screen" msgid="4130366993484706483">"Joan orri nagusira"</string> <string name="group_system_overview_open_apps" msgid="5659958952937994104">"Ikusi azkenaldiko aplikazioak"</string> <string name="group_system_cycle_forward" msgid="5478663965957647805">"Ikusi azken aplikazioak banan-banan (aurrerantz)"</string> <string name="group_system_cycle_back" msgid="8194102916946802902">"Ikusi azken aplikazioak banan-banan (atzerantz)"</string> @@ -1096,7 +1094,7 @@ <string name="build_number_copy_toast" msgid="877720921605503046">"Kopiatu da konpilazio-zenbakia arbelean."</string> <string name="basic_status" msgid="2315371112182658176">"Elkarrizketa irekia"</string> <string name="select_conversation_title" msgid="6716364118095089519">"Elkarrizketa-widgetak"</string> - <string name="select_conversation_text" msgid="3376048251434956013">"Sakatu elkarrizketa bat hasierako pantailan gehitzeko"</string> + <string name="select_conversation_text" msgid="3376048251434956013">"Sakatu elkarrizketa bat orri nagusian gehitzeko"</string> <string name="no_conversations_text" msgid="5354115541282395015">"Azken elkarrizketak agertuko dira hemen"</string> <string name="priority_conversations" msgid="3967482288896653039">"Lehentasunezko elkarrizketak"</string> <string name="recent_conversations" msgid="8531874684782574622">"Azken elkarrizketak"</string> diff --git a/packages/SystemUI/res/values-eu/tiles_states_strings.xml b/packages/SystemUI/res/values-eu/tiles_states_strings.xml index d023076ca2bf..accecacbe8dd 100644 --- a/packages/SystemUI/res/values-eu/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-eu/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Desaktibatuta"</item> <item msgid="578444932039713369">"Aktibatuta"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Ez dago erabilgarri"</item> <item msgid="8707481475312432575">"Desaktibatuta"</item> diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index 0f937814e55d..95f17b0d1e56 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"زنگ زدن"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"لرزش"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"صامت"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"ارسال محتوا"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"دردسترس نیست، چون زنگ بیصدا شده است"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. برای باصدا کردن ضربه بزنید."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. برای تنظیم روی لرزش ضربه بزنید. ممکن است سرویسهای دسترسپذیری بیصدا شوند."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. برای صامت کردن ضربه بزنید. ممکن است سرویسهای دسترسپذیری صامت شود."</string> diff --git a/packages/SystemUI/res/values-fa/tiles_states_strings.xml b/packages/SystemUI/res/values-fa/tiles_states_strings.xml index b341e9e64c7b..01a549ed70b3 100644 --- a/packages/SystemUI/res/values-fa/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-fa/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"خاموش"</item> <item msgid="578444932039713369">"روشن"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"دردسترس نیست"</item> <item msgid="8707481475312432575">"خاموش"</item> diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml index 5d3959d92b68..ab022dd80277 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Soittoääni"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Värinä"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Äänetön"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Striimaa"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Ei käytettävissä, soittoääni mykistetty"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Poista mykistys koskettamalla."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Siirry värinätilaan koskettamalla. Myös esteettömyyspalvelut saattavat mykistyä."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Mykistä koskettamalla. Myös esteettömyyspalvelut saattavat mykistyä."</string> diff --git a/packages/SystemUI/res/values-fi/tiles_states_strings.xml b/packages/SystemUI/res/values-fi/tiles_states_strings.xml index bbd64fdb87da..f7a8ec94f1bd 100644 --- a/packages/SystemUI/res/values-fi/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-fi/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Poissa päältä"</item> <item msgid="578444932039713369">"Päällä"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Ei saatavilla"</item> <item msgid="8707481475312432575">"Poissa päältä"</item> diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml index e02c75ceae3b..1186c81c1a10 100644 --- a/packages/SystemUI/res/values-fr-rCA/strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Sonnerie"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibration"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Sonnerie désactivée"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Diffuser"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Inaccessible : sonnerie en sourdine"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Touchez pour réactiver le son."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Touchez pour activer les vibrations. Il est possible de couper le son des services d\'accessibilité."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Touchez pour couper le son. Il est possible de couper le son des services d\'accessibilité."</string> diff --git a/packages/SystemUI/res/values-fr-rCA/tiles_states_strings.xml b/packages/SystemUI/res/values-fr-rCA/tiles_states_strings.xml index b96984173d37..7b9708ef03f4 100644 --- a/packages/SystemUI/res/values-fr-rCA/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Désactivé"</item> <item msgid="578444932039713369">"Activé"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Non disponible"</item> <item msgid="8707481475312432575">"Désactivé"</item> diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml index 2621a904fa33..a02c9f7f9e6f 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Sonnerie"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibreur"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Couper le son"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Caster"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Indisponible, car la sonnerie est coupée"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Appuyez pour ne plus ignorer."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Appuyez pour mettre en mode vibreur. Vous pouvez ignorer les services d\'accessibilité."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Appuyez pour ignorer. Vous pouvez ignorer les services d\'accessibilité."</string> diff --git a/packages/SystemUI/res/values-fr/tiles_states_strings.xml b/packages/SystemUI/res/values-fr/tiles_states_strings.xml index 34440a0d299b..af1d09d3db3d 100644 --- a/packages/SystemUI/res/values-fr/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-fr/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Désactivé"</item> <item msgid="578444932039713369">"Activé"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Indisponible"</item> <item msgid="8707481475312432575">"Désactivée"</item> diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml index 1955c040990f..e758af880582 100644 --- a/packages/SystemUI/res/values-gl/strings.xml +++ b/packages/SystemUI/res/values-gl/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Facer soar"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrar"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Silenciar"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Emitir"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Non dispoñible (o son está silenciado)"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Toca para activar o son."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Toca para establecer a vibración. Pódense silenciar os servizos de accesibilidade."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Toca para silenciar. Pódense silenciar os servizos de accesibilidade."</string> diff --git a/packages/SystemUI/res/values-gl/tiles_states_strings.xml b/packages/SystemUI/res/values-gl/tiles_states_strings.xml index b03f31171e5c..a963decd28e9 100644 --- a/packages/SystemUI/res/values-gl/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-gl/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Non"</item> <item msgid="578444932039713369">"Si"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Non dispoñible"</item> <item msgid="8707481475312432575">"Non"</item> diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml index 5a1cf421da54..6d1d8df1bc27 100644 --- a/packages/SystemUI/res/values-gu/strings.xml +++ b/packages/SystemUI/res/values-gu/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"રિંગ કરો"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"વાઇબ્રેટ"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"મ્યૂટ કરો"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"કાસ્ટ કરો"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"રિંગ મ્યૂટ કરી હોવાના કારણે અનુપલબ્ધ છે"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. અનમ્યૂટ કરવા માટે ટૅપ કરો."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. વાઇબ્રેટ પર સેટ કરવા માટે ટૅપ કરો. ઍક્સેસિબિલિટી સેવાઓ મ્યૂટ કરવામાં આવી શકે છે."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. મ્યૂટ કરવા માટે ટૅપ કરો. ઍક્સેસિબિલિટી સેવાઓ મ્યૂટ કરવામાં આવી શકે છે."</string> diff --git a/packages/SystemUI/res/values-gu/tiles_states_strings.xml b/packages/SystemUI/res/values-gu/tiles_states_strings.xml index 5d1ad6ff0514..580ec104dfb4 100644 --- a/packages/SystemUI/res/values-gu/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-gu/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"બંધ છે"</item> <item msgid="578444932039713369">"ચાલુ છે"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"ઉપલબ્ધ નથી"</item> <item msgid="8707481475312432575">"બંધ છે"</item> diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index 4b8c959064af..2035429a00b9 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"आवाज़ चालू है"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"वाइब्रेशन"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"आवाज़ बंद है"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"कास्ट करें"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"रिंग म्यूट होने से आवाज़ नहीं सुनाई दी"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. अनम्यूट करने के लिए टैप करें."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. कंपन पर सेट करने के लिए टैप करें. सुलभता सेवाएं म्यूट हो सकती हैं."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. म्यूट करने के लिए टैप करें. सुलभता सेवाएं म्यूट हो सकती हैं."</string> diff --git a/packages/SystemUI/res/values-hi/tiles_states_strings.xml b/packages/SystemUI/res/values-hi/tiles_states_strings.xml index cd29fb92063f..3fd0b30ea4a0 100644 --- a/packages/SystemUI/res/values-hi/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-hi/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"बंद है"</item> <item msgid="578444932039713369">"चालू है"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"उपलब्ध नहीं है"</item> <item msgid="8707481475312432575">"बंद है"</item> diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml index 45666c07aa21..d50a95121148 100644 --- a/packages/SystemUI/res/values-hr/strings.xml +++ b/packages/SystemUI/res/values-hr/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Zvonjenje"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibriranje"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Zvuk je isključen"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Emitiraj"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Nedostupno jer je zvono utišano"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Dodirnite da biste uključili zvuk."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Dodirnite da biste postavili na vibraciju. Usluge pristupačnosti možda neće imati zvuk."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Dodirnite da biste isključili zvuk. Usluge pristupačnosti možda neće imati zvuk."</string> diff --git a/packages/SystemUI/res/values-hr/tiles_states_strings.xml b/packages/SystemUI/res/values-hr/tiles_states_strings.xml index 32051ef19743..217d99975d3f 100644 --- a/packages/SystemUI/res/values-hr/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-hr/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Isključeno"</item> <item msgid="578444932039713369">"Uključeno"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Nedostupno"</item> <item msgid="8707481475312432575">"Isključeno"</item> diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml index 3606d7486737..b09419bd396d 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Csörgés"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Rezgés"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Néma"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Átküldés"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Nem lehetséges, a csörgés le van némítva"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Koppintson a némítás megszüntetéséhez."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Koppintson a rezgés beállításához. Előfordulhat, hogy a kisegítő lehetőségek szolgáltatásai le vannak némítva."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Koppintson a némításhoz. Előfordulhat, hogy a kisegítő lehetőségek szolgáltatásai le vannak némítva."</string> diff --git a/packages/SystemUI/res/values-hu/tiles_states_strings.xml b/packages/SystemUI/res/values-hu/tiles_states_strings.xml index 157c552b31a6..fad2cd4e6d4b 100644 --- a/packages/SystemUI/res/values-hu/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-hu/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Ki"</item> <item msgid="578444932039713369">"Be"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Nem áll rendelkezésre"</item> <item msgid="8707481475312432575">"Ki"</item> diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml index 1510291e00f2..4ea86d0571bb 100644 --- a/packages/SystemUI/res/values-hy/strings.xml +++ b/packages/SystemUI/res/values-hy/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Սովորական"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Թրթռոց"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Անձայն"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Հեռարձակում"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Հասանելի չէ, երբ զանգի ձայնն անջատված է"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s: Հպեք՝ ձայնը միացնելու համար:"</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s: Հպեք՝ թրթռումը միացնելու համար: Մատչելիության ծառայությունների ձայնը կարող է անջատվել:"</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s: Հպեք՝ ձայնն անջատելու համար: Մատչելիության ծառայությունների ձայնը կարող է անջատվել:"</string> diff --git a/packages/SystemUI/res/values-hy/tiles_states_strings.xml b/packages/SystemUI/res/values-hy/tiles_states_strings.xml index 089716fa51d6..380d9d2d9a95 100644 --- a/packages/SystemUI/res/values-hy/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-hy/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Անջատված է"</item> <item msgid="578444932039713369">"Միացված է"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Հասանելի չէ"</item> <item msgid="8707481475312432575">"Անջատված է"</item> diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index 09a9938b5abb..188f3fbcd14d 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Dering"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Getar"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Nonaktifkan"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Transmisi"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Tidak tersedia karena volume dering dibisukan"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Ketuk untuk menyuarakan."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Ketuk untuk menyetel agar bergetar. Layanan aksesibilitas mungkin dibisukan."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Ketuk untuk membisukan. Layanan aksesibilitas mungkin dibisukan."</string> diff --git a/packages/SystemUI/res/values-in/tiles_states_strings.xml b/packages/SystemUI/res/values-in/tiles_states_strings.xml index 71460a71b414..9be5d0276a24 100644 --- a/packages/SystemUI/res/values-in/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-in/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Nonaktif"</item> <item msgid="578444932039713369">"Aktif"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Tidak tersedia"</item> <item msgid="8707481475312432575">"Nonaktif"</item> diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml index bbc8eab4f9da..47756c213877 100644 --- a/packages/SystemUI/res/values-is/strings.xml +++ b/packages/SystemUI/res/values-is/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Hringing"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Titringur"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Hljóð af"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Senda út"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Ekki í boði þar sem hringing er þögguð"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Ýttu til að hætta að þagga."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Ýttu til að stilla á titring. Hugsanlega verður slökkt á hljóði aðgengisþjónustu."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Ýttu til að þagga. Hugsanlega verður slökkt á hljóði aðgengisþjónustu."</string> diff --git a/packages/SystemUI/res/values-is/tiles_states_strings.xml b/packages/SystemUI/res/values-is/tiles_states_strings.xml index 17aaf6c2a877..1ee6e471fad3 100644 --- a/packages/SystemUI/res/values-is/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-is/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Slökkt"</item> <item msgid="578444932039713369">"Kveikt"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Ekki í boði"</item> <item msgid="8707481475312432575">"Slökkt"</item> diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml index 7c09c597f2a5..5bad44c836a7 100644 --- a/packages/SystemUI/res/values-it/strings.xml +++ b/packages/SystemUI/res/values-it/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Attiva suoneria"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Attiva vibrazione"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Silenzia"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Trasmissione"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Non disponibile con l\'audio disattivato"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Tocca per riattivare l\'audio."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Tocca per attivare la vibrazione. L\'audio dei servizi di accessibilità può essere disattivato."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Tocca per disattivare l\'audio. L\'audio dei servizi di accessibilità può essere disattivato."</string> diff --git a/packages/SystemUI/res/values-it/tiles_states_strings.xml b/packages/SystemUI/res/values-it/tiles_states_strings.xml index 7aa09d420f87..28e28aed82b8 100644 --- a/packages/SystemUI/res/values-it/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-it/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Off"</item> <item msgid="578444932039713369">"On"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Non disponibile"</item> <item msgid="8707481475312432575">"Off"</item> diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml index 644b599268ec..154de1523bbb 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"צלצול"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"רטט"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"השתקה"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"הפעלת Cast"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"לא זמין כי הצלצול מושתק"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. יש להקיש כדי לבטל את ההשתקה."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. צריך להקיש כדי להגדיר רטט. ייתכן ששירותי הנגישות מושתקים."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. יש להקיש כדי להשתיק. ייתכן ששירותי הנגישות יושתקו."</string> diff --git a/packages/SystemUI/res/values-iw/tiles_states_strings.xml b/packages/SystemUI/res/values-iw/tiles_states_strings.xml index bd2a6f7d0d2b..bb3eb10fd719 100644 --- a/packages/SystemUI/res/values-iw/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-iw/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"כבוי"</item> <item msgid="578444932039713369">"פועל"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"לא זמין"</item> <item msgid="8707481475312432575">"כבוי"</item> diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml index 3e0d24586686..227edd3570d0 100644 --- a/packages/SystemUI/res/values-ja/strings.xml +++ b/packages/SystemUI/res/values-ja/strings.xml @@ -429,8 +429,7 @@ <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"ウィジェットの追加"</string> <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"長押ししてウィジェットをカスタマイズ"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ウィジェットのカスタマイズ"</string> - <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) --> - <skip /> + <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"無効なウィジェットのアプリアイコン"</string> <string name="edit_widget" msgid="9030848101135393954">"ウィジェットを編集"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"削除"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ウィジェットを追加"</string> @@ -575,10 +574,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"着信音"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"バイブレーション"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"ミュート"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"キャスト"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"着信音がミュートされているため利用できません"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s。タップしてミュートを解除します。"</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s。タップしてバイブレーションに設定します。ユーザー補助機能サービスがミュートされる場合があります。"</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s。タップしてミュートします。ユーザー補助機能サービスがミュートされる場合があります。"</string> @@ -839,10 +836,8 @@ <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"電源ボタン メニュー"</string> <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"ページ <xliff:g id="ID_1">%1$d</xliff:g>/<xliff:g id="ID_2">%2$d</xliff:g>"</string> <string name="tuner_lock_screen" msgid="2267383813241144544">"ロック画面"</string> - <!-- no translation found for finder_active (7907846989716941952) --> - <skip /> - <!-- no translation found for shutdown_progress (5464239146561542178) --> - <skip /> + <string name="finder_active" msgid="7907846989716941952">"「デバイスを探す」を使うと、電源が OFF の状態でもこのスマートフォンの現在地を確認できます"</string> + <string name="shutdown_progress" msgid="5464239146561542178">"シャットダウン中…"</string> <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"取り扱いに関する手順をご覧ください"</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"取り扱いに関する手順をご覧ください"</string> <string name="high_temp_alarm_title" msgid="8654754369605452169">"デバイスを電源から外します"</string> diff --git a/packages/SystemUI/res/values-ja/tiles_states_strings.xml b/packages/SystemUI/res/values-ja/tiles_states_strings.xml index 31158ca59882..ebadf3b385ce 100644 --- a/packages/SystemUI/res/values-ja/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ja/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"OFF"</item> <item msgid="578444932039713369">"ON"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"使用不可"</item> <item msgid="8707481475312432575">"OFF"</item> diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml index 973af6fa5361..70eeb334556d 100644 --- a/packages/SystemUI/res/values-ka/strings.xml +++ b/packages/SystemUI/res/values-ka/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"დარეკვა"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"ვიბრაცია"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"დადუმება"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"ტრანსლირება"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"ზარის დადუმების გამო ხელმისაწვდომი არაა"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. შეეხეთ დადუმების გასაუქმებლად."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. შეეხეთ ვიბრაციაზე დასაყენებლად. შეიძლება დადუმდეს მარტივი წვდომის სერვისებიც."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. შეეხეთ დასადუმებლად. შეიძლება დადუმდეს მარტივი წვდომის სერვისებიც."</string> diff --git a/packages/SystemUI/res/values-ka/tiles_states_strings.xml b/packages/SystemUI/res/values-ka/tiles_states_strings.xml index 366030a2231f..07a8a76b8c97 100644 --- a/packages/SystemUI/res/values-ka/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ka/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"გამორთულია"</item> <item msgid="578444932039713369">"ჩართულია"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"მიუწვდომელია"</item> <item msgid="8707481475312432575">"გამორთულია"</item> diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml index d5ae0ac6419f..c2525d9aec8f 100644 --- a/packages/SystemUI/res/values-kk/strings.xml +++ b/packages/SystemUI/res/values-kk/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Шылдырлау"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Діріл"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Дыбысын өшіру"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Трансляциялау"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Қолжетімді емес, шылдырлату өшірулі."</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Дыбысын қосу үшін түртіңіз."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Діріл режимін орнату үшін түртіңіз. Арнайы мүмкіндік қызметтерінің дыбысы өшуі мүмкін."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Дыбысын өшіру үшін түртіңіз. Арнайы мүмкіндік қызметтерінің дыбысы өшуі мүмкін."</string> diff --git a/packages/SystemUI/res/values-kk/tiles_states_strings.xml b/packages/SystemUI/res/values-kk/tiles_states_strings.xml index b8089e44a5ce..f5b094855618 100644 --- a/packages/SystemUI/res/values-kk/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-kk/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Өшірулі"</item> <item msgid="578444932039713369">"Қосулы"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Қолжетімсіз"</item> <item msgid="8707481475312432575">"Өшірулі"</item> diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml index 11a284d2d9f4..0d2503359790 100644 --- a/packages/SystemUI/res/values-km/strings.xml +++ b/packages/SystemUI/res/values-km/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"រោទ៍"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"ញ័រ"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"បិទសំឡេង"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"បញ្ជូន"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"មិនអាចប្រើបានទេ ព្រោះសំឡេងរោទ៍ត្រូវបានបិទ"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s។ ប៉ះដើម្បីបើកសំឡេង។"</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s។ ប៉ះដើម្បីកំណត់ឲ្យញ័រ។ សេវាកម្មលទ្ធភាពប្រើប្រាស់អាចនឹងត្រូវបានបិទសំឡេង។"</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s។ ប៉ះដើម្បីបិទសំឡេង។ សេវាកម្មលទ្ធភាពប្រើប្រាស់អាចនឹងត្រូវបានបិទសំឡេង។"</string> diff --git a/packages/SystemUI/res/values-km/tiles_states_strings.xml b/packages/SystemUI/res/values-km/tiles_states_strings.xml index 8c5c8d1ce088..a2031b070a0c 100644 --- a/packages/SystemUI/res/values-km/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-km/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"បិទ"</item> <item msgid="578444932039713369">"បើក"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"មិនមានទេ"</item> <item msgid="8707481475312432575">"បិទ"</item> diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml index a311a8621af3..66b8e72ee2cf 100644 --- a/packages/SystemUI/res/values-kn/strings.xml +++ b/packages/SystemUI/res/values-kn/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"ರಿಂಗ್"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"ವೈಬ್ರೇಟ್"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"ಮ್ಯೂಟ್"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"ಬಿತ್ತರಿಸಿ"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"ರಿಂಗ್ ಮ್ಯೂಟ್ ಆಗಿರುವ ಕಾರಣ ಲಭ್ಯವಿಲ್ಲ"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. ಅನ್ಮ್ಯೂಟ್ ಮಾಡುವುದಕ್ಕಾಗಿ ಟ್ಯಾಪ್ ಮಾಡಿ."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. ಕಂಪನಕ್ಕೆ ಹೊಂದಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ. ಆ್ಯಕ್ಸೆಸಿಬಿಲಿಟಿ ಸೇವೆಗಳನ್ನು ಮ್ಯೂಟ್ ಮಾಡಬಹುದು."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. ಮ್ಯೂಟ್ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ. ಆ್ಯಕ್ಸೆಸಿಬಿಲಿಟಿ ಸೇವೆಗಳನ್ನು ಮ್ಯೂಟ್ ಮಾಡಬಹುದು."</string> diff --git a/packages/SystemUI/res/values-kn/tiles_states_strings.xml b/packages/SystemUI/res/values-kn/tiles_states_strings.xml index 250eb5adc8de..de0fcae601cd 100644 --- a/packages/SystemUI/res/values-kn/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-kn/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"ಆಫ್"</item> <item msgid="578444932039713369">"ಆನ್"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"ಲಭ್ಯವಿಲ್ಲ"</item> <item msgid="8707481475312432575">"ಆಫ್"</item> diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index 1fe5f90fd78f..028c8cf06eb9 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"벨소리"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"진동"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"음소거"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"전송"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"벨소리가 음소거되어 있으므로 사용할 수 없음"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. 탭하여 음소거를 해제하세요."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. 탭하여 진동으로 설정하세요. 접근성 서비스가 음소거될 수 있습니다."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. 탭하여 음소거로 설정하세요. 접근성 서비스가 음소거될 수 있습니다."</string> diff --git a/packages/SystemUI/res/values-ko/tiles_states_strings.xml b/packages/SystemUI/res/values-ko/tiles_states_strings.xml index 7981d285946c..c9b2846ad9e1 100644 --- a/packages/SystemUI/res/values-ko/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ko/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"꺼짐"</item> <item msgid="578444932039713369">"켜짐"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"이용 불가"</item> <item msgid="8707481475312432575">"꺼짐"</item> diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml index 789f7d806548..0f0055561759 100644 --- a/packages/SystemUI/res/values-ky/strings.xml +++ b/packages/SystemUI/res/values-ky/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Шыңгыратуу"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Дирилдөө"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Үнсүз"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Тышкы экранга чыгруу"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Үнсүз режимде жеткиликсиз"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Үнүн чыгаруу үчүн таптап коюңуз."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Дирилдөөгө коюу үчүн таптап коюңуз. Атайын мүмкүнчүлүктөр кызматынын үнүн өчүрүп койсо болот."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Үнүн өчүрүү үчүн таптап коюңуз. Атайын мүмкүнчүлүктөр кызматынын үнүн өчүрүп койсо болот."</string> diff --git a/packages/SystemUI/res/values-ky/tiles_states_strings.xml b/packages/SystemUI/res/values-ky/tiles_states_strings.xml index 0f277f9de292..bc47e5aea38f 100644 --- a/packages/SystemUI/res/values-ky/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ky/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Өчүк"</item> <item msgid="578444932039713369">"Күйүк"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Жеткиликсиз"</item> <item msgid="8707481475312432575">"Өчүк"</item> diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml index 1ca3b2fee9bc..db976555009d 100644 --- a/packages/SystemUI/res/values-lo/strings.xml +++ b/packages/SystemUI/res/values-lo/strings.xml @@ -429,8 +429,7 @@ <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"ເພີ່ມວິດເຈັດເພີ່ມເຕີມ"</string> <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ກົດຄ້າງໄວ້ເພື່ອປັບແຕ່ງວິດເຈັດ"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ປັບແຕ່ງວິດເຈັດ"</string> - <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) --> - <skip /> + <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"ໄອຄອນແອັບສຳລັບວິດເຈັດທີ່ຖືກປິດການນຳໃຊ້"</string> <string name="edit_widget" msgid="9030848101135393954">"ແກ້ໄຂວິດເຈັດ"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"ລຶບອອກ"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ເພີ່ມວິດເຈັດ"</string> @@ -837,10 +836,8 @@ <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"ເມນູເປີດປິດ"</string> <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_1">%1$d</xliff:g> ຈາກທັງໝົດ <xliff:g id="ID_2">%2$d</xliff:g>"</string> <string name="tuner_lock_screen" msgid="2267383813241144544">"ໜ້າຈໍລັອກ"</string> - <!-- no translation found for finder_active (7907846989716941952) --> - <skip /> - <!-- no translation found for shutdown_progress (5464239146561542178) --> - <skip /> + <string name="finder_active" msgid="7907846989716941952">"ທ່ານສາມາດຊອກຫາສະຖານທີ່ຂອງໂທລະສັບເຄື່ອງນີ້ໄດ້ດ້ວຍແອັບຊອກຫາອຸປະກອນຂອງຂ້ອຍເຖິງແມ່ນວ່າຈະປິດເຄື່ອງຢູ່ກໍຕາມ"</string> + <string name="shutdown_progress" msgid="5464239146561542178">"ກຳລັງປິດເຄື່ອງ…"</string> <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"ເບິ່ງຂັ້ນຕອນການເບິ່ງແຍງ"</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"ເບິ່ງຂັ້ນຕອນການເບິ່ງແຍງ"</string> <string name="high_temp_alarm_title" msgid="8654754369605452169">"ຖອດອຸປະກອນຂອງທ່ານອອກ"</string> diff --git a/packages/SystemUI/res/values-lo/tiles_states_strings.xml b/packages/SystemUI/res/values-lo/tiles_states_strings.xml index d54cf4d24181..75958972cdce 100644 --- a/packages/SystemUI/res/values-lo/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-lo/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"ປິດ"</item> <item msgid="578444932039713369">"ເປີດ"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"ບໍ່ສາມາດໃຊ້ໄດ້"</item> <item msgid="8707481475312432575">"ປິດ"</item> diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml index eac5ee48ead1..9eaab8f6a6f0 100644 --- a/packages/SystemUI/res/values-lt/strings.xml +++ b/packages/SystemUI/res/values-lt/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Skambinti"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibruoti"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Nutildyti"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Perdavimas"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Nepasiekiama, nes skambutis nutildytas"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Palieskite, kad įjungtumėte garsą."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Palieskite, kad nustatytumėte vibravimą. Gali būti nutildytos pritaikymo neįgaliesiems paslaugos."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Palieskite, kad nutildytumėte. Gali būti nutildytos pritaikymo neįgaliesiems paslaugos."</string> diff --git a/packages/SystemUI/res/values-lt/tiles_states_strings.xml b/packages/SystemUI/res/values-lt/tiles_states_strings.xml index e66f590364dd..94343bac9ff8 100644 --- a/packages/SystemUI/res/values-lt/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-lt/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Išjungta"</item> <item msgid="578444932039713369">"Įjungta"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Nepasiekiama"</item> <item msgid="8707481475312432575">"Išjungta"</item> diff --git a/packages/SystemUI/res/values-lv/tiles_states_strings.xml b/packages/SystemUI/res/values-lv/tiles_states_strings.xml index d32efec81a99..d8b24676ea27 100644 --- a/packages/SystemUI/res/values-lv/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-lv/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Izslēgta"</item> <item msgid="578444932039713369">"Ieslēgta"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Nav pieejama"</item> <item msgid="8707481475312432575">"Izslēgta"</item> diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml index b244fe9343bb..6ea4d1f1d9ab 100644 --- a/packages/SystemUI/res/values-mk/strings.xml +++ b/packages/SystemUI/res/values-mk/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Ѕвони"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Вибрации"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Исклучи звук"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Емитување"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Недостапно бидејќи ѕвонењето е исклучено"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Допрете за да вклучите звук."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Допрете за да поставите на вибрации. Можеби ќе се исклучи звукот на услугите за достапност."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Допрете за да исклучите звук. Можеби ќе се исклучи звукот на услугите за достапност."</string> diff --git a/packages/SystemUI/res/values-mk/tiles_states_strings.xml b/packages/SystemUI/res/values-mk/tiles_states_strings.xml index 0a42d7cbe2df..8b0faf7d966e 100644 --- a/packages/SystemUI/res/values-mk/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-mk/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Исклучено"</item> <item msgid="578444932039713369">"Вклучено"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Недостапно"</item> <item msgid="8707481475312432575">"Исклучено"</item> diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml index 8678b663a089..82d257a473b2 100644 --- a/packages/SystemUI/res/values-ml/strings.xml +++ b/packages/SystemUI/res/values-ml/strings.xml @@ -429,8 +429,7 @@ <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"കൂടുതൽ വിജറ്റുകൾ ചേർക്കുക"</string> <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"വിജറ്റുകൾ ഇഷ്ടാനുസൃതമാക്കാൻ ദീർഘനേരം അമർത്തുക"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"വിജറ്റുകൾ ഇഷ്ടാനുസൃതമാക്കുക"</string> - <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) --> - <skip /> + <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"പ്രവർത്തനരഹിതമാക്കിയ വിജറ്റിനുള്ള ആപ്പ് ഐക്കൺ"</string> <string name="edit_widget" msgid="9030848101135393954">"വിജറ്റ് എഡിറ്റ് ചെയ്യുക"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"നീക്കം ചെയ്യുക"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"വിജറ്റ് ചേർക്കുക"</string> @@ -837,10 +836,8 @@ <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"പവർ മെനു"</string> <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"പേജ് <xliff:g id="ID_1">%1$d</xliff:g> / <xliff:g id="ID_2">%2$d</xliff:g>"</string> <string name="tuner_lock_screen" msgid="2267383813241144544">"ലോക്ക് സ്ക്രീൻ"</string> - <!-- no translation found for finder_active (7907846989716941952) --> - <skip /> - <!-- no translation found for shutdown_progress (5464239146561542178) --> - <skip /> + <string name="finder_active" msgid="7907846989716941952">"ഓഫായിരിക്കുമ്പോഴും Find My Device ഉപയോഗിച്ച് നിങ്ങൾക്ക് ഈ ഫോൺ കണ്ടെത്താനാകും"</string> + <string name="shutdown_progress" msgid="5464239146561542178">"ഷട്ട്ഡൗൺ ചെയ്യുന്നു…"</string> <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"പരിപാലന നിർദ്ദേശങ്ങൾ കാണുക"</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"പരിപാലന നിർദ്ദേശങ്ങൾ കാണുക"</string> <string name="high_temp_alarm_title" msgid="8654754369605452169">"ഉപകരണം അൺപ്ലഗ് ചെയ്യുക"</string> diff --git a/packages/SystemUI/res/values-ml/tiles_states_strings.xml b/packages/SystemUI/res/values-ml/tiles_states_strings.xml index 62bac5cd1212..529d0def3e27 100644 --- a/packages/SystemUI/res/values-ml/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ml/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"ഓഫാണ്"</item> <item msgid="578444932039713369">"ഓണാണ്"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"ലഭ്യമല്ല"</item> <item msgid="8707481475312432575">"ഓഫാണ്"</item> diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml index 8aafee3341de..aad6a13c43d7 100644 --- a/packages/SystemUI/res/values-mn/strings.xml +++ b/packages/SystemUI/res/values-mn/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Хонх дуугаргах"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Чичиргэх"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Хаах"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Дамжуулах"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Хонхны дууг хаасан тул боломжгүй"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Дууг нь нээхийн тулд товшино уу."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Чичиргээнд тохируулахын тулд товшино уу. Хүртээмжийн үйлчилгээний дууг хаасан."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Дууг нь хаахын тулд товшино уу. Хүртээмжийн үйлчилгээний дууг хаасан."</string> diff --git a/packages/SystemUI/res/values-mn/tiles_states_strings.xml b/packages/SystemUI/res/values-mn/tiles_states_strings.xml index 33f359672477..0db122904755 100644 --- a/packages/SystemUI/res/values-mn/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-mn/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Унтраалттай"</item> <item msgid="578444932039713369">"Асаалттай"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Боломжгүй"</item> <item msgid="8707481475312432575">"Унтраалттай"</item> diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml index e781b45803ef..d7acf5f5fea2 100644 --- a/packages/SystemUI/res/values-mr/strings.xml +++ b/packages/SystemUI/res/values-mr/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"रिंग करा"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"व्हायब्रेट"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"म्यूट करा"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"कास्ट करा"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"रिंग म्यूट केल्यामुळे उपलब्ध नाही"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. अनम्यूट करण्यासाठी टॅप करा."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. व्हायब्रेट सेट करण्यासाठी टॅप करा. प्रवेशयोग्यता सेवा म्यूट केल्या जाऊ शकतात."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. म्यूट करण्यासाठी टॅप करा. प्रवेशक्षमता सेवा म्यूट केल्या जाऊ शकतात."</string> diff --git a/packages/SystemUI/res/values-mr/tiles_states_strings.xml b/packages/SystemUI/res/values-mr/tiles_states_strings.xml index 24d3b47af961..b70a5cc671a7 100644 --- a/packages/SystemUI/res/values-mr/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-mr/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"बंद आहे"</item> <item msgid="578444932039713369">"सुरू आहे"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"उपलब्ध नाही"</item> <item msgid="8707481475312432575">"बंद आहे"</item> diff --git a/packages/SystemUI/res/values-ms/tiles_states_strings.xml b/packages/SystemUI/res/values-ms/tiles_states_strings.xml index 07a8426feeb2..b72a3756ccaa 100644 --- a/packages/SystemUI/res/values-ms/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ms/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Mati"</item> <item msgid="578444932039713369">"Hidup"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Tidak tersedia"</item> <item msgid="8707481475312432575">"Mati"</item> diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml index c408118e48ce..c6d46bccf254 100644 --- a/packages/SystemUI/res/values-my/strings.xml +++ b/packages/SystemUI/res/values-my/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"အသံမြည်သည်"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"တုန်ခါသည်"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"အသံတိတ်သည်"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"ကာစ်လုပ်ရန်"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"ဖုန်းမြည်သံပိတ်ထားသဖြင့် မရနိုင်ပါ"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s။ အသံပြန်ဖွင့်ရန် တို့ပါ။"</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s။ တုန်ခါမှုကို သတ်မှတ်ရန် တို့ပါ။ အများသုံးနိုင်မှု ဝန်ဆောင်မှုများကို အသံပိတ်ထားနိုင်ပါသည်။"</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s။ အသံပိတ်ရန် တို့ပါ။ အများသုံးနိုင်မှု ဝန်ဆောင်မှုများကို အသံပိတ်ထားနိုင်ပါသည်။"</string> diff --git a/packages/SystemUI/res/values-my/tiles_states_strings.xml b/packages/SystemUI/res/values-my/tiles_states_strings.xml index fd375d4f3eb1..d223dc9d0bed 100644 --- a/packages/SystemUI/res/values-my/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-my/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"ပိတ်"</item> <item msgid="578444932039713369">"ဖွင့်"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"မရနိုင်ပါ"</item> <item msgid="8707481475312432575">"ပိတ်"</item> diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index 2ce016ac586f..a2b97eee2fbe 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Ring"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrer"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Ignorer"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Cast"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Utilgjengelig fordi ringelyden er kuttet"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Trykk for å slå på lyden."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Trykk for å angi vibrasjon. Lyden kan bli slått av for tilgjengelighetstjenestene."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Trykk for å slå av lyden. Lyden kan bli slått av for tilgjengelighetstjenestene."</string> diff --git a/packages/SystemUI/res/values-nb/tiles_states_strings.xml b/packages/SystemUI/res/values-nb/tiles_states_strings.xml index e4a811941b2a..2ed00960fdfd 100644 --- a/packages/SystemUI/res/values-nb/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-nb/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Av"</item> <item msgid="578444932039713369">"På"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Utilgjengelig"</item> <item msgid="8707481475312432575">"Av"</item> diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml index 0096f480f67e..1388a85a6f15 100644 --- a/packages/SystemUI/res/values-ne/strings.xml +++ b/packages/SystemUI/res/values-ne/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"घन्टी"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"कम्पन"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"म्युट गर्नुहोस्"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"कास्ट गर्नुहोस्"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"डिभाइस म्युट गरिएकाले यो सुविधा उपलब्ध छैन"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s। अनम्यूट गर्नाका लागि ट्याप गर्नुहोस्।"</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s। कम्पनमा सेट गर्नाका लागि ट्याप गर्नुहोस्। पहुँच सम्बन्धी सेवाहरू म्यूट हुन सक्छन्।"</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s। म्यूट गर्नाका लागि ट्याप गर्नुहोस्। पहुँच सम्बन्धी सेवाहरू म्यूट हुन सक्छन्।"</string> diff --git a/packages/SystemUI/res/values-ne/tiles_states_strings.xml b/packages/SystemUI/res/values-ne/tiles_states_strings.xml index 5cf91e533494..40159fb17279 100644 --- a/packages/SystemUI/res/values-ne/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ne/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"अफ छ"</item> <item msgid="578444932039713369">"अन छ"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"उपलब्ध छैन"</item> <item msgid="8707481475312432575">"अफ छ"</item> diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml index 723b9096cfcf..09156cf074e6 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Bellen"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Trillen"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Geluid staat uit"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Casten"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Niet beschikbaar, belgeluid staat uit"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Tik om dempen op te heffen."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Tik om in te stellen op trillen. Het geluid van toegankelijkheidsservices kan hierdoor uitgaan."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Tik om te dempen. Het geluid van toegankelijkheidsservices kan hierdoor uitgaan."</string> diff --git a/packages/SystemUI/res/values-nl/tiles_states_strings.xml b/packages/SystemUI/res/values-nl/tiles_states_strings.xml index 592ecf5b69d6..60e35da7611b 100644 --- a/packages/SystemUI/res/values-nl/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-nl/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Uit"</item> <item msgid="578444932039713369">"Aan"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Niet beschikbaar"</item> <item msgid="8707481475312432575">"Uit"</item> diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml index 612e012a2546..b49c2979ac43 100644 --- a/packages/SystemUI/res/values-or/strings.xml +++ b/packages/SystemUI/res/values-or/strings.xml @@ -287,7 +287,7 @@ <string name="quick_settings_media_device_label" msgid="8034019242363789941">"ମିଡିଆ ଡିଭାଇସ୍"</string> <string name="quick_settings_user_title" msgid="8673045967216204537">"ୟୁଜର"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"ୱାଇ-ଫାଇ"</string> - <string name="quick_settings_internet_label" msgid="6603068555872455463">"ଇଣ୍ଟରନେଟ"</string> + <string name="quick_settings_internet_label" msgid="6603068555872455463">"ଇଣ୍ଟର୍ନେଟ"</string> <string name="quick_settings_networks_available" msgid="1875138606855420438">"ନେଟୱାର୍କଗୁଡ଼ିକ ଉପଲବ୍ଧ"</string> <string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"ନେଟୱାର୍କ ଉପଲବ୍ଧ ନାହିଁ"</string> <string name="quick_settings_wifi_detail_empty_text" msgid="483130889414601732">"କୌଣସି ୱାଇ-ଫାଇ ନେଟ୍ୱର୍କ ଉପଲବ୍ଧ ନାହିଁ"</string> @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"ରିଙ୍ଗ"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"ଭାଇବ୍ରେଟ୍"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"ମ୍ୟୁଟ"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"କାଷ୍ଟ କରନ୍ତୁ"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"ରିଂକୁ ମ୍ୟୁଟ କରାଯାଇଥିବା ଯୋଗୁଁ ଉପଲବ୍ଧ ନାହିଁ"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s। ଅନମ୍ୟୁଟ୍ କରିବା ପାଇଁ ଟାପ୍ କରନ୍ତୁ।"</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s। ଭାଇବ୍ରେଟ୍ ସେଟ୍ କରିବାକୁ ଟାପ୍ କରନ୍ତୁ। ଆକ୍ସେସିବିଲିଟୀ ସର୍ଭିସ୍ ମ୍ୟୁଟ୍ କରାଯାଇପାରେ।"</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s। ମ୍ୟୁଟ୍ କରିବାକୁ ଟାପ୍ କରନ୍ତୁ। ଆକ୍ସେସିବିଲିଟୀ ସର୍ଭିସ୍ ମ୍ୟୁଟ୍ କରାଯାଇପାରେ।"</string> diff --git a/packages/SystemUI/res/values-or/tiles_states_strings.xml b/packages/SystemUI/res/values-or/tiles_states_strings.xml index d362c65fa971..43bddbf9f49b 100644 --- a/packages/SystemUI/res/values-or/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-or/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"ବନ୍ଦ ଅଛି"</item> <item msgid="578444932039713369">"ଚାଲୁ ଅଛି"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"ଉପଲବ୍ଧ ନାହିଁ"</item> <item msgid="8707481475312432575">"ବନ୍ଦ ଅଛି"</item> diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml index c02641329304..cd55198317e8 100644 --- a/packages/SystemUI/res/values-pa/strings.xml +++ b/packages/SystemUI/res/values-pa/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"ਘੰਟੀ"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"ਥਰਥਰਾਹਟ"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"ਮਿਊਟ"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"ਕਾਸਟ ਕਰੋ"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"ਉਪਲਬਧ ਨਹੀਂ ਹੈ ਕਿਉਂਕਿ ਘੰਟੀ ਮਿਊਟ ਕੀਤੀ ਹੋਈ ਹੈ"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s। ਅਣਮਿਊਟ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ।"</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s। ਥਰਥਰਾਹਟ ਸੈੱਟ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ। ਪਹੁੰਚਯੋਗਤਾ ਸੇਵਾਵਾਂ ਮਿਊਟ ਹੋ ਸਕਦੀਆਂ ਹਨ।"</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s। ਮਿਊਟ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ। ਪਹੁੰਚਯੋਗਤਾ ਸੇਵਾਵਾਂ ਮਿਊਟ ਹੋ ਸਕਦੀਆਂ ਹਨ।"</string> diff --git a/packages/SystemUI/res/values-pa/tiles_states_strings.xml b/packages/SystemUI/res/values-pa/tiles_states_strings.xml index f249afb72b6d..5f0ca172dca9 100644 --- a/packages/SystemUI/res/values-pa/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-pa/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"ਬੰਦ ਹੈ"</item> <item msgid="578444932039713369">"ਚਾਲੂ ਹੈ"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"ਅਣਉਪਲਬਧ ਹੈ"</item> <item msgid="8707481475312432575">"ਬੰਦ ਹੈ"</item> diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index 44639b990e48..76547ed5266c 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Dzwonek"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Wibracje"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Wyciszenie"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Przesyłanie"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Niedostępne, bo dzwonek jest wyciszony"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Kliknij, by wyłączyć wyciszenie."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Kliknij, by włączyć wibracje. Ułatwienia dostępu mogą być wyciszone."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Kliknij, by wyciszyć. Ułatwienia dostępu mogą być wyciszone."</string> diff --git a/packages/SystemUI/res/values-pl/tiles_states_strings.xml b/packages/SystemUI/res/values-pl/tiles_states_strings.xml index ea6ad58c825a..5e3a1189c940 100644 --- a/packages/SystemUI/res/values-pl/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-pl/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Wyłączony"</item> <item msgid="578444932039713369">"Włączony"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Niedostępne"</item> <item msgid="8707481475312432575">"Wyłączone"</item> diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml index 639a488c5226..598ef78ae63b 100644 --- a/packages/SystemUI/res/values-pt-rBR/strings.xml +++ b/packages/SystemUI/res/values-pt-rBR/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Tocar"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrar"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Desativar som"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Transmitir"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Indisponível com o toque foi silenciado"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Toque para ativar o som."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Toque para configurar para vibrar. É possível que os serviços de acessibilidade sejam silenciados."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Toque para silenciar. É possível que os serviços de acessibilidade sejam silenciados."</string> diff --git a/packages/SystemUI/res/values-pt-rBR/tiles_states_strings.xml b/packages/SystemUI/res/values-pt-rBR/tiles_states_strings.xml index 28c07f40b6b9..d4fd83803ba7 100644 --- a/packages/SystemUI/res/values-pt-rBR/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-pt-rBR/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Desativada"</item> <item msgid="578444932039713369">"Ativada"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Indisponível"</item> <item msgid="8707481475312432575">"Desativado"</item> diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index f34dea1dfbf2..7e5a7368faaf 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -429,8 +429,7 @@ <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Adicionar mais widgets"</string> <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantenha premido para personalizar os widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizar widgets"</string> - <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) --> - <skip /> + <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ícone da app do widget desativado"</string> <string name="edit_widget" msgid="9030848101135393954">"Editar widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Remover"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Adicionar widget"</string> @@ -575,10 +574,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Toque"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrar"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Desativar som"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Transmitir"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Indisponível porque o toque está desat."</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Toque para reativar o som."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Toque para ativar a vibração. Os serviços de acessibilidade podem ser silenciados."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Toque para desativar o som. Os serviços de acessibilidade podem ser silenciados."</string> @@ -839,10 +836,8 @@ <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Menu ligar/desligar"</string> <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Página <xliff:g id="ID_1">%1$d</xliff:g> de <xliff:g id="ID_2">%2$d</xliff:g>"</string> <string name="tuner_lock_screen" msgid="2267383813241144544">"Ecrã de bloqueio"</string> - <!-- no translation found for finder_active (7907846989716941952) --> - <skip /> - <!-- no translation found for shutdown_progress (5464239146561542178) --> - <skip /> + <string name="finder_active" msgid="7907846989716941952">"Pode localizar este telemóvel com o serviço Localizar o meu dispositivo mesmo quando está desligado"</string> + <string name="shutdown_progress" msgid="5464239146561542178">"A encerrar…"</string> <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Veja os passos de manutenção"</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Veja os passos de manutenção"</string> <string name="high_temp_alarm_title" msgid="8654754369605452169">"Desligue o dispositivo"</string> diff --git a/packages/SystemUI/res/values-pt-rPT/tiles_states_strings.xml b/packages/SystemUI/res/values-pt-rPT/tiles_states_strings.xml index b58b8488a3e4..e94b1ec6b0fd 100644 --- a/packages/SystemUI/res/values-pt-rPT/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Desligado"</item> <item msgid="578444932039713369">"Ligado"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Indisponível"</item> <item msgid="8707481475312432575">"Desligado"</item> diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml index 639a488c5226..598ef78ae63b 100644 --- a/packages/SystemUI/res/values-pt/strings.xml +++ b/packages/SystemUI/res/values-pt/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Tocar"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrar"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Desativar som"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Transmitir"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Indisponível com o toque foi silenciado"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Toque para ativar o som."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Toque para configurar para vibrar. É possível que os serviços de acessibilidade sejam silenciados."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Toque para silenciar. É possível que os serviços de acessibilidade sejam silenciados."</string> diff --git a/packages/SystemUI/res/values-pt/tiles_states_strings.xml b/packages/SystemUI/res/values-pt/tiles_states_strings.xml index 28c07f40b6b9..d4fd83803ba7 100644 --- a/packages/SystemUI/res/values-pt/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-pt/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Desativada"</item> <item msgid="578444932039713369">"Ativada"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Indisponível"</item> <item msgid="8707481475312432575">"Desativado"</item> diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index 7d8c2a31751c..026e22bd6328 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Sonerie"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrații"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Blochează"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Proiectează"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Indisponibil; soneria este dezactivată"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Atinge pentru a activa sunetul."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Atinge pentru a seta vibrarea. Sunetul se poate dezactiva pentru serviciile de accesibilitate."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Atinge pentru a dezactiva sunetul. Sunetul se poate dezactiva pentru serviciile de accesibilitate."</string> diff --git a/packages/SystemUI/res/values-ro/tiles_states_strings.xml b/packages/SystemUI/res/values-ro/tiles_states_strings.xml index 5a5eb9f6788c..75565f942ac4 100644 --- a/packages/SystemUI/res/values-ro/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ro/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Dezactivată"</item> <item msgid="578444932039713369">"Activată"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Indisponibilă"</item> <item msgid="8707481475312432575">"Dezactivată"</item> diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index 37b7ae5f7b3d..1df112ba8493 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Со звуком"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Вибрация"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Без звука"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Трансляция"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Недоступно, когда отключен звук вызовов"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Нажмите, чтобы включить звук."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Нажмите, чтобы включить вибрацию. Специальные возможности могут прекратить работу."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Нажмите, чтобы выключить звук. Специальные возможности могут прекратить работу."</string> diff --git a/packages/SystemUI/res/values-ru/tiles_states_strings.xml b/packages/SystemUI/res/values-ru/tiles_states_strings.xml index cd140791bbd5..3099e008c93a 100644 --- a/packages/SystemUI/res/values-ru/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ru/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Откл."</item> <item msgid="578444932039713369">"Вкл."</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Функция недоступна"</item> <item msgid="8707481475312432575">"Откл."</item> diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml index c851f8583f0d..f4b7d1eb1254 100644 --- a/packages/SystemUI/res/values-si/strings.xml +++ b/packages/SystemUI/res/values-si/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"නාද කරන්න"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"කම්පනය කරන්න"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"නිහඬ කරන්න"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"විකාශය"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"නාදය නිහඬ කර ඇති නිසා නොලැබේ"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. නිහඬ කිරීම ඉවත් කිරීමට තට්ටු කරන්න."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. කම්පනය කිරීමට තට්ටු කරන්න. ප්රවේශ්යතා සේවා නිහඬ කළ හැකිය."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. නිහඬ කිරීමට තට්ටු කරන්න. ප්රවේශ්යතා සේවා නිහඬ කළ හැකිය."</string> diff --git a/packages/SystemUI/res/values-si/tiles_states_strings.xml b/packages/SystemUI/res/values-si/tiles_states_strings.xml index fcd768bfc4e3..48e8cc4af507 100644 --- a/packages/SystemUI/res/values-si/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-si/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"අක්රියයි"</item> <item msgid="578444932039713369">"සක්රියයි"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"නොමැත"</item> <item msgid="8707481475312432575">"අක්රියයි"</item> diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index d25e3687b589..0f579da44ed6 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Prezvoniť"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrovať"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Vypnúť zvuk"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Prenos"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Nedostupné, pretože je vypnuté zvonenie"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Klepnutím zapnite zvuk."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Klepnutím aktivujte režim vibrovania. Služby dostupnosti je možné stlmiť."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Klepnutím vypnite zvuk. Služby dostupnosti je možné stlmiť."</string> diff --git a/packages/SystemUI/res/values-sk/tiles_states_strings.xml b/packages/SystemUI/res/values-sk/tiles_states_strings.xml index 660f85d365e2..fdfcd27db263 100644 --- a/packages/SystemUI/res/values-sk/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-sk/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Vypnuté"</item> <item msgid="578444932039713369">"Zapnuté"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Nie je k dispozícii"</item> <item msgid="8707481475312432575">"Vypnuté"</item> diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml index eb56a44aa313..7acaa54cbbad 100644 --- a/packages/SystemUI/res/values-sl/strings.xml +++ b/packages/SystemUI/res/values-sl/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Zvonjenje"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibriranje"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Utišano"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Predvajanje"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Ni na voljo, ker je zvonjenje izklopljeno"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Dotaknite se, če želite vklopiti zvok."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Dotaknite se, če želite nastaviti vibriranje. V storitvah za dostopnost bo morda izklopljen zvok."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Dotaknite se, če želite izklopiti zvok. V storitvah za dostopnost bo morda izklopljen zvok."</string> diff --git a/packages/SystemUI/res/values-sl/tiles_states_strings.xml b/packages/SystemUI/res/values-sl/tiles_states_strings.xml index d7e62caac28d..3804d639bb52 100644 --- a/packages/SystemUI/res/values-sl/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-sl/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Izklopljeno"</item> <item msgid="578444932039713369">"Vklopljeno"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Ni na voljo"</item> <item msgid="8707481475312432575">"Izklopljeno"</item> diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml index eb8fa4372d2e..21a974faf29e 100644 --- a/packages/SystemUI/res/values-sq/strings.xml +++ b/packages/SystemUI/res/values-sq/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Bjeri ziles"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Dridhje"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Pa zë"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Transmeto"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Nuk ofrohet; ziles i është hequr zëri"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Trokit për të aktivizuar."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Trokit për ta caktuar te dridhja. Shërbimet e qasshmërisë mund të çaktivizohen."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Trokit për të çaktivizuar. Shërbimet e qasshmërisë mund të çaktivizohen."</string> diff --git a/packages/SystemUI/res/values-sq/tiles_states_strings.xml b/packages/SystemUI/res/values-sq/tiles_states_strings.xml index b8e1355dfb25..6318700bf6f2 100644 --- a/packages/SystemUI/res/values-sq/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-sq/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Joaktive"</item> <item msgid="578444932039713369">"Aktiv"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Nuk ofrohet"</item> <item msgid="8707481475312432575">"Joaktive"</item> diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml index 0644a8095675..2f52f90b3622 100644 --- a/packages/SystemUI/res/values-sr/strings.xml +++ b/packages/SystemUI/res/values-sr/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Активирај звоно"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Вибрирај"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Искључи звук"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Пребацивање"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Недоступно јер је звук искључен"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Додирните да бисте укључили звук."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Додирните да бисте подесили на вибрацију. Звук услуга приступачности ће можда бити искључен."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Додирните да бисте искључили звук. Звук услуга приступачности ће можда бити искључен."</string> diff --git a/packages/SystemUI/res/values-sr/tiles_states_strings.xml b/packages/SystemUI/res/values-sr/tiles_states_strings.xml index c959bfb0eb73..e4cf0b6cd2c0 100644 --- a/packages/SystemUI/res/values-sr/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-sr/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Искључено"</item> <item msgid="578444932039713369">"Укључено"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Недоступно"</item> <item msgid="8707481475312432575">"Искључено"</item> diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index 208891f4fbfa..20bc7e70a0da 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Ringsignal"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibration"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Dölj"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Casta"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Otillgängligt eftersom ringljudet är av"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Tryck här om du vill slå på ljudet."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Tryck här om du vill sätta på vibrationen. Tillgänglighetstjänster kanske inaktiveras."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Tryck här om du vill stänga av ljudet. Tillgänglighetstjänsterna kanske inaktiveras."</string> diff --git a/packages/SystemUI/res/values-sv/tiles_states_strings.xml b/packages/SystemUI/res/values-sv/tiles_states_strings.xml index 28717dfa5229..8981ac775e50 100644 --- a/packages/SystemUI/res/values-sv/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-sv/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Av"</item> <item msgid="578444932039713369">"På"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Inte tillgängligt"</item> <item msgid="8707481475312432575">"Av"</item> diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index 404cb487e445..fa0e7b5eba05 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Piga"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Kutetema"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Zima sauti"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Tuma"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Halipatikani kwa sababu sauti imezimwa"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Gusa ili urejeshe."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Gusa ili uweke mtetemo. Huenda ikakomesha huduma za zana za walio na matatizo ya kuona au kusikia."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Gusa ili ukomeshe. Huenda ikakomesha huduma za zana za walio na matatizo ya kuona au kusikia."</string> diff --git a/packages/SystemUI/res/values-sw/tiles_states_strings.xml b/packages/SystemUI/res/values-sw/tiles_states_strings.xml index 2fe40603bc7d..08a1f149264c 100644 --- a/packages/SystemUI/res/values-sw/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-sw/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Kimezimwa"</item> <item msgid="578444932039713369">"Kimewashwa"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Hakipatikani"</item> <item msgid="8707481475312432575">"Kimezimwa"</item> diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml index 03dd362e2b18..0f360e6bc17b 100644 --- a/packages/SystemUI/res/values-ta/strings.xml +++ b/packages/SystemUI/res/values-ta/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"ஒலி"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"அதிர்வு"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"அமைதி"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"அலைபரப்பு"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"\'ரிங்\' மியூட்டில் உள்ளதால் கிடைக்கவில்லை"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. ஒலி இயக்க, தட்டவும்."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. அதிர்விற்கு அமைக்க, தட்டவும். அணுகல்தன்மை சேவைகள் ஒலியடக்கப்படக்கூடும்."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. ஒலியடக்க, தட்டவும். அணுகல்தன்மை சேவைகள் ஒலியடக்கப்படக்கூடும்."</string> diff --git a/packages/SystemUI/res/values-ta/tiles_states_strings.xml b/packages/SystemUI/res/values-ta/tiles_states_strings.xml index 5bcc6c761e1c..741d6dea191f 100644 --- a/packages/SystemUI/res/values-ta/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ta/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"முடக்கப்பட்டுள்ளது"</item> <item msgid="578444932039713369">"இயக்கப்பட்டுள்ளது"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"கிடைக்கவில்லை"</item> <item msgid="8707481475312432575">"முடக்கப்பட்டுள்ளது"</item> diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml index 08067c93bead..4e8e2d0e4ca8 100644 --- a/packages/SystemUI/res/values-te/strings.xml +++ b/packages/SystemUI/res/values-te/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"రింగ్"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"వైబ్రేట్"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"మ్యూట్"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"ప్రసారం చేయండి"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"వాల్యూమ్ మ్యూట్ అయినందున అందుబాటులో లేదు"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. అన్మ్యూట్ చేయడానికి నొక్కండి."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. వైబ్రేషన్కు సెట్ చేయడానికి నొక్కండి. యాక్సెస్ సామర్థ్య సేవలు మ్యూట్ చేయబడవచ్చు."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. మ్యూట్ చేయడానికి నొక్కండి. యాక్సెస్ సామర్థ్య సేవలు మ్యూట్ చేయబడవచ్చు."</string> diff --git a/packages/SystemUI/res/values-te/tiles_states_strings.xml b/packages/SystemUI/res/values-te/tiles_states_strings.xml index 9d2b407ccd10..6ff293479860 100644 --- a/packages/SystemUI/res/values-te/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-te/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"ఆఫ్లో ఉంది"</item> <item msgid="578444932039713369">"ఆన్లో ఉంది"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"అందుబాటులో లేదు"</item> <item msgid="8707481475312432575">"ఆఫ్లో ఉంది"</item> diff --git a/packages/SystemUI/res/values-th/tiles_states_strings.xml b/packages/SystemUI/res/values-th/tiles_states_strings.xml index 69449a72196e..d961385ba72e 100644 --- a/packages/SystemUI/res/values-th/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-th/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"ปิด"</item> <item msgid="578444932039713369">"เปิด"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"ไม่พร้อมใช้งาน"</item> <item msgid="8707481475312432575">"ปิด"</item> diff --git a/packages/SystemUI/res/values-tl/tiles_states_strings.xml b/packages/SystemUI/res/values-tl/tiles_states_strings.xml index 689c2a26ca61..a12c010f766a 100644 --- a/packages/SystemUI/res/values-tl/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-tl/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Naka-off"</item> <item msgid="578444932039713369">"Naka-on"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Hindi available"</item> <item msgid="8707481475312432575">"Naka-off"</item> diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index 495000ba0318..2cdc6e7e1296 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Zili çaldır"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Titreşim"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Sesi kapat"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Yayınla"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Zil sesi kapatıldığı için kullanılamıyor"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Sesi açmak için dokunun."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Titreşime ayarlamak için dokunun. Erişilebilirlik hizmetlerinin sesi kapatılabilir."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Sesi kapatmak için dokunun. Erişilebilirlik hizmetlerinin sesi kapatılabilir."</string> diff --git a/packages/SystemUI/res/values-tr/tiles_states_strings.xml b/packages/SystemUI/res/values-tr/tiles_states_strings.xml index a8c7f784920d..c6a8aecf0da1 100644 --- a/packages/SystemUI/res/values-tr/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-tr/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Kapalı"</item> <item msgid="578444932039713369">"Açık"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Kullanılamıyor"</item> <item msgid="8707481475312432575">"Kapalı"</item> diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index 67d5ffc3373a..c89ae75a5ae2 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Дзвінок"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Вібросигнал"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Без звуку"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Трансляція"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Недоступно: звук дзвінків вимкнено"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Торкніться, щоб увімкнути звук."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Торкніться, щоб налаштувати вібросигнал. Спеціальні можливості може бути вимкнено."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Торкніться, щоб вимкнути звук. Спеціальні можливості може бути вимкнено."</string> diff --git a/packages/SystemUI/res/values-uk/tiles_states_strings.xml b/packages/SystemUI/res/values-uk/tiles_states_strings.xml index 4062f1be4c08..a8e1ab8f3f99 100644 --- a/packages/SystemUI/res/values-uk/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-uk/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Вимкнено"</item> <item msgid="578444932039713369">"Увімкнено"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Недоступно"</item> <item msgid="8707481475312432575">"Вимкнено"</item> diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml index 7414ac62c4a8..3237f32f5347 100644 --- a/packages/SystemUI/res/values-ur/strings.xml +++ b/packages/SystemUI/res/values-ur/strings.xml @@ -429,8 +429,7 @@ <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"مزید ویجٹس شامل کریں"</string> <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ویجٹس کو حسب ضرورت بنانے کے لیے لانگ پریس کریں"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ویجیٹس کو حسب ضرورت بنائیں"</string> - <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) --> - <skip /> + <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"غیر فعال ویجیٹ کے لئے ایپ آئیکن"</string> <string name="edit_widget" msgid="9030848101135393954">"ویجیٹ میں ترمیم کریں"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"ہٹائیں"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ویجیٹ شامل کریں"</string> @@ -575,10 +574,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"رِنگ کریں"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"وائبریٹ"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"خاموش کریں"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"کاسٹ"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"دستیاب نہیں ہے کیونکہ رنگ خاموش ہے"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s۔ آواز چالو کرنے کیلئے تھپتھپائیں۔"</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s۔ ارتعاش پر سیٹ کرنے کیلئے تھپتھپائیں۔ ایکسیسبیلٹی سروسز شاید خاموش ہوں۔"</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s۔ خاموش کرنے کیلئے تھپتھپائیں۔ ایکسیسبیلٹی سروسز شاید خاموش ہوں۔"</string> @@ -839,10 +836,8 @@ <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"پاور مینیو"</string> <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"صفحہ <xliff:g id="ID_1">%1$d</xliff:g> از <xliff:g id="ID_2">%2$d</xliff:g>"</string> <string name="tuner_lock_screen" msgid="2267383813241144544">"مقفل اسکرین"</string> - <!-- no translation found for finder_active (7907846989716941952) --> - <skip /> - <!-- no translation found for shutdown_progress (5464239146561542178) --> - <skip /> + <string name="finder_active" msgid="7907846989716941952">"پاور آف ہونے پر بھی آپ میرا آلہ ڈھونڈیں کے ساتھ اس فون کو تلاش کر سکتے ہیں"</string> + <string name="shutdown_progress" msgid="5464239146561542178">"بند ہو رہا ہے…"</string> <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"نگہداشت کے اقدامات ملاحظہ کریں"</string> <string name="high_temp_dialog_help_text" msgid="7380171287943345858">"نگہداشت کے اقدامات ملاحظہ کریں"</string> <string name="high_temp_alarm_title" msgid="8654754369605452169">"اپنے آلہ کو ان پلگ کریں"</string> diff --git a/packages/SystemUI/res/values-ur/tiles_states_strings.xml b/packages/SystemUI/res/values-ur/tiles_states_strings.xml index bb27b9fbe9f2..6d1e7076da40 100644 --- a/packages/SystemUI/res/values-ur/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-ur/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"آف ہے"</item> <item msgid="578444932039713369">"آن ہے"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"دستیاب نہیں ہے"</item> <item msgid="8707481475312432575">"آف ہے"</item> diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml index 09e5ff04d9b6..478fcdb95527 100644 --- a/packages/SystemUI/res/values-uz/strings.xml +++ b/packages/SystemUI/res/values-uz/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Jiringlatish"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Tebranish"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Ovozsiz"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Translatsiya"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Jiringlash ovozsizligi uchun ishlamaydi"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Ovozini yoqish uchun ustiga bosing."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Tebranishni yoqish uchun ustiga bosing. Qulayliklar ishlamasligi mumkin."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Ovozini o‘chirish uchun ustiga bosing. Qulayliklar ishlamasligi mumkin."</string> diff --git a/packages/SystemUI/res/values-uz/tiles_states_strings.xml b/packages/SystemUI/res/values-uz/tiles_states_strings.xml index bd5ee8951c5b..0558c4a91794 100644 --- a/packages/SystemUI/res/values-uz/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-uz/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Oʻchiq"</item> <item msgid="578444932039713369">"Yoniq"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Ishlamaydi"</item> <item msgid="8707481475312432575">"Oʻchiq"</item> diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index f7057e948b70..7e8638b5bdeb 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Đổ chuông"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Rung"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Tắt tiếng"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Truyền"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Không hoạt động vì chuông bị tắt"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Nhấn để bật tiếng."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Nhấn để đặt chế độ rung. Bạn có thể tắt tiếng dịch vụ trợ năng."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Nhấn để tắt tiếng. Bạn có thể tắt tiếng dịch vụ trợ năng."</string> diff --git a/packages/SystemUI/res/values-vi/tiles_states_strings.xml b/packages/SystemUI/res/values-vi/tiles_states_strings.xml index 201a45b3d445..eee10d330258 100644 --- a/packages/SystemUI/res/values-vi/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-vi/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Đang tắt"</item> <item msgid="578444932039713369">"Đang bật"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Không hoạt động"</item> <item msgid="8707481475312432575">"Đang tắt"</item> diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml index 2c87e2491814..255213858f91 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"响铃"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"振动"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"静音"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"投屏"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"该功能无法使用,因为铃声被静音"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s。点按即可取消静音。"</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s。点按即可设为振动,但可能会同时将无障碍服务设为静音。"</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s。点按即可设为静音,但可能会同时将无障碍服务设为静音。"</string> diff --git a/packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml b/packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml index 3ab2d7a3d1c7..82ab6710729d 100644 --- a/packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"已关闭"</item> <item msgid="578444932039713369">"已开启"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"不可用"</item> <item msgid="8707481475312432575">"已关闭"</item> diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml index 78d7c7a0c9b4..5941dad6903b 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"鈴聲"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"震動"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"靜音"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"投放"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"鈴聲已設定為靜音,因此無法使用"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s。輕按即可取消靜音。"</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s。輕按即可設為震動。無障礙功能服務可能已經設為靜音。"</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s。輕按即可設為靜音。無障礙功能服務可能已經設為靜音。"</string> diff --git a/packages/SystemUI/res/values-zh-rHK/tiles_states_strings.xml b/packages/SystemUI/res/values-zh-rHK/tiles_states_strings.xml index 89d66284ede6..6bac27502583 100644 --- a/packages/SystemUI/res/values-zh-rHK/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"已關閉"</item> <item msgid="578444932039713369">"已開啟"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"無法使用"</item> <item msgid="8707481475312432575">"已關閉"</item> diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index 46d775086fc3..c46d831c741a 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"鈴聲"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"震動"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"靜音"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"投放"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"鈴聲已設為靜音,因此無法使用"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s。輕觸即可取消靜音。"</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s。輕觸即可設為震動,但系統可能會將無障礙服務一併設為靜音。"</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s。輕觸即可設為靜音,但系統可能會將無障礙服務一併設為靜音。"</string> diff --git a/packages/SystemUI/res/values-zh-rTW/tiles_states_strings.xml b/packages/SystemUI/res/values-zh-rTW/tiles_states_strings.xml index a046e3354729..5794bf1703f9 100644 --- a/packages/SystemUI/res/values-zh-rTW/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"已關閉"</item> <item msgid="578444932039713369">"已開啟"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"無法使用"</item> <item msgid="8707481475312432575">"已關閉"</item> diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml index f12c2bbdd81c..0bbac05638cc 100644 --- a/packages/SystemUI/res/values-zu/strings.xml +++ b/packages/SystemUI/res/values-zu/strings.xml @@ -575,10 +575,8 @@ <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Khalisa"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Dlidlizela"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Thulisa"</string> - <!-- no translation found for media_device_cast (4786241789687569892) --> - <skip /> - <!-- no translation found for stream_notification_unavailable (4313854556205836435) --> - <skip /> + <string name="media_device_cast" msgid="4786241789687569892">"Sakaza"</string> + <string name="stream_notification_unavailable" msgid="4313854556205836435">"Ayitholakali ngoba ukukhala kuthulisiwe"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Thepha ukuze ususe ukuthula."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Thepha ukuze usethe ukudlidliza. Amasevisi okufinyelela angathuliswa."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Thepha ukuze uthulise. Amasevisi okufinyelela angathuliswa."</string> diff --git a/packages/SystemUI/res/values-zu/tiles_states_strings.xml b/packages/SystemUI/res/values-zu/tiles_states_strings.xml index e35840b85ab4..8c7b652cdfe0 100644 --- a/packages/SystemUI/res/values-zu/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-zu/tiles_states_strings.xml @@ -126,6 +126,9 @@ <item msgid="8259411607272330225">"Valiwe"</item> <item msgid="578444932039713369">"Vuliwe"</item> </string-array> + <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) --> + <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) --> + <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) --> <string-array name="tile_states_reverse"> <item msgid="3574611556622963971">"Akutholakali"</item> <item msgid="8707481475312432575">"Valiwe"</item> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 035cfdc492d0..71ae0d716429 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -223,7 +223,6 @@ <item type="id" name="lock_icon" /> <item type="id" name="lock_icon_bg" /> <item type="id" name="burn_in_layer" /> - <item type="id" name="burn_in_layer_empty_view" /> <item type="id" name="communal_tutorial_indicator" /> <item type="id" name="nssl_placeholder_barrier_bottom" /> <item type="id" name="ambient_indication_container" /> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 346bdfc8b2d8..b71341791ee6 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -3254,6 +3254,12 @@ <!-- Text for education page content description for unfolded animation. [CHAR_LIMIT=NONE] --> <string name="rear_display_accessibility_unfolded_animation">Foldable device being flipped around</string> + <!-- QuickSettings: Additional label for the auto-rotation quicksettings tile indicating that the setting corresponds to the folded posture for a foldable device [CHAR LIMIT=32] --> + <string name="quick_settings_rotation_posture_folded">folded</string> + <!-- QuickSettings: Additional label for the auto-rotation quicksettings tile indicating that the setting corresponds to the unfolded posture for a foldable device [CHAR LIMIT=32] --> + <string name="quick_settings_rotation_posture_unfolded">unfolded</string> + <!-- QuickSettings: template for rotation tile foldable secondary label [CHAR LIMIT=64] !--> + <string name="rotation_tile_with_posture_secondary_label_template">%1$s / %2$s</string> <!-- Title for notification of low stylus battery with percentage. "percentage" is the value of the battery capacity remaining [CHAR LIMIT=none]--> <string name="stylus_battery_low_percentage"><xliff:g id="percentage" example="16%">%s</xliff:g> battery remaining</string> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 617eadbd447c..ce08ca3e43af 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -1581,4 +1581,8 @@ <style name="Theme.PrivacyDialog" parent="@style/Theme.SystemUI.Dialog"> <item name="android:colorBackground">?androidprv:attr/materialColorSurfaceContainer</item> </style> + + <style name="Theme.SystemUI.Dialog.StickyKeys" parent="@style/Theme.SystemUI.Dialog"> + <item name="android:colorBackground">@color/transparent</item> + </style> </resources> diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java index 75cace424e8b..b9b8fbe9637f 100644 --- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java +++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java @@ -209,6 +209,9 @@ public class CarrierTextManager { // This will set/remove the listeners appropriately. Note that it will never double // add the listeners. handleSetListening(mCarrierTextCallback); + mainExecutor.execute(() -> { + mKeyguardUpdateMonitor.registerCallback(mCallback); + }); } }); } @@ -276,7 +279,6 @@ public class CarrierTextManager { if (mNetworkSupported.get()) { // Keyguard update monitor expects callbacks from main thread mMainExecutor.execute(() -> { - mKeyguardUpdateMonitor.registerCallback(mCallback); mWakefulnessLifecycle.addObserver(mWakefulnessObserver); }); mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener); @@ -289,7 +291,6 @@ public class CarrierTextManager { } else { mCarrierTextCallback = null; mMainExecutor.execute(() -> { - mKeyguardUpdateMonitor.removeCallback(mCallback); mWakefulnessLifecycle.removeObserver(mWakefulnessObserver); }); mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mPhoneStateListener); diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 169a4e0f3501..f28d4052b5a8 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -15,8 +15,6 @@ */ package com.android.keyguard -import com.android.systemui.keyguard.shared.model.KeyguardState.AOD -import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import android.content.BroadcastReceiver import android.content.Context import android.content.Intent @@ -43,14 +41,16 @@ import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags.REGION_SAMPLING import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.log.core.Logger +import com.android.systemui.plugins.clocks.AlarmData import com.android.systemui.plugins.clocks.ClockController import com.android.systemui.plugins.clocks.ClockFaceController import com.android.systemui.plugins.clocks.ClockMessageBuffers import com.android.systemui.plugins.clocks.ClockTickRate -import com.android.systemui.plugins.clocks.AlarmData import com.android.systemui.plugins.clocks.WeatherData import com.android.systemui.plugins.clocks.ZenData import com.android.systemui.plugins.clocks.ZenData.ZenMode @@ -61,16 +61,18 @@ import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChang import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.ZenModeController import com.android.systemui.util.concurrency.DelayableExecutor +import java.util.Locale +import java.util.TimeZone +import java.util.concurrent.Executor +import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.Job import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge import kotlinx.coroutines.launch -import java.util.Locale -import java.util.TimeZone -import java.util.concurrent.Executor -import javax.inject.Inject /** * Controller for a Clock provided by the registry and used on the keyguard. Instantiated by @@ -93,11 +95,13 @@ constructor( private val featureFlags: FeatureFlagsClassic, private val zenModeController: ZenModeController, ) { - var loggers = listOf( - clockBuffers.infraMessageBuffer, - clockBuffers.smallClockMessageBuffer, - clockBuffers.largeClockMessageBuffer - ).map { Logger(it, TAG) } + var loggers = + listOf( + clockBuffers.infraMessageBuffer, + clockBuffers.smallClockMessageBuffer, + clockBuffers.largeClockMessageBuffer + ) + .map { Logger(it, TAG) } var clock: ClockController? = null get() = field @@ -108,11 +112,12 @@ constructor( } private fun disconnectClock(clock: ClockController?) { - if (clock == null) { return; } + if (clock == null) { + return + } smallClockOnAttachStateChangeListener?.let { clock.smallClock.view.removeOnAttachStateChangeListener(it) - smallClockFrame?.viewTreeObserver - ?.removeOnGlobalLayoutListener(onGlobalLayoutListener) + smallClockFrame?.viewTreeObserver?.removeOnGlobalLayoutListener(onGlobalLayoutListener) } largeClockOnAttachStateChangeListener?.let { clock.largeClock.view.removeOnAttachStateChangeListener(it) @@ -120,7 +125,9 @@ constructor( } private fun connectClock(clock: ClockController?) { - if (clock == null) { return; } + if (clock == null) { + return + } val clockStr = clock.toString() loggers.forEach { it.d({ "New Clock: $str1" }) { str1 = clockStr } } @@ -129,23 +136,27 @@ constructor( if (!regionSamplingEnabled) { updateColors() } else { - smallRegionSampler = createRegionSampler( - clock.smallClock.view, - mainExecutor, - bgExecutor, - regionSamplingEnabled, - isLockscreen = true, - ::updateColors - ).apply { startRegionSampler() } - - largeRegionSampler = createRegionSampler( - clock.largeClock.view, - mainExecutor, - bgExecutor, - regionSamplingEnabled, - isLockscreen = true, - ::updateColors - ).apply { startRegionSampler() } + smallRegionSampler = + createRegionSampler( + clock.smallClock.view, + mainExecutor, + bgExecutor, + regionSamplingEnabled, + isLockscreen = true, + ::updateColors + ) + .apply { startRegionSampler() } + + largeRegionSampler = + createRegionSampler( + clock.largeClock.view, + mainExecutor, + bgExecutor, + regionSamplingEnabled, + isLockscreen = true, + ::updateColors + ) + .apply { startRegionSampler() } updateColors() } @@ -158,49 +169,49 @@ constructor( } clock.events.onWeatherDataChanged(it) } - zenData?.let { - clock.events.onZenDataChanged(it) - } - alarmData?.let { - clock.events.onAlarmDataChanged(it) - } - - smallClockOnAttachStateChangeListener = object : OnAttachStateChangeListener { - var pastVisibility: Int? = null - override fun onViewAttachedToWindow(view: View) { - clock.events.onTimeFormatChanged(DateFormat.is24HourFormat(context)) - // Match the asing for view.parent's layout classes. - smallClockFrame = (view.parent as ViewGroup)?.also { frame -> - pastVisibility = frame.visibility - onGlobalLayoutListener = OnGlobalLayoutListener { - val currentVisibility = frame.visibility - if (pastVisibility != currentVisibility) { - pastVisibility = currentVisibility - // when small clock is visible, - // recalculate bounds and sample - if (currentVisibility == View.VISIBLE) { - smallRegionSampler?.stopRegionSampler() - smallRegionSampler?.startRegionSampler() + zenData?.let { clock.events.onZenDataChanged(it) } + alarmData?.let { clock.events.onAlarmDataChanged(it) } + + smallClockOnAttachStateChangeListener = + object : OnAttachStateChangeListener { + var pastVisibility: Int? = null + override fun onViewAttachedToWindow(view: View) { + clock.events.onTimeFormatChanged(DateFormat.is24HourFormat(context)) + // Match the asing for view.parent's layout classes. + smallClockFrame = + (view.parent as ViewGroup)?.also { frame -> + pastVisibility = frame.visibility + onGlobalLayoutListener = OnGlobalLayoutListener { + val currentVisibility = frame.visibility + if (pastVisibility != currentVisibility) { + pastVisibility = currentVisibility + // when small clock is visible, + // recalculate bounds and sample + if (currentVisibility == View.VISIBLE) { + smallRegionSampler?.stopRegionSampler() + smallRegionSampler?.startRegionSampler() + } + } } + frame.viewTreeObserver.addOnGlobalLayoutListener(onGlobalLayoutListener) } - } - frame.viewTreeObserver.addOnGlobalLayoutListener(onGlobalLayoutListener) } - } - override fun onViewDetachedFromWindow(p0: View) { - smallClockFrame?.viewTreeObserver + override fun onViewDetachedFromWindow(p0: View) { + smallClockFrame + ?.viewTreeObserver ?.removeOnGlobalLayoutListener(onGlobalLayoutListener) + } } - } clock.smallClock.view.addOnAttachStateChangeListener(smallClockOnAttachStateChangeListener) - largeClockOnAttachStateChangeListener = object : OnAttachStateChangeListener { - override fun onViewAttachedToWindow(p0: View) { - clock.events.onTimeFormatChanged(DateFormat.is24HourFormat(context)) + largeClockOnAttachStateChangeListener = + object : OnAttachStateChangeListener { + override fun onViewAttachedToWindow(p0: View) { + clock.events.onTimeFormatChanged(DateFormat.is24HourFormat(context)) + } + override fun onViewDetachedFromWindow(p0: View) {} } - override fun onViewDetachedFromWindow(p0: View) {} - } clock.largeClock.view.addOnAttachStateChangeListener(largeClockOnAttachStateChangeListener) } @@ -263,7 +274,9 @@ constructor( bgExecutor, regionSamplingEnabled, isLockscreen, - ) { updateColors() } + ) { + updateColors() + } } var smallRegionSampler: RegionSampler? = null @@ -364,35 +377,36 @@ constructor( } } - private val zenModeCallback = object : ZenModeController.Callback { - override fun onZenChanged(zen: Int) { - var mode = ZenMode.fromInt(zen) - if (mode == null) { - Log.e(TAG, "Failed to get zen mode from int: $zen") - return - } - - zenData = ZenData( - mode, - if (mode == ZenMode.OFF) SysuiR.string::dnd_is_off.name - else SysuiR.string::dnd_is_on.name - ).also { data -> - mainExecutor.execute { - clock?.run { events.onZenDataChanged(data) } + private val zenModeCallback = + object : ZenModeController.Callback { + override fun onZenChanged(zen: Int) { + var mode = ZenMode.fromInt(zen) + if (mode == null) { + Log.e(TAG, "Failed to get zen mode from int: $zen") + return } + + zenData = + ZenData( + mode, + if (mode == ZenMode.OFF) SysuiR.string::dnd_is_off.name + else SysuiR.string::dnd_is_on.name + ) + .also { data -> + mainExecutor.execute { clock?.run { events.onZenDataChanged(data) } } + } } - } - override fun onNextAlarmChanged() { - val nextAlarmMillis = zenModeController.getNextAlarm() - alarmData = AlarmData( - if (nextAlarmMillis > 0) nextAlarmMillis else null, - SysuiR.string::status_bar_alarm.name - ).also { data -> - clock?.run { events.onAlarmDataChanged(data) } + override fun onNextAlarmChanged() { + val nextAlarmMillis = zenModeController.getNextAlarm() + alarmData = + AlarmData( + if (nextAlarmMillis > 0) nextAlarmMillis else null, + SysuiR.string::status_bar_alarm.name + ) + .also { data -> clock?.run { events.onAlarmDataChanged(data) } } } } - } fun registerListeners(parent: View) { if (isRegistered) { @@ -413,6 +427,7 @@ constructor( listenForDozing(this) if (migrateClocksToBlueprint()) { listenForDozeAmountTransition(this) + listenForAnyStateToAodTransition(this) } else { listenForDozeAmount(this) } @@ -444,12 +459,15 @@ constructor( largeRegionSampler?.stopRegionSampler() smallTimeListener?.stop() largeTimeListener?.stop() - clock?.smallClock?.view - ?.removeOnAttachStateChangeListener(smallClockOnAttachStateChangeListener) - smallClockFrame?.viewTreeObserver - ?.removeOnGlobalLayoutListener(onGlobalLayoutListener) - clock?.largeClock?.view - ?.removeOnAttachStateChangeListener(largeClockOnAttachStateChangeListener) + clock + ?.smallClock + ?.view + ?.removeOnAttachStateChangeListener(smallClockOnAttachStateChangeListener) + smallClockFrame?.viewTreeObserver?.removeOnGlobalLayoutListener(onGlobalLayoutListener) + clock + ?.largeClock + ?.view + ?.removeOnAttachStateChangeListener(largeClockOnAttachStateChangeListener) } /** @@ -473,12 +491,10 @@ constructor( largeTimeListener = null clock?.let { - smallTimeListener = TimeListener(it.smallClock, mainExecutor).apply { - update(shouldTimeListenerRun) - } - largeTimeListener = TimeListener(it.largeClock, mainExecutor).apply { - update(shouldTimeListenerRun) - } + smallTimeListener = + TimeListener(it.smallClock, mainExecutor).apply { update(shouldTimeListenerRun) } + largeTimeListener = + TimeListener(it.largeClock, mainExecutor).apply { update(shouldTimeListenerRun) } } } @@ -517,7 +533,27 @@ constructor( @VisibleForTesting internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job { return scope.launch { - keyguardTransitionInteractor.dozeAmountTransition.collect { handleDoze(it.value) } + merge( + keyguardTransitionInteractor.aodToLockscreenTransition.map { step -> + step.copy(value = 1f - step.value) + }, + keyguardTransitionInteractor.lockscreenToAodTransition, + ) + .collect { handleDoze(it.value) } + } + } + + /** + * When keyguard is displayed again after being gone, the clock must be reset to full dozing. + */ + @VisibleForTesting + internal fun listenForAnyStateToAodTransition(scope: CoroutineScope): Job { + return scope.launch { + keyguardTransitionInteractor + .transitionStepsToState(AOD) + .filter { it.transitionState == TransitionState.STARTED } + .filter { it.from != LOCKSCREEN } + .collect { handleDoze(1f) } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index c0ae4a1f4036..9421f150a38a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -53,6 +53,9 @@ import com.android.systemui.Dumpable; import com.android.systemui.animation.ViewHierarchyAnimator; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.keyguard.shared.model.TransitionState; +import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.plugins.clocks.ClockController; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.power.shared.model.ScreenPowerState; @@ -101,6 +104,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV private final Rect mClipBounds = new Rect(); private final KeyguardInteractor mKeyguardInteractor; private final PowerInteractor mPowerInteractor; + private final KeyguardTransitionInteractor mKeyguardTransitionInteractor; private final DozeParameters mDozeParameters; private View mStatusArea = null; @@ -108,6 +112,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV private Boolean mSplitShadeEnabled = false; private Boolean mStatusViewCentered = true; + private boolean mGoneToAodTransitionRunning = false; private DumpManager mDumpManager; private final TransitionListenerAdapter mKeyguardStatusAlignmentTransitionListener = @@ -176,6 +181,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV KeyguardLogger logger, InteractionJankMonitor interactionJankMonitor, KeyguardInteractor keyguardInteractor, + KeyguardTransitionInteractor keyguardTransitionInteractor, DumpManager dumpManager, PowerInteractor powerInteractor) { super(keyguardStatusView); @@ -191,6 +197,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV mDumpManager = dumpManager; mKeyguardInteractor = keyguardInteractor; mPowerInteractor = powerInteractor; + mKeyguardTransitionInteractor = keyguardTransitionInteractor; } @Override @@ -225,7 +232,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV mDumpManager.registerDumpable(getInstanceName(), this); if (migrateClocksToBlueprint()) { startCoroutines(EmptyCoroutineContext.INSTANCE); - mView.setVisibility(View.GONE); } } @@ -241,6 +247,15 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV dozeTimeTick(); } }, context); + + collectFlow(mView, mKeyguardTransitionInteractor.getGoneToAodTransition(), + (TransitionStep step) -> { + if (step.getTransitionState() == TransitionState.RUNNING) { + mGoneToAodTransitionRunning = true; + } else { + mGoneToAodTransitionRunning = false; + } + }, context); } public KeyguardStatusView getView() { @@ -311,7 +326,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV * Set keyguard status view alpha. */ public void setAlpha(float alpha) { - if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) { + if (!mKeyguardVisibilityHelper.isVisibilityAnimating() && !mGoneToAodTransitionRunning) { mView.setAlpha(alpha); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index b7667a8669f4..8c51a4e0ce66 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -3313,6 +3313,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab becameAbsent |= ABSENT_SIM_STATE_LIST.contains(state); + // TODO(b/327476182): Preserve SIM_STATE_CARD_IO_ERROR sims in a separate data source. SimData data = mSimDatas.get(subId); final boolean changed; if (data == null) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java index 77054bd29147..2000028dff41 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java @@ -88,10 +88,6 @@ public class KeyguardVisibilityHelper { boolean keyguardFadingAway, boolean goingToFullShade, int oldStatusBarState) { - if (migrateClocksToBlueprint()) { - log("Ignoring all of KeyguardVisibilityelper"); - return; - } Assert.isMainThread(); PropertyAnimator.cancelAnimation(mView, AnimatableProperty.ALPHA); boolean isOccluded = mKeyguardStateController.isOccluded(); diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index 88535893b60c..33f14d44de75 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -22,17 +22,13 @@ import android.util.ArrayMap; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; -import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.Preconditions; import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.systemui.accessibility.AccessibilityButtonModeObserver; -import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver; import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.assist.AssistManager; 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.flags.FeatureFlags; import com.android.systemui.fragments.FragmentService; @@ -47,7 +43,6 @@ import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationMediaManager; -import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; import com.android.systemui.statusbar.notification.stack.AmbientState; @@ -58,7 +53,6 @@ import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider; import com.android.systemui.statusbar.phone.SystemUIDialogManager; import com.android.systemui.statusbar.policy.BluetoothController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; -import com.android.systemui.statusbar.policy.FlashlightController; import com.android.systemui.statusbar.window.StatusBarWindowController; import com.android.systemui.tuner.TunablePadding.TunablePaddingService; import com.android.systemui.tuner.TunerService; @@ -70,6 +64,7 @@ import java.util.function.Consumer; import javax.inject.Inject; import javax.inject.Named; + /** * Class to handle ugly dependencies throughout sysui until we determine the * long-term dependency injection solution. @@ -96,10 +91,6 @@ public class Dependency { * Key for getting a Handler for receiving time tick broadcasts on. */ public static final String TIME_TICK_HANDLER_NAME = "time_tick_handler"; - /** - * Generic handler on the main thread. - */ - private static final String MAIN_HANDLER_NAME = "main_handler"; /** * An email address to send memory leak reports to by default. @@ -121,11 +112,6 @@ public class Dependency { */ public static final DependencyKey<Handler> TIME_TICK_HANDLER = new DependencyKey<>(TIME_TICK_HANDLER_NAME); - /** - * Generic handler on the main thread. - */ - public static final DependencyKey<Handler> MAIN_HANDLER = - new DependencyKey<>(MAIN_HANDLER_NAME); private final ArrayMap<Object, Object> mDependencies = new ArrayMap<>(); private final ArrayMap<Object, LazyDependencyCreator> mProviders = new ArrayMap<>(); @@ -134,7 +120,6 @@ public class Dependency { @Inject Lazy<BroadcastDispatcher> mBroadcastDispatcher; @Inject Lazy<BluetoothController> mBluetoothController; - @Inject Lazy<FlashlightController> mFlashlightController; @Inject Lazy<KeyguardUpdateMonitor> mKeyguardUpdateMonitor; @Inject Lazy<DeviceProvisionedController> mDeviceProvisionedController; @Inject Lazy<PluginManager> mPluginManager; @@ -150,15 +135,10 @@ public class Dependency { @Inject Lazy<LightBarController> mLightBarController; @Inject Lazy<OverviewProxyService> mOverviewProxyService; @Inject Lazy<NavigationModeController> mNavBarModeController; - @Inject Lazy<AccessibilityButtonModeObserver> mAccessibilityButtonModeObserver; - @Inject Lazy<AccessibilityButtonTargetsObserver> mAccessibilityButtonListController; - @Inject Lazy<IStatusBarService> mIStatusBarService; - @Inject Lazy<NotificationRemoteInputManager.Callback> mNotificationRemoteInputManagerCallback; @Inject Lazy<NavigationBarController> mNavigationBarController; @Inject Lazy<StatusBarStateController> mStatusBarStateController; @Inject Lazy<NotificationMediaManager> mNotificationMediaManager; @Inject @Background Lazy<Looper> mBgLooper; - @Inject @Main Lazy<Handler> mMainHandler; @Inject @Named(TIME_TICK_HANDLER_NAME) Lazy<Handler> mTimeTickHandler; @Inject Lazy<SysUiState> mSysUiStateFlagsContainer; @Inject Lazy<CommandQueue> mCommandQueue; @@ -187,10 +167,8 @@ public class Dependency { // on imports. mProviders.put(TIME_TICK_HANDLER, mTimeTickHandler::get); mProviders.put(BG_LOOPER, mBgLooper::get); - mProviders.put(MAIN_HANDLER, mMainHandler::get); mProviders.put(BroadcastDispatcher.class, mBroadcastDispatcher::get); mProviders.put(BluetoothController.class, mBluetoothController::get); - mProviders.put(FlashlightController.class, mFlashlightController::get); mProviders.put(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor::get); mProviders.put(DeviceProvisionedController.class, mDeviceProvisionedController::get); mProviders.put(PluginManager.class, mPluginManager::get); @@ -205,13 +183,6 @@ public class Dependency { mProviders.put(LightBarController.class, mLightBarController::get); mProviders.put(OverviewProxyService.class, mOverviewProxyService::get); mProviders.put(NavigationModeController.class, mNavBarModeController::get); - mProviders.put(AccessibilityButtonModeObserver.class, - mAccessibilityButtonModeObserver::get); - mProviders.put(AccessibilityButtonTargetsObserver.class, - mAccessibilityButtonListController::get); - mProviders.put(IStatusBarService.class, mIStatusBarService::get); - mProviders.put(NotificationRemoteInputManager.Callback.class, - mNotificationRemoteInputManagerCallback::get); mProviders.put(NavigationBarController.class, mNavigationBarController::get); mProviders.put(StatusBarStateController.class, mStatusBarStateController::get); mProviders.put(NotificationMediaManager.class, mNotificationMediaManager::get); diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt index 36d3ed52b655..f1a0e5e3539c 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt @@ -11,7 +11,6 @@ import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags import com.android.systemui.bouncer.ui.BouncerDialogFactory import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel -import com.android.systemui.compose.ComposeFacade import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.Flags.COMPOSE_BOUNCER_ENABLED import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel @@ -60,11 +59,7 @@ constructor( private val composeBouncerDependencies: Lazy<ComposeBouncerDependencies>, ) { fun bind(view: ViewGroup) { - if ( - COMPOSE_BOUNCER_ENABLED && - composeBouncerFlags.isOnlyComposeBouncerEnabled() && - ComposeFacade.isComposeAvailable() - ) { + if (COMPOSE_BOUNCER_ENABLED && composeBouncerFlags.isOnlyComposeBouncerEnabled()) { val deps = composeBouncerDependencies.get() ComposeBouncerViewBinder.bind( view, diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt index 7b053956091e..179fa874db79 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt @@ -1,15 +1,17 @@ package com.android.systemui.bouncer.ui.binder import android.view.ViewGroup +import androidx.compose.ui.platform.ComposeView import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle +import com.android.compose.theme.PlatformTheme import com.android.keyguard.ViewMediatorCallback import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.bouncer.ui.BouncerDialogFactory +import com.android.systemui.bouncer.ui.composable.BouncerContent import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel -import com.android.systemui.compose.ComposeFacade import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.user.domain.interactor.SelectedUserInteractor import kotlinx.coroutines.flow.collectLatest @@ -27,12 +29,11 @@ object ComposeBouncerViewBinder { viewMediatorCallback: ViewMediatorCallback?, ) { view.addView( - ComposeFacade.createBouncer( - view.context, - viewModel, - dialogFactory, - ) + ComposeView(view.context).apply { + setContent { PlatformTheme { BouncerContent(viewModel, dialogFactory) } } + } ) + view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { launch { diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt index 1c8b84d82a56..b42eda108d54 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt @@ -73,6 +73,14 @@ class PasswordBouncerViewModel( initialValue = isInputEnabled.value && !isTextFieldFocused.value, ) + /** The ID of the currently-selected user. */ + val selectedUserId: StateFlow<Int> = + selectedUserInteractor.selectedUser.stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(), + initialValue = selectedUserInteractor.getSelectedUserId(), + ) + override fun onHidden() { super.onHidden() isTextFieldFocused.value = false diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraRotationModule.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraRotationModule.kt new file mode 100644 index 000000000000..f12382801887 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/camera/CameraRotationModule.kt @@ -0,0 +1,33 @@ +/* + * 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.camera + +import com.android.systemui.camera.data.repository.CameraAutoRotateRepository +import com.android.systemui.camera.data.repository.CameraAutoRotateRepositoryImpl +import com.android.systemui.camera.data.repository.CameraSensorPrivacyRepository +import com.android.systemui.camera.data.repository.CameraSensorPrivacyRepositoryImpl +import dagger.Binds +import dagger.Module + +/** Module for repositories that provide data regarding camera rotation state. */ +@Module +interface CameraRotationModule { + + @Binds + fun bindsPrivacyRepoImpl(impl: CameraSensorPrivacyRepositoryImpl): CameraSensorPrivacyRepository + @Binds fun bindsRotateRepoImpl(impl: CameraAutoRotateRepositoryImpl): CameraAutoRotateRepository +} diff --git a/packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepository.kt b/packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepository.kt new file mode 100644 index 000000000000..023fd285c6d8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepository.kt @@ -0,0 +1,70 @@ +/* + * 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.camera.data.repository + +import android.os.UserHandle +import android.provider.Settings +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.util.settings.SecureSettings +import com.android.systemui.util.settings.SettingsProxyExt.observerFlow +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.stateIn + +interface CameraAutoRotateRepository { + /** @return true if camera auto rotate setting is enabled */ + fun isCameraAutoRotateSettingEnabled(userHandle: UserHandle): StateFlow<Boolean> +} + +@SysUISingleton +class CameraAutoRotateRepositoryImpl +@Inject +constructor( + private val secureSettings: SecureSettings, + @Background private val bgCoroutineContext: CoroutineContext, + @Application private val applicationScope: CoroutineScope, +) : CameraAutoRotateRepository { + private val userMap = mutableMapOf<Int, StateFlow<Boolean>>() + + override fun isCameraAutoRotateSettingEnabled(userHandle: UserHandle): StateFlow<Boolean> { + return userMap.getOrPut(userHandle.identifier) { + secureSettings + .observerFlow(userHandle.identifier, Settings.Secure.CAMERA_AUTOROTATE) + .map { isAutoRotateSettingEnabled(userHandle.identifier) } + .onStart { emit(isAutoRotateSettingEnabled(userHandle.identifier)) } + .flowOn(bgCoroutineContext) + .stateIn(applicationScope, SharingStarted.WhileSubscribed(), false) + } + } + + private fun isAutoRotateSettingEnabled(userId: Int) = + secureSettings.getIntForUser(SETTING_NAME, DISABLED, userId) == ENABLED + + private companion object { + const val SETTING_NAME = Settings.Secure.CAMERA_AUTOROTATE + const val DISABLED = 0 + const val ENABLED = 1 + } +} diff --git a/packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepository.kt b/packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepository.kt new file mode 100644 index 000000000000..7816a1487c01 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepository.kt @@ -0,0 +1,76 @@ +/* + * 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.camera.data.repository + +import android.hardware.SensorPrivacyManager +import android.hardware.SensorPrivacyManager.Sensors.CAMERA +import android.os.UserHandle +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.stateIn + +interface CameraSensorPrivacyRepository { + /** Tracks whether camera sensor privacy is enabled. */ + fun isEnabled(userHandle: UserHandle): StateFlow<Boolean> +} + +@SysUISingleton +class CameraSensorPrivacyRepositoryImpl +@Inject +constructor( + @Background private val bgCoroutineContext: CoroutineContext, + @Application private val scope: CoroutineScope, + private val privacyManager: SensorPrivacyManager, +) : CameraSensorPrivacyRepository { + private val userMap = mutableMapOf<Int, StateFlow<Boolean>>() + + /** Whether camera sensor privacy is enabled */ + override fun isEnabled(userHandle: UserHandle): StateFlow<Boolean> = + userMap.getOrPut(userHandle.identifier) { + privacyManager + .isEnabled(userHandle) + .flowOn(bgCoroutineContext) + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + } +} + +fun SensorPrivacyManager.isEnabled(userHandle: UserHandle): Flow<Boolean> { + return conflatedCallbackFlow { + val privacyCallback = + SensorPrivacyManager.OnSensorPrivacyChangedListener { sensor, enabled -> + if (sensor == CAMERA) { + trySend(enabled) + } + } + addSensorPrivacyListener(CAMERA, userHandle.identifier, privacyCallback) + awaitClose { removeSensorPrivacyListener(privacyCallback) } + } + .onStart { emit(isSensorPrivacyEnabled(SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE, CAMERA)) } + .distinctUntilChanged() +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt index 8397372e0735..c3c7411c401d 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt @@ -16,25 +16,23 @@ package com.android.systemui.communal +import com.android.compose.animation.scene.SceneKey import com.android.systemui.CoreStartable import com.android.systemui.communal.domain.interactor.CommunalInteractor -import com.android.systemui.communal.shared.model.CommunalSceneKey +import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dock.DockManager -import com.android.systemui.dock.retrieveIsDocked import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionStep -import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.mapLatest @@ -65,38 +63,40 @@ constructor( .onEach { nextScene -> communalInteractor.onSceneChanged(nextScene) } .launchIn(applicationScope) + // TODO(b/322787129): re-enable once custom animations are in place // Handle automatically switching to communal when docked. - dockManager - .retrieveIsDocked() - // Allow some time after docking to ensure the dream doesn't start. If the dream - // starts, then we don't want to automatically transition to glanceable hub. - .debounce(DOCK_DEBOUNCE_DELAY) - .sample(keyguardTransitionInteractor.startedKeyguardState, ::Pair) - .onEach { (docked, lastStartedState) -> - if (docked && lastStartedState == KeyguardState.LOCKSCREEN) { - communalInteractor.onSceneChanged(CommunalSceneKey.Communal) - } - } - .launchIn(bgScope) + // dockManager + // .retrieveIsDocked() + // // Allow some time after docking to ensure the dream doesn't start. If the + // dream + // // starts, then we don't want to automatically transition to glanceable hub. + // .debounce(DOCK_DEBOUNCE_DELAY) + // .sample(keyguardTransitionInteractor.startedKeyguardState, ::Pair) + // .onEach { (docked, lastStartedState) -> + // if (docked && lastStartedState == KeyguardState.LOCKSCREEN) { + // communalInteractor.onSceneChanged(CommunalScenes.Communal) + // } + // } + // .launchIn(bgScope) } private suspend fun determineSceneAfterTransition( lastStartedTransition: TransitionStep, - ): CommunalSceneKey? { + ): SceneKey? { val to = lastStartedTransition.to val from = lastStartedTransition.from val docked = dockManager.isDocked return when { - docked && to == KeyguardState.LOCKSCREEN && from != KeyguardState.GLANCEABLE_HUB -> { - CommunalSceneKey.Communal + docked && to == KeyguardState.LOCKSCREEN && from == KeyguardState.DREAMING -> { + CommunalScenes.Communal } - to == KeyguardState.GONE -> CommunalSceneKey.Blank + to == KeyguardState.GONE -> CommunalScenes.Blank !docked && !KeyguardState.deviceIsAwakeInState(to) -> { // If the user taps the screen and wakes the device within this timeout, we don't // want to dismiss the hub delay(AWAKE_DEBOUNCE_DELAY) - CommunalSceneKey.Blank + CommunalScenes.Blank } else -> null } 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 f4a3bcb7a0fa..201ce832cc41 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 @@ -16,8 +16,9 @@ package com.android.systemui.communal.data.repository -import com.android.systemui.communal.shared.model.CommunalSceneKey -import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey +import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.scene.data.repository.SceneContainerRepository @@ -40,20 +41,20 @@ interface CommunalRepository { * Target scene as requested by the underlying [SceneTransitionLayout] or through * [setDesiredScene]. */ - val desiredScene: StateFlow<CommunalSceneKey> + val desiredScene: StateFlow<SceneKey> /** Exposes the transition state of the communal [SceneTransitionLayout]. */ - val transitionState: StateFlow<ObservableCommunalTransitionState> + val transitionState: StateFlow<ObservableTransitionState> /** Updates the requested scene. */ - fun setDesiredScene(desiredScene: CommunalSceneKey) + fun setDesiredScene(desiredScene: SceneKey) /** * Updates the transition state of the hub [SceneTransitionLayout]. * * Note that you must call is with `null` when the UI is done or risk a memory leak. */ - fun setTransitionState(transitionState: Flow<ObservableCommunalTransitionState>?) + fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) } @OptIn(ExperimentalCoroutinesApi::class) @@ -66,14 +67,12 @@ constructor( sceneContainerRepository: SceneContainerRepository, ) : CommunalRepository { - private val _desiredScene: MutableStateFlow<CommunalSceneKey> = - MutableStateFlow(CommunalSceneKey.DEFAULT) - override val desiredScene: StateFlow<CommunalSceneKey> = _desiredScene.asStateFlow() + private val _desiredScene: MutableStateFlow<SceneKey> = MutableStateFlow(CommunalScenes.Default) + override val desiredScene: StateFlow<SceneKey> = _desiredScene.asStateFlow() - private val defaultTransitionState = - ObservableCommunalTransitionState.Idle(CommunalSceneKey.DEFAULT) - private val _transitionState = MutableStateFlow<Flow<ObservableCommunalTransitionState>?>(null) - override val transitionState: StateFlow<ObservableCommunalTransitionState> = + private val defaultTransitionState = ObservableTransitionState.Idle(CommunalScenes.Default) + private val _transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null) + override val transitionState: StateFlow<ObservableTransitionState> = _transitionState .flatMapLatest { innerFlowOrNull -> innerFlowOrNull ?: flowOf(defaultTransitionState) } .stateIn( @@ -82,7 +81,7 @@ constructor( initialValue = defaultTransitionState, ) - override fun setDesiredScene(desiredScene: CommunalSceneKey) { + override fun setDesiredScene(desiredScene: SceneKey) { _desiredScene.value = desiredScene } @@ -91,7 +90,7 @@ constructor( * * Note that you must call is with `null` when the UI is done or risk a memory leak. */ - override fun setTransitionState(transitionState: Flow<ObservableCommunalTransitionState>?) { + override fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) { _transitionState.value = transitionState } } 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 151e1eeaefc5..814295787b6c 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 @@ -19,6 +19,8 @@ package com.android.systemui.communal.domain.interactor import android.app.smartspace.SmartspaceTarget import android.content.ComponentName import android.os.UserHandle +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey import com.android.systemui.communal.data.repository.CommunalMediaRepository import com.android.systemui.communal.data.repository.CommunalPrefsRepository import com.android.systemui.communal.data.repository.CommunalRepository @@ -29,9 +31,8 @@ import com.android.systemui.communal.shared.model.CommunalContentSize import com.android.systemui.communal.shared.model.CommunalContentSize.FULL import com.android.systemui.communal.shared.model.CommunalContentSize.HALF import com.android.systemui.communal.shared.model.CommunalContentSize.THIRD -import com.android.systemui.communal.shared.model.CommunalSceneKey +import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.CommunalWidgetContentModel -import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState import com.android.systemui.communal.widgets.CommunalAppWidgetHost import com.android.systemui.communal.widgets.EditWidgetsActivityStarter import com.android.systemui.communal.widgets.WidgetConfigurator @@ -46,7 +47,7 @@ import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlags -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.settings.UserTracker import com.android.systemui.smartspace.data.repository.SmartspaceRepository import com.android.systemui.util.kotlin.BooleanFlowOperators.and @@ -131,34 +132,33 @@ constructor( * Target scene as requested by the underlying [SceneTransitionLayout] or through * [onSceneChanged]. * - * If [isCommunalAvailable] is false, will return [CommunalSceneKey.Blank] + * If [isCommunalAvailable] is false, will return [CommunalScenes.Blank] */ - val desiredScene: Flow<CommunalSceneKey> = + val desiredScene: Flow<SceneKey> = communalRepository.desiredScene.combine(isCommunalAvailable) { scene, available -> - if (available) scene else CommunalSceneKey.Blank + if (available) scene else CommunalScenes.Blank } /** Transition state of the hub mode. */ - val transitionState: StateFlow<ObservableCommunalTransitionState> = - communalRepository.transitionState + val transitionState: StateFlow<ObservableTransitionState> = communalRepository.transitionState /** * Updates the transition state of the hub [SceneTransitionLayout]. * * Note that you must call is with `null` when the UI is done or risk a memory leak. */ - fun setTransitionState(transitionState: Flow<ObservableCommunalTransitionState>?) { + fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) { communalRepository.setTransitionState(transitionState) } /** Returns a flow that tracks the progress of transitions to the given scene from 0-1. */ - fun transitionProgressToScene(targetScene: CommunalSceneKey) = + fun transitionProgressToScene(targetScene: SceneKey) = transitionState .flatMapLatest { state -> when (state) { - is ObservableCommunalTransitionState.Idle -> + is ObservableTransitionState.Idle -> flowOf(CommunalTransitionProgress.Idle(state.scene)) - is ObservableCommunalTransitionState.Transition -> + is ObservableTransitionState.Transition -> if (state.toScene == targetScene) { state.progress.map { CommunalTransitionProgress.Transition( @@ -176,7 +176,7 @@ constructor( /** * Flow that emits a boolean if the communal UI is the target scene, ie. the [desiredScene] is - * the [CommunalSceneKey.Communal]. + * the [CommunalScenes.Communal]. * * This will be true as soon as the desired scene is set programmatically or at whatever point * during a fling that SceneTransitionLayout determines that the end state will be the communal @@ -191,9 +191,9 @@ constructor( flow { emit(sceneContainerFlags.isEnabled()) } .flatMapLatest { sceneContainerEnabled -> if (sceneContainerEnabled) { - sceneInteractor.currentScene.map { it == SceneKey.Communal } + sceneInteractor.currentScene.map { it == Scenes.Communal } } else { - desiredScene.map { it == CommunalSceneKey.Communal } + desiredScene.map { it == CommunalScenes.Communal } } } .distinctUntilChanged() @@ -220,7 +220,7 @@ constructor( */ val isIdleOnCommunal: Flow<Boolean> = communalRepository.transitionState.map { - it is ObservableCommunalTransitionState.Idle && it.scene == CommunalSceneKey.Communal + it is ObservableTransitionState.Idle && it.scene == CommunalScenes.Communal } /** @@ -230,11 +230,11 @@ constructor( */ val isCommunalVisible: Flow<Boolean> = communalRepository.transitionState.map { - !(it is ObservableCommunalTransitionState.Idle && it.scene == CommunalSceneKey.Blank) + !(it is ObservableTransitionState.Idle && it.scene == CommunalScenes.Blank) } /** Callback received whenever the [SceneTransitionLayout] finishes a scene transition. */ - fun onSceneChanged(newScene: CommunalSceneKey) { + fun onSceneChanged(newScene: SceneKey) { communalRepository.setDesiredScene(newScene) } @@ -422,7 +422,7 @@ constructor( /** Simplified transition progress data class for tracking a single transition between scenes. */ sealed class CommunalTransitionProgress { /** No transition/animation is currently running. */ - data class Idle(val scene: CommunalSceneKey) : CommunalTransitionProgress() + data class Idle(val scene: SceneKey) : CommunalTransitionProgress() /** There is a transition animating to the expected scene. */ data class Transition( diff --git a/packages/SystemUI/src/com/android/systemui/communal/log/CommunalLoggerStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/log/CommunalLoggerStartable.kt index 889023e8dab6..f2b473864a78 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/log/CommunalLoggerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/log/CommunalLoggerStartable.kt @@ -16,12 +16,12 @@ package com.android.systemui.communal.log +import com.android.compose.animation.scene.ObservableTransitionState import com.android.internal.logging.UiEventLogger import com.android.systemui.CoreStartable import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.shared.log.CommunalUiEvent -import com.android.systemui.communal.shared.model.CommunalSceneKey -import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState +import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.util.kotlin.pairwise @@ -87,25 +87,25 @@ constructor( } /** Whether currently in communal scene. */ -private fun ObservableCommunalTransitionState.isOnCommunal(): Boolean { - return this is ObservableCommunalTransitionState.Idle && scene == CommunalSceneKey.Communal +private fun ObservableTransitionState.isOnCommunal(): Boolean { + return this is ObservableTransitionState.Idle && scene == CommunalScenes.Communal } /** Whether currently in a scene other than communal. */ -private fun ObservableCommunalTransitionState.isNotOnCommunal(): Boolean { - return this is ObservableCommunalTransitionState.Idle && scene != CommunalSceneKey.Communal +private fun ObservableTransitionState.isNotOnCommunal(): Boolean { + return this is ObservableTransitionState.Idle && scene != CommunalScenes.Communal } /** Whether currently transitioning from another scene to communal. */ -private fun ObservableCommunalTransitionState.isSwipingToCommunal(): Boolean { - return this is ObservableCommunalTransitionState.Transition && - toScene == CommunalSceneKey.Communal && +private fun ObservableTransitionState.isSwipingToCommunal(): Boolean { + return this is ObservableTransitionState.Transition && + toScene == CommunalScenes.Communal && isInitiatedByUserInput } /** Whether currently transitioning from communal to another scene. */ -private fun ObservableCommunalTransitionState.isSwipingFromCommunal(): Boolean { - return this is ObservableCommunalTransitionState.Transition && - fromScene == CommunalSceneKey.Communal && +private fun ObservableTransitionState.isSwipingFromCommunal(): Boolean { + return this is ObservableTransitionState.Transition && + fromScene == CommunalScenes.Communal && isInitiatedByUserInput } diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalSceneKey.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalScenes.kt index c68dd4ff271c..d5a56c1e9ee0 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalSceneKey.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalScenes.kt @@ -16,21 +16,15 @@ package com.android.systemui.communal.shared.model -/** Definition of the possible scenes for the communal UI. */ -sealed class CommunalSceneKey( - private val loggingName: String, -) { - /** The communal scene containing the hub UI. */ - object Communal : CommunalSceneKey("communal") +import com.android.compose.animation.scene.SceneKey +/** Definition of the possible scenes for the communal UI. */ +object CommunalScenes { /** The default scene, shows nothing and is only there to allow swiping to communal. */ - object Blank : CommunalSceneKey("blank") + @JvmField val Blank = SceneKey("blank") - override fun toString(): String { - return loggingName - } + /** The communal scene containing the hub UI. */ + @JvmField val Communal = SceneKey("communal") - companion object { - val DEFAULT = Blank - } + @JvmField val Default = Blank } diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/ObservableCommunalTransitionState.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/ObservableCommunalTransitionState.kt deleted file mode 100644 index d834715768c9..000000000000 --- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/ObservableCommunalTransitionState.kt +++ /dev/null @@ -1,54 +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.communal.shared.model - -import kotlinx.coroutines.flow.Flow - -/** - * This is a fork of the `com.android.compose.animation.scene.ObservableTransitionState` class. - * - * TODO(b/315490861): remove this fork, once we can compile Compose into System UI. - */ -sealed class ObservableCommunalTransitionState { - /** No transition/animation is currently running. */ - data class Idle(val scene: CommunalSceneKey) : ObservableCommunalTransitionState() - - /** There is a transition animating between two scenes. */ - data class Transition( - val fromScene: CommunalSceneKey, - val toScene: CommunalSceneKey, - val progress: Flow<Float>, - - /** - * Whether the transition was originally triggered by user input rather than being - * programmatic. If this value is initially true, it will remain true until the transition - * fully completes, even if the user input that triggered the transition has ended. Any - * sub-transitions launched by this one will inherit this value. For example, if the user - * drags a pointer but does not exceed the threshold required to transition to another - * scene, this value will remain true after the pointer is no longer touching the screen and - * will be true in any transition created to animate back to the original position. - */ - val isInitiatedByUserInput: Boolean, - - /** - * Whether user input is currently driving the transition. For example, if a user is - * dragging a pointer, this emits true. Once they lift their finger, this emits false while - * the transition completes/settles. - */ - val isUserInputOngoing: Flow<Boolean>, - ) : ObservableCommunalTransitionState() -} 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 3ec9a268f80c..35372cd28c15 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 @@ -18,10 +18,10 @@ package com.android.systemui.communal.ui.viewmodel import android.content.ComponentName import android.os.UserHandle +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.model.CommunalContentModel -import com.android.systemui.communal.shared.model.CommunalSceneKey -import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState import com.android.systemui.communal.widgets.WidgetConfigurator import com.android.systemui.media.controls.ui.view.MediaHost import kotlinx.coroutines.flow.Flow @@ -34,7 +34,7 @@ abstract class BaseCommunalViewModel( private val communalInteractor: CommunalInteractor, val mediaHost: MediaHost, ) { - val currentScene: Flow<CommunalSceneKey> = communalInteractor.desiredScene + val currentScene: Flow<SceneKey> = communalInteractor.desiredScene /** Whether widgets are currently being re-ordered. */ open val reorderingWidgets: StateFlow<Boolean> = MutableStateFlow(false) @@ -45,7 +45,7 @@ abstract class BaseCommunalViewModel( val selectedKey: StateFlow<String?> get() = _selectedKey - fun onSceneChanged(scene: CommunalSceneKey) { + fun onSceneChanged(scene: SceneKey) { communalInteractor.onSceneChanged(scene) } @@ -54,7 +54,7 @@ abstract class BaseCommunalViewModel( * * Note that you must call is with `null` when the UI is done or risk a memory leak. */ - fun setTransitionState(transitionState: Flow<ObservableCommunalTransitionState>?) { + fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) { communalInteractor.setTransitionState(transitionState) } 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 fc9a7df4744d..35b27aaeb6bc 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 @@ -21,6 +21,7 @@ import com.android.systemui.communal.domain.interactor.CommunalTutorialInteracto import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger import com.android.systemui.log.dagger.CommunalLog @@ -46,6 +47,7 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch /** The default view model used for showing the communal hub. */ +@OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class CommunalViewModel @Inject @@ -54,6 +56,7 @@ constructor( private val communalInteractor: CommunalInteractor, tutorialInteractor: CommunalTutorialInteractor, shadeInteractor: ShadeInteractor, + deviceEntryInteractor: DeviceEntryInteractor, @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost, @CommunalLog logBuffer: LogBuffer, ) : BaseCommunalViewModel(communalInteractor, mediaHost) { @@ -87,6 +90,8 @@ constructor( /** Whether touches should be disabled in communal */ val touchesAllowed: Flow<Boolean> = not(shadeInteractor.isAnyFullyExpanded) + val deviceUnlocked: Flow<Boolean> = deviceEntryInteractor.isUnlocked + init { // Initialize our media host for the UMO. This only needs to happen once and must be done // before the MediaHierarchyManager attempts to move the UMO to the hub. 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 a5a390d7683b..b6ad26b24dc7 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt @@ -25,14 +25,21 @@ import android.util.Log import android.view.IWindowManager import android.view.WindowInsets import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.ui.Modifier +import com.android.compose.theme.LocalAndroidColorScheme +import com.android.compose.theme.PlatformTheme import com.android.internal.logging.UiEventLogger import com.android.systemui.communal.shared.log.CommunalUiEvent -import com.android.systemui.communal.shared.model.CommunalSceneKey +import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.communal.ui.compose.CommunalHub import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel import com.android.systemui.communal.util.WidgetPickerIntentUtils.getWidgetExtraFromIntent -import com.android.systemui.compose.ComposeFacade.setCommunalEditWidgetActivityContent import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger import com.android.systemui.log.dagger.CommunalLog @@ -110,56 +117,68 @@ constructor( val preselectedKey = intent.getStringExtra(EXTRA_PRESELECTED_KEY) communalViewModel.setSelectedKey(preselectedKey) - setCommunalEditWidgetActivityContent( - activity = this, - viewModel = communalViewModel, - widgetConfigurator = widgetConfigurator, - onOpenWidgetPicker = { - val intent = - Intent(Intent.ACTION_MAIN).also { it.addCategory(Intent.CATEGORY_HOME) } - packageManager - .resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) - ?.activityInfo - ?.packageName - ?.let { packageName -> - try { - addWidgetActivityLauncher.launch( - Intent(Intent.ACTION_PICK).apply { - setPackage(packageName) - putExtra( - EXTRA_DESIRED_WIDGET_WIDTH, - resources.getDimensionPixelSize( - R.dimen.communal_widget_picker_desired_width - ) - ) - putExtra( - EXTRA_DESIRED_WIDGET_HEIGHT, - resources.getDimensionPixelSize( - R.dimen.communal_widget_picker_desired_height - ) - ) - putExtra( - AppWidgetManager.EXTRA_CATEGORY_FILTER, - communalViewModel.getCommunalWidgetCategories - ) - } + setContent { + PlatformTheme { + Box( + modifier = + Modifier.fillMaxSize() + .background(LocalAndroidColorScheme.current.outlineVariant), + ) { + CommunalHub( + viewModel = communalViewModel, + onOpenWidgetPicker = ::onOpenWidgetPicker, + widgetConfigurator = widgetConfigurator, + onEditDone = ::onEditDone, + ) + } + } + } + } + + private fun onOpenWidgetPicker() { + val intent = Intent(Intent.ACTION_MAIN).also { it.addCategory(Intent.CATEGORY_HOME) } + packageManager + .resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) + ?.activityInfo + ?.packageName + ?.let { packageName -> + try { + addWidgetActivityLauncher.launch( + Intent(Intent.ACTION_PICK).apply { + setPackage(packageName) + putExtra( + EXTRA_DESIRED_WIDGET_WIDTH, + resources.getDimensionPixelSize( + R.dimen.communal_widget_picker_desired_width + ) + ) + putExtra( + EXTRA_DESIRED_WIDGET_HEIGHT, + resources.getDimensionPixelSize( + R.dimen.communal_widget_picker_desired_height + ) + ) + putExtra( + AppWidgetManager.EXTRA_CATEGORY_FILTER, + communalViewModel.getCommunalWidgetCategories ) - } catch (e: Exception) { - Log.e(TAG, "Failed to launch widget picker activity", e) } - } - ?: run { Log.e(TAG, "Couldn't resolve launcher package name") } - }, - onEditDone = { - try { - communalViewModel.onSceneChanged(CommunalSceneKey.Communal) - checkNotNull(windowManagerService).lockNow(/* options */ null) - finish() - } catch (e: RemoteException) { - Log.e(TAG, "Couldn't lock the device as WindowManager is dead.") + ) + } catch (e: Exception) { + Log.e(TAG, "Failed to launch widget picker activity", e) } } - ) + ?: run { Log.e(TAG, "Couldn't resolve launcher package name") } + } + + private fun onEditDone() { + try { + communalViewModel.onSceneChanged(CommunalScenes.Communal) + checkNotNull(windowManagerService).lockNow(/* options */ null) + finish() + } catch (e: RemoteException) { + Log.e(TAG, "Couldn't lock the device as WindowManager is dead.") + } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt deleted file mode 100644 index a0aaa906802a..000000000000 --- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (C) 2022 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.compose - -import android.content.Context -import android.view.View -import android.view.WindowInsets -import androidx.activity.ComponentActivity -import androidx.lifecycle.LifecycleOwner -import com.android.systemui.bouncer.ui.BouncerDialogFactory -import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel -import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel -import com.android.systemui.communal.ui.viewmodel.CommunalViewModel -import com.android.systemui.communal.widgets.WidgetConfigurator -import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel -import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint -import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel -import com.android.systemui.people.ui.viewmodel.PeopleViewModel -import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel -import com.android.systemui.scene.shared.model.Scene -import com.android.systemui.scene.shared.model.SceneDataSourceDelegator -import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel -import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.StateFlow - -/** - * A facade to interact with Compose, when it is available. - * - * You should access this facade by calling the static methods on - * [com.android.systemui.compose.ComposeFacade] directly. - */ -interface BaseComposeFacade { - /** - * Whether Compose is currently available. This function should be checked before calling any - * other functions on this facade. - * - * This value will never change at runtime. - */ - fun isComposeAvailable(): Boolean - - /** - * Return the [ComposeInitializer] to make Compose usable in windows outside normal activities. - */ - fun composeInitializer(): ComposeInitializer - - /** Bind the content of [activity] to [viewModel]. */ - fun setPeopleSpaceActivityContent( - activity: ComponentActivity, - viewModel: PeopleViewModel, - onResult: (PeopleViewModel.Result) -> Unit, - ) - - /** Bind the content of [activity] to [viewModel]. */ - fun setCommunalEditWidgetActivityContent( - activity: ComponentActivity, - viewModel: BaseCommunalViewModel, - widgetConfigurator: WidgetConfigurator, - onOpenWidgetPicker: () -> Unit, - onEditDone: () -> Unit, - ) - - fun setVolumePanelActivityContent( - activity: ComponentActivity, - viewModel: VolumePanelViewModel, - onDismiss: () -> Unit, - ) - - /** Create a [View] to represent [viewModel] on screen. */ - fun createFooterActionsView( - context: Context, - viewModel: FooterActionsViewModel, - qsVisibilityLifecycleOwner: LifecycleOwner, - ): View - - /** Create a [View] to represent [viewModel] on screen. */ - fun createSceneContainerView( - scope: CoroutineScope, - context: Context, - viewModel: SceneContainerViewModel, - windowInsets: StateFlow<WindowInsets?>, - sceneByKey: Map<SceneKey, Scene>, - dataSourceDelegator: SceneDataSourceDelegator, - ): View - - /** Creates sticky key indicator content presenting provided [viewModel] */ - fun createStickyKeysIndicatorContent( - context: Context, - viewModel: StickyKeysIndicatorViewModel - ): View - - /** Create a [View] to represent [viewModel] on screen. */ - fun createCommunalView( - context: Context, - viewModel: BaseCommunalViewModel, - ): View - - /** Create a [View] to represent the [BouncerViewModel]. */ - fun createBouncer( - context: Context, - viewModel: BouncerViewModel, - dialogFactory: BouncerDialogFactory, - ): View - - /** Creates a container that hosts the communal UI and handles gesture transitions. */ - fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View - - /** Creates a [View] that represents the Lockscreen. */ - fun createLockscreen( - context: Context, - viewModel: LockscreenContentViewModel, - blueprints: Set<@JvmSuppressWildcards LockscreenSceneBlueprint>, - ): View -} diff --git a/packages/SystemUI/src/com/android/systemui/compose/ComposeInitializer.kt b/packages/SystemUI/src/com/android/systemui/compose/ComposeInitializer.kt index 90dc3a00daa2..813e0e025bf5 100644 --- a/packages/SystemUI/src/com/android/systemui/compose/ComposeInitializer.kt +++ b/packages/SystemUI/src/com/android/systemui/compose/ComposeInitializer.kt @@ -17,6 +17,13 @@ package com.android.systemui.compose import android.view.View +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.findViewTreeLifecycleOwner +import androidx.lifecycle.setViewTreeLifecycleOwner +import androidx.savedstate.SavedStateRegistryController +import androidx.savedstate.SavedStateRegistryOwner +import com.android.compose.animation.ViewTreeSavedStateRegistryOwner +import com.android.systemui.lifecycle.ViewLifecycleOwner /** * An initializer to use Compose outside of an Activity, e.g. inside a window added directly using @@ -39,10 +46,55 @@ import android.view.View * } * ``` */ -interface ComposeInitializer { +object ComposeInitializer { /** Function to be called on your window root view's [View.onAttachedToWindow] function. */ - fun onAttachedToWindow(root: View) + fun onAttachedToWindow(root: View) { + if (root.findViewTreeLifecycleOwner() != null) { + error("root $root already has a LifecycleOwner") + } + + val parent = root.parent + if (parent is View && parent.id != android.R.id.content) { + error( + "ComposeInitializer.onAttachedToWindow(View) must be called on the content child." + + "Outside of activities and dialogs, this is usually the top-most View of a " + + "window." + ) + } + + // The lifecycle owner, which is STARTED when [root] is visible and RESUMED when [root] is + // both visible and focused. + val lifecycleOwner = ViewLifecycleOwner(root) + + // We create a trivial implementation of [SavedStateRegistryOwner] that does not do any save + // or restore because SystemUI process is always running and top-level windows using this + // initializer are created once, when the process is started. + val savedStateRegistryOwner = + object : SavedStateRegistryOwner { + private val savedStateRegistryController = + SavedStateRegistryController.create(this).apply { performRestore(null) } + + override val savedStateRegistry = savedStateRegistryController.savedStateRegistry + + override val lifecycle: Lifecycle + get() = lifecycleOwner.lifecycle + } + + // We must call [ViewLifecycleOwner.onCreate] after creating the [SavedStateRegistryOwner] + // because `onCreate` might move the lifecycle state to STARTED which will make + // [SavedStateRegistryController.performRestore] throw. + lifecycleOwner.onCreate() + + // Set the owners on the root. They will be reused by any ComposeView inside the root + // hierarchy. + root.setViewTreeLifecycleOwner(lifecycleOwner) + ViewTreeSavedStateRegistryOwner.set(root, savedStateRegistryOwner) + } /** Function to be called on your window root view's [View.onDetachedFromWindow] function. */ - fun onDetachedFromWindow(root: View) + fun onDetachedFromWindow(root: View) { + (root.findViewTreeLifecycleOwner() as ViewLifecycleOwner).onDestroy() + root.setViewTreeLifecycleOwner(null) + ViewTreeSavedStateRegistryOwner.set(root, null) + } } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java index f7bc5cdc69c2..a4011fd7718c 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java @@ -43,6 +43,7 @@ import com.android.systemui.reardisplay.RearDisplayModule; import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsImplementation; import com.android.systemui.rotationlock.RotationLockModule; +import com.android.systemui.rotationlock.RotationLockNewModule; import com.android.systemui.scene.SceneContainerFrameworkModule; import com.android.systemui.screenshot.ReferenceScreenshotModule; import com.android.systemui.settings.MultiUserUtilsModule; @@ -110,6 +111,7 @@ import javax.inject.Named; RearDisplayModule.class, ReferenceScreenshotModule.class, RotationLockModule.class, + RotationLockNewModule.class, ScreenDecorationsModule.class, SystemActionsModule.class, ShadeModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt index 21fd87c1c241..029a4f33cd27 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 @@ -25,7 +25,7 @@ import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository import com.android.systemui.keyguard.data.repository.TrustRepository import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlags -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -81,9 +81,9 @@ constructor( val isDeviceEntered: StateFlow<Boolean> = sceneInteractor.currentScene .filter { currentScene -> - currentScene == SceneKey.Gone || currentScene == SceneKey.Lockscreen + currentScene == Scenes.Gone || currentScene == Scenes.Lockscreen } - .map { it == SceneKey.Gone } + .map { it == Scenes.Gone } .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, @@ -148,12 +148,12 @@ constructor( applicationScope.launch { if (isAuthenticationRequired()) { sceneInteractor.changeScene( - toScene = SceneKey.Bouncer, + toScene = Scenes.Bouncer, loggingReason = "request to unlock device while authentication required", ) } else { sceneInteractor.changeScene( - toScene = SceneKey.Gone, + toScene = Scenes.Gone, loggingReason = "request to unlock device while authentication isn't required", ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeyDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeyDialogFactory.kt index 3ed58a7fe5ae..89433d3e8abb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeyDialogFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeyDialogFactory.kt @@ -20,15 +20,16 @@ import android.app.Dialog import android.content.Context import android.view.Gravity import android.view.Window +import android.view.WindowInsets import android.view.WindowManager import android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE import android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL import androidx.activity.ComponentDialog -import com.android.systemui.compose.ComposeFacade import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyboard.stickykeys.ui.view.createStickyKeyIndicatorView import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel import com.android.systemui.res.R import javax.inject.Inject @@ -45,10 +46,10 @@ constructor( } private fun createStickyKeyIndicator(viewModel: StickyKeysIndicatorViewModel): Dialog { - return ComponentDialog(context, R.style.Theme_SystemUI_Dialog).apply { + return ComponentDialog(context, R.style.Theme_SystemUI_Dialog_StickyKeys).apply { // because we're requesting window feature it must be called before setting content window?.setStickyKeyWindowAttributes() - setContentView(ComposeFacade.createStickyKeysIndicatorContent(context, viewModel)) + setContentView(createStickyKeyIndicatorView(context, viewModel)) } } @@ -61,6 +62,9 @@ constructor( attributes = WindowManager.LayoutParams().apply { copyFrom(attributes) + // needed because we're above system bars windows, see [TYPE_STATUS_BAR_SUB_PANEL] + receiveInsetsIgnoringZOrder = true + fitInsetsTypes = WindowInsets.Type.systemBars() title = "StickyKeysIndicator" } } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt index 842fd04bfcc5..78c4e77cad74 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt @@ -17,15 +17,13 @@ package com.android.systemui.keyboard.stickykeys.ui import android.app.Dialog -import android.util.Log -import com.android.systemui.compose.ComposeFacade import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyboard.stickykeys.StickyKeysLogger import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel +import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -import javax.inject.Inject @SysUISingleton class StickyKeysIndicatorCoordinator @@ -40,11 +38,6 @@ constructor( private var dialog: Dialog? = null fun startListening() { - // this check needs to be moved to PhysicalKeyboardCoreStartable - if (!ComposeFacade.isComposeAvailable()) { - Log.e("StickyKeysIndicatorCoordinator", "Compose is required for this UI") - return - } applicationScope.launch { viewModel.indicatorContent.collect { stickyKeys -> stickyKeysLogger.logNewUiState(stickyKeys) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index 301942f6242b..106fdf1fbcbe 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -20,6 +20,9 @@ package com.android.systemui.keyguard import android.content.Context import android.view.LayoutInflater import android.view.View +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.ComposeView import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet.BOTTOM import androidx.constraintlayout.widget.ConstraintSet.END @@ -35,7 +38,6 @@ import com.android.keyguard.dagger.KeyguardStatusViewComponent import com.android.systemui.CoreStartable import com.android.systemui.Flags.keyguardBottomAreaRefactor import com.android.systemui.common.ui.ConfigurationState -import com.android.systemui.compose.ComposeFacade import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor @@ -45,6 +47,8 @@ import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder +import com.android.systemui.keyguard.ui.composable.LockscreenContent +import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea import com.android.systemui.keyguard.ui.view.KeyguardRootView import com.android.systemui.keyguard.ui.view.layout.KeyguardBlueprintCommandListener @@ -132,7 +136,7 @@ constructor( if (!SceneContainerFlag.isEnabled) { if (ComposeLockscreen.isEnabled) { val composeView = - ComposeFacade.createLockscreen( + createLockscreen( context = context, viewModel = lockscreenContentViewModel, blueprints = lockscreenSceneBlueprintsLazy.get(), @@ -207,6 +211,21 @@ constructor( ) } + private fun createLockscreen( + context: Context, + viewModel: LockscreenContentViewModel, + blueprints: Set<@JvmSuppressWildcards LockscreenSceneBlueprint>, + ): View { + val sceneBlueprints = + blueprints.mapNotNull { it as? ComposableLockscreenSceneBlueprint }.toSet() + return ComposeView(context).apply { + setContent { + LockscreenContent(viewModel = viewModel, blueprints = sceneBlueprints) + .Content(modifier = Modifier.fillMaxSize()) + } + } + } + /** * Temporary, to allow NotificationPanelViewController to use the same instance while code is * migrated: b/288242803 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 ad0c3fb0c53a..64e28700aa74 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 @@ -24,6 +24,7 @@ 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 import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main @@ -79,6 +80,12 @@ interface KeyguardRepository { val keyguardAlpha: StateFlow<Float> /** + * Observable of the relative offset of the lock-screen clock from its natural position on the + * screen. + */ + val clockPosition: StateFlow<Position> + + /** * Observable for whether the keyguard is showing. * * Note: this is also `true` when the lock-screen is occluded with an `Activity` "above" it in @@ -233,6 +240,11 @@ interface KeyguardRepository { fun setKeyguardAlpha(alpha: Float) /** + * Sets the relative offset of the lock-screen clock from its natural position on the screen. + */ + fun setClockPosition(x: Int, y: Int) + + /** * Returns whether the keyguard bottom area should be constrained to the top of the lock icon */ fun isUdfpsSupported(): Boolean @@ -311,6 +323,9 @@ constructor( private val _keyguardAlpha = MutableStateFlow(1f) override val keyguardAlpha = _keyguardAlpha.asStateFlow() + private val _clockPosition = MutableStateFlow(Position(0, 0)) + override val clockPosition = _clockPosition.asStateFlow() + private val _clockShouldBeCentered = MutableStateFlow(true) override val clockShouldBeCentered: Flow<Boolean> = _clockShouldBeCentered.asStateFlow() @@ -662,6 +677,10 @@ constructor( _keyguardAlpha.value = alpha } + override fun setClockPosition(x: Int, y: Int) { + _clockPosition.value = Position(x, y) + } + override fun isUdfpsSupported(): Boolean = keyguardUpdateMonitor.isUdfpsSupported override fun setQuickSettingsVisible(isVisible: Boolean) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt index ee892a800b63..7ae70a9a3e7c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt @@ -63,19 +63,18 @@ constructor( burnInHelperWrapper.burnInProgressOffset() ) - /** Given the max x,y dimens, determine the current translation shifts. */ - fun burnIn(xDimenResourceId: Int, yDimenResourceId: Int): Flow<BurnInModel> { - return combine( - burnInOffset(xDimenResourceId, isXAxis = true), - burnInOffset(yDimenResourceId, isXAxis = false).map { - it * 2 - context.resources.getDimensionPixelSize(yDimenResourceId) + val keyguardBurnIn: Flow<BurnInModel> = + combine( + burnInOffset(R.dimen.burn_in_prevention_offset_x, isXAxis = true), + burnInOffset(R.dimen.burn_in_prevention_offset_y, isXAxis = false).map { + it * 2 - + context.resources.getDimensionPixelSize(R.dimen.burn_in_prevention_offset_y) } ) { translationX, translationY -> BurnInModel(translationX, translationY, burnInHelperWrapper.burnInScale()) } .distinctUntilChanged() .stateIn(scope, SharingStarted.Lazily, BurnInModel()) - } /** * Use for max burn-in offsets that are NOT specified in pixels. This flow will recalculate the diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt index e0b5c0e1f4c6..dbd5e26eacfc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt @@ -27,12 +27,14 @@ import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled +import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.util.kotlin.Utils.Companion.sample import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.launch @SysUISingleton @@ -45,6 +47,7 @@ constructor( @Background bgDispatcher: CoroutineDispatcher, @Main mainDispatcher: CoroutineDispatcher, private val keyguardInteractor: KeyguardInteractor, + private val powerInteractor: PowerInteractor, ) : TransitionInteractor( fromState = KeyguardState.AOD, @@ -68,15 +71,16 @@ constructor( */ private fun listenForAodToOccluded() { scope.launch { - keyguardInteractor.isKeyguardOccluded.sample(startedKeyguardState, ::Pair).collect { - (isOccluded, startedKeyguardState) -> - if (isOccluded && startedKeyguardState == KeyguardState.AOD) { - startTransitionTo( - toState = KeyguardState.OCCLUDED, - modeOnCanceled = TransitionModeOnCanceled.RESET - ) + keyguardInteractor.isKeyguardOccluded + .sample(startedKeyguardTransitionStep, ::Pair) + .collect { (isOccluded, lastStartedStep) -> + if (isOccluded && lastStartedStep.to == KeyguardState.AOD) { + startTransitionTo( + toState = KeyguardState.OCCLUDED, + modeOnCanceled = TransitionModeOnCanceled.RESET + ) + } } - } } } @@ -85,15 +89,26 @@ constructor( keyguardInteractor .dozeTransitionTo(DozeStateModel.FINISH) .sample( + keyguardInteractor.isKeyguardShowing, startedKeyguardTransitionStep, keyguardInteractor.isKeyguardOccluded, keyguardInteractor.biometricUnlockState, + keyguardInteractor.primaryBouncerShowing, ) - .collect { (_, lastStartedStep, occluded, biometricUnlockState) -> + .collect { + ( + _, + isKeyguardShowing, + lastStartedStep, + occluded, + biometricUnlockState, + primaryBouncerShowing) -> if ( lastStartedStep.to == KeyguardState.AOD && !occluded && - !isWakeAndUnlock(biometricUnlockState) + !isWakeAndUnlock(biometricUnlockState) && + isKeyguardShowing && + !primaryBouncerShowing ) { val modeOnCanceled = if (lastStartedStep.from == KeyguardState.LOCKSCREEN) { @@ -134,13 +149,31 @@ constructor( } scope.launch { - keyguardInteractor.biometricUnlockState.sample(finishedKeyguardState, ::Pair).collect { - (biometricUnlockState, keyguardState) -> - KeyguardWmStateRefactor.assertInLegacyMode() - if (keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)) { - startTransitionTo(KeyguardState.GONE) + powerInteractor.isAwake + .debounce(50L) + .sample( + keyguardInteractor.biometricUnlockState, + startedKeyguardTransitionStep, + keyguardInteractor.isKeyguardShowing, + keyguardInteractor.isKeyguardDismissible, + ) + .collect { + ( + isAwake, + biometricUnlockState, + lastStartedTransitionStep, + isKeyguardShowing, + isKeyguardDismissible) -> + KeyguardWmStateRefactor.assertInLegacyMode() + if ( + isAwake && + lastStartedTransitionStep.to == KeyguardState.AOD && + (isWakeAndUnlock(biometricUnlockState) || + (!isKeyguardShowing && isKeyguardDismissible)) + ) { + startTransitionTo(KeyguardState.GONE) + } } - } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt index 54d5908e9fa4..8591fe782d3a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt @@ -26,13 +26,14 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.power.domain.interactor.PowerInteractor -import com.android.systemui.util.kotlin.Utils.Companion.toQuad -import com.android.systemui.util.kotlin.sample +import com.android.systemui.util.kotlin.Utils.Companion.sample import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.launch @SysUISingleton @@ -56,50 +57,57 @@ constructor( ) { override fun start() { - listenForDozingToLockscreenHubOrOccluded() - listenForDozingToGone() + listenForDozingToAny() listenForTransitionToCamera(scope, keyguardInteractor) } - private fun listenForDozingToLockscreenHubOrOccluded() { + private val canDismissLockScreen: Flow<Boolean> = + combine( + keyguardInteractor.isKeyguardShowing, + keyguardInteractor.isKeyguardDismissible, + ) { isKeyguardShowing, isKeyguardDismissible -> + isKeyguardDismissible && !isKeyguardShowing + } + + private fun listenForDozingToAny() { scope.launch { powerInteractor.isAwake + .debounce(50L) .sample( - combine( - startedKeyguardTransitionStep, - keyguardInteractor.isKeyguardOccluded, - communalInteractor.isIdleOnCommunal, - ::Triple - ), - ::toQuad + keyguardInteractor.biometricUnlockState, + startedKeyguardTransitionStep, + keyguardInteractor.isKeyguardOccluded, + communalInteractor.isIdleOnCommunal, + canDismissLockScreen, + keyguardInteractor.primaryBouncerShowing, ) - .collect { (isAwake, lastStartedTransition, occluded, isIdleOnCommunal) -> - if (isAwake && lastStartedTransition.to == KeyguardState.DOZING) { - startTransitionTo( - if (occluded) { - KeyguardState.OCCLUDED - } else if (isIdleOnCommunal) { - KeyguardState.GLANCEABLE_HUB - } else { - KeyguardState.LOCKSCREEN - } - ) - } - } - } - } - - private fun listenForDozingToGone() { - scope.launch { - keyguardInteractor.biometricUnlockState - .sample(startedKeyguardTransitionStep, ::Pair) - .collect { (biometricUnlockState, lastStartedTransition) -> - if ( - lastStartedTransition.to == KeyguardState.DOZING && - isWakeAndUnlock(biometricUnlockState) - ) { - startTransitionTo(KeyguardState.GONE) + .collect { + ( + isAwake, + biometricUnlockState, + lastStartedTransition, + occluded, + isIdleOnCommunal, + canDismissLockScreen, + primaryBouncerShowing) -> + if (!(isAwake && lastStartedTransition.to == KeyguardState.DOZING)) { + return@collect } + startTransitionTo( + if (isWakeAndUnlock(biometricUnlockState)) { + KeyguardState.GONE + } else if (canDismissLockScreen) { + KeyguardState.GONE + } else if (primaryBouncerShowing) { + KeyguardState.PRIMARY_BOUNCER + } else if (occluded) { + KeyguardState.OCCLUDED + } else if (isIdleOnCommunal) { + KeyguardState.GLANCEABLE_HUB + } else { + KeyguardState.LOCKSCREEN + } + ) } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt index acfa107cc1f1..28282c630b8f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt @@ -27,6 +27,7 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine import com.android.systemui.util.kotlin.Utils.Companion.toTriple import com.android.systemui.util.kotlin.sample import javax.inject.Inject @@ -57,7 +58,8 @@ constructor( override fun start() { listenForDreamingToOccluded() - listenForDreamingToGone() + listenForDreamingToGoneWhenDismissable() + listenForDreamingToGoneFromBiometricUnlock() listenForDreamingToAodOrDozing() listenForTransitionToCamera(scope, keyguardInteractor) listenForDreamingToGlanceableHub() @@ -101,7 +103,29 @@ constructor( } } - private fun listenForDreamingToGone() { + private fun listenForDreamingToGoneWhenDismissable() { + scope.launch { + keyguardInteractor.isAbleToDream + .sampleCombine( + keyguardInteractor.isKeyguardShowing, + keyguardInteractor.isKeyguardDismissible, + startedKeyguardTransitionStep, + ) + .collect { + (isDreaming, isKeyguardShowing, isKeyguardDismissible, lastStartedTransition) -> + if ( + !isDreaming && + lastStartedTransition.to == KeyguardState.DREAMING && + isKeyguardDismissible && + !isKeyguardShowing + ) { + startTransitionTo(KeyguardState.GONE) + } + } + } + } + + private fun listenForDreamingToGoneFromBiometricUnlock() { scope.launch { keyguardInteractor.biometricUnlockState .sample(startedKeyguardTransitionStep, ::Pair) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt index 6cb1eb493db3..744301019dfc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt @@ -20,7 +20,7 @@ import android.animation.ValueAnimator import com.android.app.animation.Interpolators import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.CommunalTransitionProgress -import com.android.systemui.communal.shared.model.CommunalSceneKey +import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState @@ -55,9 +55,9 @@ constructor( ) { val toScene = if (fromState == KeyguardState.GLANCEABLE_HUB) { - CommunalSceneKey.Blank + CommunalScenes.Blank } else { - CommunalSceneKey.Communal + CommunalScenes.Communal } var transitionId: UUID? = null diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt index e5bb5a081b70..d2a7486eed0b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt @@ -22,8 +22,6 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.KeyguardRepository import javax.inject.Inject import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow /** Encapsulates business-logic specifically related to the keyguard bottom area. */ @SysUISingleton @@ -37,12 +35,10 @@ constructor( /** The amount of alpha for the UI components of the bottom area. */ val alpha: Flow<Float> = repository.bottomAreaAlpha /** The position of the keyguard clock. */ - private val _clockPosition = MutableStateFlow(Position(0, 0)) - @Deprecated("with migrateClocksToBlueprint()") - val clockPosition: Flow<Position> = _clockPosition.asStateFlow() + val clockPosition: Flow<Position> = repository.clockPosition fun setClockPosition(x: Int, y: Int) { - _clockPosition.value = Position(x, y) + repository.setClockPosition(x, y) } fun setAlpha(alpha: Float) { 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 8b06b85e70c9..5410b10a4b93 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 @@ -27,6 +27,7 @@ import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.common.shared.model.NotificationContainerBounds +import com.android.systemui.common.shared.model.Position import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.KeyguardRepository @@ -42,7 +43,7 @@ import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlags -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.statusbar.CommandQueue import com.android.systemui.util.kotlin.sample @@ -231,6 +232,9 @@ constructor( /** The approximate location on the screen of the face unlock sensor, if one is available. */ val faceSensorLocation: Flow<Point?> = repository.faceSensorLocation + /** The position of the keyguard clock. */ + val clockPosition: Flow<Position> = repository.clockPosition + @Deprecated("Use the relevant TransitionViewModel") val keyguardAlpha: Flow<Float> = repository.keyguardAlpha @@ -288,7 +292,7 @@ constructor( sceneInteractorProvider .get() .transitioningTo - .map { it == SceneKey.Lockscreen } + .map { it == Scenes.Lockscreen } .distinctUntilChanged() .flatMapLatest { isTransitioningToLockscreenScene -> if (isTransitioningToLockscreenScene) { @@ -338,6 +342,10 @@ constructor( repository.setQuickSettingsVisible(isVisible) } + fun setClockPosition(x: Int, y: Int) { + repository.setClockPosition(x, y) + } + fun setAlpha(alpha: Float) { repository.setKeyguardAlpha(alpha) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt index a03fa38ec850..d81f1f14158c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt @@ -21,6 +21,8 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.core.LogLevel.VERBOSE import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -37,6 +39,8 @@ constructor( private val keyguardInteractor: KeyguardInteractor, private val logger: KeyguardLogger, private val powerInteractor: PowerInteractor, + private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel, + private val shadeInteractor: ShadeInteractor, ) { fun start() { @@ -47,6 +51,30 @@ constructor( } scope.launch { + sharedNotificationContainerViewModel + .getMaxNotifications { height, useExtraShelfSpace -> height.toInt() } + .collect { logger.log(TAG, VERBOSE, "Notif: max height in px", it) } + } + + scope.launch { + sharedNotificationContainerViewModel.isOnLockscreen.collect { + logger.log(TAG, VERBOSE, "Notif: isOnLockscreen", it) + } + } + + scope.launch { + shadeInteractor.isUserInteracting.collect { + logger.log(TAG, VERBOSE, "Shade: isUserInteracting", it) + } + } + + scope.launch { + sharedNotificationContainerViewModel.isOnLockscreenWithoutShade.collect { + logger.log(TAG, VERBOSE, "Notif: isOnLockscreenWithoutShade", it) + } + } + + scope.launch { keyguardInteractor.primaryBouncerShowing.collect { logger.log(TAG, VERBOSE, "Primary bouncer showing", it) } @@ -75,6 +103,18 @@ constructor( } scope.launch { + keyguardInteractor.isKeyguardDismissible.collect { + logger.log(TAG, VERBOSE, "isDismissible", it) + } + } + + scope.launch { + keyguardInteractor.isKeyguardShowing.collect { + logger.log(TAG, VERBOSE, "isShowing", it) + } + } + + scope.launch { keyguardInteractor.dozeTransitionModel.collect { logger.log(TAG, VERBOSE, "Doze transition", it) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt index 7f0b483919b3..601fbfaf1b64 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/ComposeLockscreen.kt @@ -17,7 +17,6 @@ package com.android.systemui.keyguard.shared import com.android.systemui.Flags -import com.android.systemui.compose.ComposeFacade import com.android.systemui.flags.FlagToken import com.android.systemui.flags.RefactorFlagUtils @@ -34,7 +33,7 @@ object ComposeLockscreen { /** Is the refactor enabled */ @JvmStatic inline val isEnabled - get() = Flags.composeLockscreen() && ComposeFacade.isComposeAvailable() + get() = Flags.composeLockscreen() /** * Called to ensure code is only run when the flag is enabled. This protects users from the diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt index 462d5e6568b0..4812e03ec3f6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt @@ -42,7 +42,7 @@ import kotlin.math.max import kotlinx.coroutines.launch private const val TAG = "KeyguardBlueprintViewBinder" -private const val DEBUG = true +private const val DEBUG = false @SysUISingleton class KeyguardBlueprintViewBinder diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt index e67a3245bc85..98bebd091f1a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt @@ -21,8 +21,6 @@ import android.content.Context import android.view.View import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet -import androidx.constraintlayout.widget.ConstraintSet.BOTTOM -import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import com.android.systemui.Flags.migrateClocksToBlueprint import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.view.KeyguardRootView @@ -39,18 +37,13 @@ constructor( private val clockViewModel: KeyguardClockViewModel, ) : KeyguardSection() { private lateinit var burnInLayer: AodBurnInLayer - private lateinit var emptyView: View override fun addViews(constraintLayout: ConstraintLayout) { if (!migrateClocksToBlueprint()) { return } // The burn-in layer requires at least 1 view at all times - emptyView = - View(context, null).apply { - id = R.id.burn_in_layer_empty_view - visibility = View.GONE - } + val emptyView = View(context, null).apply { id = View.generateViewId() } constraintLayout.addView(emptyView) burnInLayer = AodBurnInLayer(context).apply { @@ -77,13 +70,6 @@ constructor( if (!migrateClocksToBlueprint()) { return } - - constraintSet.apply { - // The empty view should not occupy any space - constrainHeight(R.id.burn_in_layer_empty_view, 1) - constrainWidth(R.id.burn_in_layer_empty_view, 0) - connect(R.id.burn_in_layer_empty_view, BOTTOM, PARENT_ID, BOTTOM) - } } override fun removeViews(constraintLayout: ConstraintLayout) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt index 4ddd039edbaa..6184c82cbff7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt @@ -199,7 +199,7 @@ class ClockSizeTransition( private val TRANSITION_PROPERTIES = arrayOf(PROP_VISIBILITY, PROP_ALPHA, PROP_BOUNDS, SMARTSPACE_BOUNDS) - private val DEBUG = true + private val DEBUG = false private val TAG = VisibilityBoundsTransition::class.simpleName!! } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt index 62fc1da5a444..7be390a4526f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt @@ -18,7 +18,6 @@ package com.android.systemui.keyguard.ui.viewmodel -import android.util.Log import android.util.MathUtils import com.android.app.animation.Interpolators import com.android.keyguard.KeyguardClockSwitch @@ -63,8 +62,6 @@ constructor( private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel, private val keyguardClockViewModel: KeyguardClockViewModel, ) { - private val TAG = "AodBurnInViewModel" - /** Horizontal translation for elements that need to apply anti-burn-in tactics. */ fun translationX( params: BurnInParameters, @@ -74,17 +71,8 @@ constructor( /** Vertical translation for elements that need to apply anti-burn-in tactics. */ fun translationY( - burnInParams: BurnInParameters, + params: BurnInParameters, ): Flow<Float> { - val params = - if (burnInParams.minViewY < burnInParams.topInset) { - // minViewY should never be below the inset. Correct it if needed - Log.w(TAG, "minViewY is below topInset: $burnInParams") - burnInParams.copy(minViewY = burnInParams.topInset) - } else { - burnInParams - } - return configurationInteractor .dimensionPixelSize(R.dimen.keyguard_enter_from_top_translation_y) .flatMapLatest { enterFromTopAmount -> @@ -137,10 +125,7 @@ constructor( keyguardTransitionInteractor.dozeAmountTransition.map { Interpolators.FAST_OUT_SLOW_IN.getInterpolation(it.value) }, - burnInInteractor.burnIn( - xDimenResourceId = R.dimen.burn_in_prevention_offset_x, - yDimenResourceId = R.dimen.burn_in_prevention_offset_y - ) + burnInInteractor.keyguardBurnIn, ) { interpolated, burnIn -> val useScaleOnly = (clockController(params.clockControllerProvider) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt index b92a9a08987a..a9eec18319c3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt @@ -16,13 +16,16 @@ package com.android.systemui.keyguard.ui.viewmodel +import android.util.MathUtils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow /** Breaks down AOD->GONE transition into discrete steps for corresponding views to consume. */ @ExperimentalCoroutinesApi @@ -40,5 +43,15 @@ constructor( to = KeyguardState.GONE, ) + fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> { + var startAlpha = 1f + return transitionAnimation.sharedFlow( + duration = 200.milliseconds, + onStart = { startAlpha = viewState.alpha() }, + onStep = { MathUtils.lerp(startAlpha, 0f, it) }, + onFinish = { 0f }, + ) + } + override val deviceEntryParentViewAlpha = transitionAnimation.immediatelyTransitionTo(0f) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt index 31e809356e02..bd19c8006e23 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt @@ -180,7 +180,7 @@ constructor( } private val isUnlocked: Flow<Boolean> = - deviceEntryInteractor.isUnlocked.flatMapLatest { isUnlocked -> + keyguardInteractor.isKeyguardDismissible.flatMapLatest { isUnlocked -> if (!isUnlocked) { flowOf(false) } else { @@ -197,7 +197,7 @@ constructor( val iconType: Flow<DeviceEntryIconView.IconType> = combine( deviceEntryUdfpsInteractor.isListeningForUdfps, - keyguardInteractor.isKeyguardDismissible, + isUnlocked, ) { isListeningForUdfps, isUnlocked -> if (isListeningForUdfps) { DeviceEntryIconView.IconType.FINGERPRINT diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt index fca1604946e1..8851a51f15b0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt @@ -16,12 +16,14 @@ package com.android.systemui.keyguard.ui.viewmodel +import android.util.MathUtils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor.Companion.TO_GONE_DURATION import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -41,6 +43,16 @@ constructor( to = KeyguardState.GONE, ) + fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> { + var startAlpha = 1f + return transitionAnimation.sharedFlow( + duration = 200.milliseconds, + onStart = { startAlpha = viewState.alpha() }, + onStep = { MathUtils.lerp(startAlpha, 0f, it) }, + onFinish = { 0f }, + ) + } + override val deviceEntryParentViewAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt index e35e06533f8c..6458edaecd9e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt @@ -17,14 +17,10 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.Flags.keyguardBottomAreaRefactor -import com.android.systemui.Flags.migrateClocksToBlueprint import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.doze.util.BurnInHelperWrapper -import com.android.systemui.keyguard.domain.interactor.BurnInInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.keyguard.shared.model.BurnInModel -import com.android.systemui.res.R import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine @@ -36,10 +32,9 @@ class KeyguardIndicationAreaViewModel @Inject constructor( private val keyguardInteractor: KeyguardInteractor, - private val bottomAreaInteractor: KeyguardBottomAreaInteractor, + bottomAreaInteractor: KeyguardBottomAreaInteractor, keyguardBottomAreaViewModel: KeyguardBottomAreaViewModel, private val burnInHelperWrapper: BurnInHelperWrapper, - private val burnInInteractor: BurnInInteractor, private val shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel, configurationInteractor: ConfigurationInteractor, ) { @@ -68,37 +63,24 @@ constructor( } .distinctUntilChanged() } - - private val burnIn: Flow<BurnInModel> = - burnInInteractor - .burnIn( - xDimenResourceId = R.dimen.burn_in_prevention_offset_x, - yDimenResourceId = R.dimen.default_burn_in_prevention_offset, - ) - .distinctUntilChanged() - /** An observable for the x-offset by which the indication area should be translated. */ val indicationAreaTranslationX: Flow<Float> = - if (migrateClocksToBlueprint() || keyguardBottomAreaRefactor()) { - burnIn.map { it.translationX.toFloat() } + if (keyguardBottomAreaRefactor()) { + keyguardInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged() } else { bottomAreaInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged() } /** Returns an observable for the y-offset by which the indication area should be translated. */ fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> { - return if (migrateClocksToBlueprint()) { - burnIn.map { it.translationY.toFloat() } - } else { - keyguardInteractor.dozeAmount - .map { dozeAmount -> - dozeAmount * - (burnInHelperWrapper.burnInOffset( - /* amplitude = */ defaultBurnInOffset * 2, - /* xAxis= */ false, - ) - defaultBurnInOffset) - } - .distinctUntilChanged() - } + return keyguardInteractor.dozeAmount + .map { dozeAmount -> + dozeAmount * + (burnInHelperWrapper.burnInOffset( + /* amplitude = */ defaultBurnInOffset * 2, + /* xAxis= */ false, + ) - defaultBurnInOffset) + } + .distinctUntilChanged() } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index bdcaf0951c5b..1760b927a6cd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -31,6 +31,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN +import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING import com.android.systemui.keyguard.shared.model.TransitionState.STARTED import com.android.systemui.keyguard.ui.StateToValue @@ -70,8 +71,11 @@ constructor( private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor, private val alternateBouncerToGoneTransitionViewModel: AlternateBouncerToGoneTransitionViewModel, + private val aodToGoneTransitionViewModel: AodToGoneTransitionViewModel, private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel, + private val dozingToGoneTransitionViewModel: DozingToGoneTransitionViewModel, private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel, + private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel, private val glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel, private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel, @@ -120,6 +124,27 @@ constructor( } .distinctUntilChanged() + /** + * Keyguard should not show while the communal hub is fully visible. This check is added since + * at the moment, closing the notification shade will cause the keyguard alpha to be set back to + * 1. Also ensure keyguard is never visible when GONE. + */ + private val hideKeyguard: Flow<Boolean> = + combine( + communalInteractor.isIdleOnCommunal, + keyguardTransitionInteractor + .transitionValue(GONE) + .map { it == 1f } + .onStart { emit(false) }, + keyguardTransitionInteractor + .transitionValue(OCCLUDED) + .map { it == 1f } + .onStart { emit(false) }, + ) { isIdleOnCommunal, isGone, isOccluded -> + isIdleOnCommunal || isGone || isOccluded + } + .distinctUntilChanged() + /** Last point that the root view was tapped */ val lastRootViewTapPosition: Flow<Point?> = keyguardInteractor.lastRootViewTapPosition @@ -136,20 +161,18 @@ constructor( /** An observable for the alpha level for the entire keyguard root view. */ fun alpha(viewState: ViewStateAccessor): Flow<Float> { return combine( - communalInteractor.isIdleOnCommunal, - keyguardTransitionInteractor - .transitionValue(GONE) - .map { it == 1f } - .onStart { emit(false) } - .distinctUntilChanged(), + hideKeyguard, // The transitions are mutually exclusive, so they are safe to merge to get the last // value emitted by any of them. Do not add flows that cannot make this guarantee. merge( alphaOnShadeExpansion, keyguardInteractor.dismissAlpha.filterNotNull(), alternateBouncerToGoneTransitionViewModel.lockscreenAlpha, + aodToGoneTransitionViewModel.lockscreenAlpha(viewState), aodToLockscreenTransitionViewModel.lockscreenAlpha(viewState), + dozingToGoneTransitionViewModel.lockscreenAlpha(viewState), dozingToLockscreenTransitionViewModel.lockscreenAlpha, + dreamingToLockscreenTransitionViewModel.lockscreenAlpha, glanceableHubToLockscreenTransitionViewModel.keyguardAlpha, goneToAodTransitionViewModel.enterFromTopAnimationAlpha, goneToDozingTransitionViewModel.lockscreenAlpha, @@ -167,12 +190,8 @@ constructor( primaryBouncerToLockscreenTransitionViewModel.lockscreenAlpha, ) .onStart { emit(1f) } - ) { isIdleOnCommunal, gone, alpha -> - if (isIdleOnCommunal || gone) { - // Keyguard should not show while the communal hub is fully visible. This check - // is added since at the moment, closing the notification shade will cause the - // keyguard alpha to be set back to 1. Also ensure keyguard is never visible - // when GONE. + ) { hideKeyguard, alpha -> + if (hideKeyguard) { 0f } else { alpha diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt index 9afe8fcd93d0..b60e99973348 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt @@ -16,11 +16,12 @@ package com.android.systemui.keyguard.ui.viewmodel +import com.android.compose.animation.scene.SceneKey import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -51,13 +52,13 @@ constructor( ) private fun upDestinationSceneKey(isUnlocked: Boolean): SceneKey { - return if (isUnlocked) SceneKey.Gone else SceneKey.Bouncer + return if (isUnlocked) Scenes.Gone else Scenes.Bouncer } /** The key of the scene we should switch to when swiping left. */ val leftDestinationSceneKey: StateFlow<SceneKey?> = communalInteractor.isCommunalAvailable - .map { available -> if (available) SceneKey.Communal else null } + .map { available -> if (available) Scenes.Communal else null } .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), 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 8b7c85b65824..f2013bec19c7 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -546,7 +546,7 @@ public class LogModule { @SysUISingleton @KeyguardLog public static LogBuffer provideKeyguardLogBuffer(LogBufferFactory factory) { - return factory.create("KeyguardLog", 250); + return factory.create("KeyguardLog", 500); } /** diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt index f1cade7512e2..0b19bab5c7c5 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt @@ -24,12 +24,14 @@ import android.media.projection.ReviewGrantedConsentResult import android.os.RemoteException import android.os.ServiceManager import android.util.Log +import android.window.WindowContainerToken +import javax.inject.Inject /** * Helper class that handles the media projection service related actions. It simplifies invoking * the MediaProjectionManagerService and updating the permission consent. */ -class MediaProjectionServiceHelper { +class MediaProjectionServiceHelper @Inject constructor() { companion object { private const val TAG = "MediaProjectionServiceHelper" private val service = @@ -90,4 +92,16 @@ class MediaProjectionServiceHelper { } } } + + /** Updates the projected task to the task that has a matching [WindowContainerToken]. */ + fun updateTaskRecordingSession(token: WindowContainerToken): Boolean { + return try { + true + // TODO: actually call the service once it is implemented + // service.updateTaskRecordingSession(token) + } catch (e: RemoteException) { + Log.e(TAG, "Unable to updateTaskRecordingSession", e) + false + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialog.kt deleted file mode 100644 index fc452288f86d..000000000000 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialog.kt +++ /dev/null @@ -1,33 +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.mediaprojection.devicepolicy - -import android.content.Context -import com.android.systemui.res.R -import com.android.systemui.statusbar.phone.SystemUIDialog - -/** Dialog that shows that screen capture is disabled on this device. */ -class ScreenCaptureDisabledDialog(context: Context) : SystemUIDialog(context) { - - init { - setTitle(context.getString(R.string.screen_capturing_disabled_by_policy_dialog_title)) - setMessage( - context.getString(R.string.screen_capturing_disabled_by_policy_dialog_description) - ) - setIcon(R.drawable.ic_cast) - setButton(BUTTON_POSITIVE, context.getString(android.R.string.ok)) { _, _ -> cancel() } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialogDelegate.kt new file mode 100644 index 000000000000..8aed535956b7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialogDelegate.kt @@ -0,0 +1,44 @@ +/* + * 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.mediaprojection.devicepolicy + +import android.content.DialogInterface.BUTTON_POSITIVE +import android.content.res.Resources +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.res.R +import com.android.systemui.statusbar.phone.SystemUIDialog +import javax.inject.Inject + +/** Dialog that shows that screen capture is disabled on this device. */ +class ScreenCaptureDisabledDialogDelegate @Inject constructor( + @Main private val resources: Resources, + private val systemUIDialogFactory: SystemUIDialog.Factory +) : SystemUIDialog.Delegate { + + override fun createDialog(): SystemUIDialog { + val dialog = systemUIDialogFactory.create(this) + dialog.setTitle(resources.getString(R.string.screen_capturing_disabled_by_policy_dialog_title)) + dialog.setMessage( + resources.getString(R.string.screen_capturing_disabled_by_policy_dialog_description) + ) + dialog.setIcon(R.drawable.ic_cast) + dialog.setButton(BUTTON_POSITIVE, resources.getString(android.R.string.ok)) { + _, _ -> dialog.cancel() + } + + return dialog + } +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java index 8b034b293dcb..17f9cafcb650 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java @@ -59,7 +59,7 @@ import com.android.systemui.mediaprojection.MediaProjectionServiceHelper; import com.android.systemui.mediaprojection.SessionCreationSource; import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver; -import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog; +import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate; import com.android.systemui.res.R; import com.android.systemui.statusbar.phone.AlertDialogWithDelegate; import com.android.systemui.statusbar.phone.SystemUIDialog; @@ -79,6 +79,7 @@ public class MediaProjectionPermissionActivity extends Activity private final Lazy<ScreenCaptureDevicePolicyResolver> mScreenCaptureDevicePolicyResolver; private final StatusBarManager mStatusBarManager; private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger; + private final ScreenCaptureDisabledDialogDelegate mScreenCaptureDisabledDialogDelegate; private String mPackageName; private int mUid; @@ -93,14 +94,17 @@ public class MediaProjectionPermissionActivity extends Activity private boolean mUserSelectingTask = false; @Inject - public MediaProjectionPermissionActivity(FeatureFlags featureFlags, + public MediaProjectionPermissionActivity( + FeatureFlags featureFlags, Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver, StatusBarManager statusBarManager, - MediaProjectionMetricsLogger mediaProjectionMetricsLogger) { + MediaProjectionMetricsLogger mediaProjectionMetricsLogger, + ScreenCaptureDisabledDialogDelegate screenCaptureDisabledDialogDelegate) { mFeatureFlags = featureFlags; mScreenCaptureDevicePolicyResolver = screenCaptureDevicePolicyResolver; mStatusBarManager = statusBarManager; mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger; + mScreenCaptureDisabledDialogDelegate = screenCaptureDisabledDialogDelegate; } @Override @@ -315,10 +319,7 @@ public class MediaProjectionPermissionActivity extends Activity final UserHandle hostUserHandle = getHostUserHandle(); if (mScreenCaptureDevicePolicyResolver.get() .isScreenCaptureCompletelyDisabled(hostUserHandle)) { - // Using application context for the dialog, instead of the activity context, so we get - // the correct screen width when in split screen. - Context dialogContext = getApplicationContext(); - AlertDialog dialog = new ScreenCaptureDisabledDialog(dialogContext); + AlertDialog dialog = mScreenCaptureDisabledDialogDelegate.createDialog(); setUpDialog(dialog); dialog.show(); return true; diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt index 9938f11e5d4c..cfbcaf91b791 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt @@ -16,11 +16,11 @@ package com.android.systemui.mediaprojection.taskswitcher.data.model -import android.app.TaskInfo +import android.app.ActivityManager.RunningTaskInfo /** Represents the state of media projection. */ sealed interface MediaProjectionState { object NotProjecting : MediaProjectionState object EntireScreen : MediaProjectionState - data class SingleTask(val task: TaskInfo) : MediaProjectionState + data class SingleTask(val task: RunningTaskInfo) : MediaProjectionState } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt index 492d482459d6..4ff54d4eae65 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt @@ -17,10 +17,14 @@ package com.android.systemui.mediaprojection.taskswitcher.data.repository import android.app.ActivityManager.RunningTaskInfo +import android.app.ActivityOptions +import android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED import android.app.ActivityTaskManager +import android.app.IActivityTaskManager import android.app.TaskStackListener import android.os.IBinder import android.util.Log +import android.view.Display import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton @@ -40,11 +44,24 @@ import kotlinx.coroutines.withContext class ActivityTaskManagerTasksRepository @Inject constructor( - private val activityTaskManager: ActivityTaskManager, + private val activityTaskManager: IActivityTaskManager, @Application private val applicationScope: CoroutineScope, @Background private val backgroundDispatcher: CoroutineDispatcher, ) : TasksRepository { + override suspend fun launchRecentTask(taskInfo: RunningTaskInfo) { + withContext(backgroundDispatcher) { + val activityOptions = ActivityOptions.makeBasic() + activityOptions.pendingIntentBackgroundActivityStartMode = + MODE_BACKGROUND_ACTIVITY_START_ALLOWED + activityOptions.launchDisplayId = taskInfo.displayId + activityTaskManager.startActivityFromRecents( + taskInfo.taskId, + activityOptions.toBundle() + ) + } + } + override suspend fun findRunningTaskFromWindowContainerToken( windowContainerToken: IBinder ): RunningTaskInfo? = @@ -53,7 +70,14 @@ constructor( } private suspend fun getRunningTasks(): List<RunningTaskInfo> = - withContext(backgroundDispatcher) { activityTaskManager.getTasks(Integer.MAX_VALUE) } + withContext(backgroundDispatcher) { + activityTaskManager.getTasks( + /* maxNum = */ Integer.MAX_VALUE, + /* filterForVisibleRecents = */ false, + /* keepIntentExtra = */ false, + /* displayId = */ Display.INVALID_DISPLAY + ) + } override val foregroundTask: Flow<RunningTaskInfo> = conflatedCallbackFlow { diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt index 6480a47e8ea2..74d19921c706 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt @@ -16,6 +16,7 @@ package com.android.systemui.mediaprojection.taskswitcher.data.repository +import android.app.ActivityManager.RunningTaskInfo import android.media.projection.MediaProjectionInfo import android.media.projection.MediaProjectionManager import android.os.Handler @@ -26,15 +27,19 @@ import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLoggin import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.mediaprojection.MediaProjectionServiceHelper import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext @SysUISingleton class MediaProjectionManagerRepository @@ -43,9 +48,21 @@ constructor( private val mediaProjectionManager: MediaProjectionManager, @Main private val handler: Handler, @Application private val applicationScope: CoroutineScope, + @Background private val backgroundDispatcher: CoroutineDispatcher, private val tasksRepository: TasksRepository, + private val mediaProjectionServiceHelper: MediaProjectionServiceHelper, ) : MediaProjectionRepository { + override suspend fun switchProjectedTask(task: RunningTaskInfo) { + withContext(backgroundDispatcher) { + if (mediaProjectionServiceHelper.updateTaskRecordingSession(task.token)) { + Log.d(TAG, "Successfully switched projected task") + } else { + Log.d(TAG, "Failed to switch projected task") + } + } + } + override val mediaProjectionState: Flow<MediaProjectionState> = conflatedCallbackFlow { val callback = @@ -82,7 +99,9 @@ constructor( } val matchingTask = tasksRepository.findRunningTaskFromWindowContainerToken( - checkNotNull(session.tokenToRecord)) ?: return MediaProjectionState.EntireScreen + checkNotNull(session.tokenToRecord) + ) + ?: return MediaProjectionState.EntireScreen return MediaProjectionState.SingleTask(matchingTask) } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt index 5bec6925babe..e495466008ce 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt @@ -16,12 +16,16 @@ package com.android.systemui.mediaprojection.taskswitcher.data.repository +import android.app.ActivityManager.RunningTaskInfo import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState import kotlinx.coroutines.flow.Flow /** Represents a repository to retrieve and change data related to media projection. */ interface MediaProjectionRepository { + /** Switches the task that should be projected. */ + suspend fun switchProjectedTask(task: RunningTaskInfo) + /** Represents the current [MediaProjectionState]. */ val mediaProjectionState: Flow<MediaProjectionState> } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.kt deleted file mode 100644 index 544eb6b99d4f..000000000000 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.kt +++ /dev/null @@ -1,33 +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.mediaprojection.taskswitcher.data.repository - -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState -import javax.inject.Inject -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.emptyFlow - -/** - * No-op implementation of [MediaProjectionRepository] that does nothing. Currently used as a - * placeholder, while the real implementation is not completed. - */ -@SysUISingleton -class NoOpMediaProjectionRepository @Inject constructor() : MediaProjectionRepository { - - override val mediaProjectionState: Flow<MediaProjectionState> = emptyFlow() -} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt index 6a535e4ecc50..9ef42b4de45c 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt @@ -23,6 +23,8 @@ import kotlinx.coroutines.flow.Flow /** Repository responsible for retrieving data related to running tasks. */ interface TasksRepository { + suspend fun launchRecentTask(taskInfo: RunningTaskInfo) + /** * Tries to find a [RunningTaskInfo] with a matching window container token. Returns `null` when * no matching task was found. diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt index fc5cf7d75bdf..eb9e6a5de057 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt @@ -16,6 +16,7 @@ package com.android.systemui.mediaprojection.taskswitcher.domain.interactor +import android.app.ActivityManager.RunningTaskInfo import android.app.TaskInfo import android.content.Intent import android.util.Log @@ -37,10 +38,18 @@ import kotlinx.coroutines.flow.map class TaskSwitchInteractor @Inject constructor( - mediaProjectionRepository: MediaProjectionRepository, + private val mediaProjectionRepository: MediaProjectionRepository, private val tasksRepository: TasksRepository, ) { + suspend fun switchProjectedTask(task: RunningTaskInfo) { + mediaProjectionRepository.switchProjectedTask(task) + } + + suspend fun goBackToTask(task: RunningTaskInfo) { + tasksRepository.launchRecentTask(task) + } + /** * Emits a stream of changes to the state of task switching, in the context of media projection. */ diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt index cd1258ed6aa8..caabc64efae1 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt @@ -16,7 +16,7 @@ package com.android.systemui.mediaprojection.taskswitcher.domain.model -import android.app.TaskInfo +import android.app.ActivityManager.RunningTaskInfo /** Represents tha state of task switching in the context of single task media projection. */ sealed interface TaskSwitchState { @@ -25,6 +25,8 @@ sealed interface TaskSwitchState { /** The foreground task is the same as the task that is currently being projected. */ object TaskUnchanged : TaskSwitchState /** The foreground task is a different one to the task it currently being projected. */ - data class TaskSwitched(val projectedTask: TaskInfo, val foregroundTask: TaskInfo) : - TaskSwitchState + data class TaskSwitched( + val projectedTask: RunningTaskInfo, + val foregroundTask: RunningTaskInfo + ) : TaskSwitchState } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt index 7840da960a83..dab7439f0f0c 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt @@ -16,23 +16,25 @@ package com.android.systemui.mediaprojection.taskswitcher.ui +import android.app.ActivityManager.RunningTaskInfo import android.app.Notification -import android.app.NotificationChannel import android.app.NotificationManager +import android.app.PendingIntent import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.Parcelable import android.util.Log -import com.android.systemui.res.R +import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState.NotShowing import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState.Showing import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel +import com.android.systemui.res.R import com.android.systemui.util.NotificationChannels import javax.inject.Inject -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.launch /** Coordinator responsible for showing/hiding the task switcher notification. */ @@ -43,32 +45,54 @@ constructor( private val context: Context, private val notificationManager: NotificationManager, @Application private val applicationScope: CoroutineScope, - @Main private val mainDispatcher: CoroutineDispatcher, private val viewModel: TaskSwitcherNotificationViewModel, + private val broadcastDispatcher: BroadcastDispatcher, ) { + fun start() { applicationScope.launch { - viewModel.uiState.flowOn(mainDispatcher).collect { uiState -> - Log.d(TAG, "uiState -> $uiState") - when (uiState) { - is Showing -> showNotification() - is NotShowing -> hideNotification() + launch { + viewModel.uiState.collect { uiState -> + Log.d(TAG, "uiState -> $uiState") + when (uiState) { + is Showing -> showNotification(uiState) + is NotShowing -> hideNotification() + } } } + launch { + broadcastDispatcher + .broadcastFlow(IntentFilter(SWITCH_ACTION)) { intent, _ -> + intent.requireParcelableExtra<RunningTaskInfo>(EXTRA_ACTION_TASK) + } + .collect { task: RunningTaskInfo -> + Log.d(TAG, "Switch action triggered: $task") + viewModel.onSwitchTaskClicked(task) + } + } + launch { + broadcastDispatcher + .broadcastFlow(IntentFilter(GO_BACK_ACTION)) { intent, _ -> + intent.requireParcelableExtra<RunningTaskInfo>(EXTRA_ACTION_TASK) + } + .collect { task -> + Log.d(TAG, "Go back action triggered: $task") + viewModel.onGoBackToTaskClicked(task) + } + } } } - private fun showNotification() { - notificationManager.notify(TAG, NOTIFICATION_ID, createNotification()) + private fun showNotification(uiState: Showing) { + notificationManager.notify(TAG, NOTIFICATION_ID, createNotification(uiState)) } - private fun createNotification(): Notification { - // TODO(b/286201261): implement actions + private fun createNotification(uiState: Showing): Notification { val actionSwitch = Notification.Action.Builder( /* icon = */ null, context.getString(R.string.media_projection_task_switcher_action_switch), - /* intent = */ null + createActionPendingIntent(action = SWITCH_ACTION, task = uiState.foregroundTask) ) .build() @@ -76,34 +100,40 @@ constructor( Notification.Action.Builder( /* icon = */ null, context.getString(R.string.media_projection_task_switcher_action_back), - /* intent = */ null + createActionPendingIntent(action = GO_BACK_ACTION, task = uiState.projectedTask) ) .build() - - val channel = - NotificationChannel( - NotificationChannels.HINTS, - context.getString(R.string.media_projection_task_switcher_notification_channel), - NotificationManager.IMPORTANCE_HIGH - ) - notificationManager.createNotificationChannel(channel) - return Notification.Builder(context, channel.id) + return Notification.Builder(context, NotificationChannels.ALERTS) .setSmallIcon(R.drawable.qs_screen_record_icon_on) .setAutoCancel(true) .setContentText(context.getString(R.string.media_projection_task_switcher_text)) .addAction(actionSwitch) .addAction(actionBack) - .setPriority(Notification.PRIORITY_HIGH) - .setDefaults(Notification.DEFAULT_VIBRATE) .build() } private fun hideNotification() { - notificationManager.cancel(NOTIFICATION_ID) + notificationManager.cancel(TAG, NOTIFICATION_ID) } + private fun createActionPendingIntent(action: String, task: RunningTaskInfo) = + PendingIntent.getBroadcast( + context, + /* requestCode= */ 0, + Intent(action).apply { putExtra(EXTRA_ACTION_TASK, task) }, + /* flags= */ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + ) + companion object { private const val TAG = "TaskSwitchNotifCoord" private const val NOTIFICATION_ID = 5566 + + private const val EXTRA_ACTION_TASK = "extra_task" + + private const val SWITCH_ACTION = "com.android.systemui.mediaprojection.SWITCH_TASK" + private const val GO_BACK_ACTION = "com.android.systemui.mediaprojection.GO_BACK" } } + +private fun <T : Parcelable> Intent.requireParcelableExtra(key: String) = + getParcelableExtra<T>(key)!! diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt index 21aee72d17ae..f307761a1875 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt @@ -16,7 +16,7 @@ package com.android.systemui.mediaprojection.taskswitcher.ui.model -import android.app.TaskInfo +import android.app.ActivityManager.RunningTaskInfo /** Represents the UI state for the task switcher notification. */ sealed interface TaskSwitcherNotificationUiState { @@ -24,7 +24,7 @@ sealed interface TaskSwitcherNotificationUiState { object NotShowing : TaskSwitcherNotificationUiState /** The notification should be shown. */ data class Showing( - val projectedTask: TaskInfo, - val foregroundTask: TaskInfo, + val projectedTask: RunningTaskInfo, + val foregroundTask: RunningTaskInfo, ) : TaskSwitcherNotificationUiState } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt index d9754d4429d4..d6629e07d87a 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt @@ -16,34 +16,64 @@ package com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel +import android.app.ActivityManager.RunningTaskInfo import android.util.Log +import androidx.annotation.VisibleForTesting +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState import javax.inject.Inject +import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.transformLatest +import kotlinx.coroutines.withContext -class TaskSwitcherNotificationViewModel @Inject constructor(interactor: TaskSwitchInteractor) { +class TaskSwitcherNotificationViewModel +@Inject +constructor( + private val interactor: TaskSwitchInteractor, + @Background private val backgroundDispatcher: CoroutineDispatcher, +) { val uiState: Flow<TaskSwitcherNotificationUiState> = - interactor.taskSwitchChanges.map { taskSwitchChange -> - Log.d(TAG, "taskSwitchChange: $taskSwitchChange") - when (taskSwitchChange) { - is TaskSwitchState.TaskSwitched -> { - TaskSwitcherNotificationUiState.Showing( - projectedTask = taskSwitchChange.projectedTask, - foregroundTask = taskSwitchChange.foregroundTask, - ) + interactor.taskSwitchChanges + .map { taskSwitchChange -> + Log.d(TAG, "taskSwitchChange: $taskSwitchChange") + when (taskSwitchChange) { + is TaskSwitchState.TaskSwitched -> { + TaskSwitcherNotificationUiState.Showing( + projectedTask = taskSwitchChange.projectedTask, + foregroundTask = taskSwitchChange.foregroundTask, + ) + } + is TaskSwitchState.NotProjectingTask, + is TaskSwitchState.TaskUnchanged -> { + TaskSwitcherNotificationUiState.NotShowing + } } - is TaskSwitchState.NotProjectingTask, - is TaskSwitchState.TaskUnchanged -> { - TaskSwitcherNotificationUiState.NotShowing + } + .transformLatest { uiState -> + emit(uiState) + if (uiState is TaskSwitcherNotificationUiState.Showing) { + delay(NOTIFICATION_MAX_SHOW_DURATION) + Log.d(TAG, "Auto hiding notification after $NOTIFICATION_MAX_SHOW_DURATION") + emit(TaskSwitcherNotificationUiState.NotShowing) } } - } + + suspend fun onSwitchTaskClicked(task: RunningTaskInfo) { + interactor.switchProjectedTask(task) + } + + suspend fun onGoBackToTaskClicked(task: RunningTaskInfo) = + withContext(backgroundDispatcher) { interactor.goBackToTask(task) } companion object { + @VisibleForTesting val NOTIFICATION_MAX_SHOW_DURATION = 5.seconds private const val TAG = "TaskSwitchNotifVM" } } diff --git a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt index 6eb62263eb9a..e7b6e6373f1c 100644 --- a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt +++ b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt @@ -16,11 +16,12 @@ package com.android.systemui.model +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey import com.android.systemui.dagger.SysUISingleton import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag -import com.android.systemui.scene.shared.model.ObservableTransitionState -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE @@ -67,11 +68,11 @@ constructor( */ val EvaluatorByFlag = mapOf<Int, (SceneKey) -> Boolean>( - SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE to { it != SceneKey.Gone }, - SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED to { it == SceneKey.Shade }, - SYSUI_STATE_QUICK_SETTINGS_EXPANDED to { it == SceneKey.QuickSettings }, - SYSUI_STATE_BOUNCER_SHOWING to { it == SceneKey.Bouncer }, - SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING to { it == SceneKey.Lockscreen }, + SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE to { it != Scenes.Gone }, + SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED to { it == Scenes.Shade }, + SYSUI_STATE_QUICK_SETTINGS_EXPANDED to { it == Scenes.QuickSettings }, + SYSUI_STATE_BOUNCER_SHOWING to { it == Scenes.Bouncer }, + SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING to { it == Scenes.Lockscreen }, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt b/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt index c74a71c52260..5c4915689f22 100644 --- a/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt +++ b/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt @@ -25,11 +25,11 @@ import com.android.systemui.dagger.qualifiers.DisplayId * ``` * sysuiState.updateFlags( * displayId, - * SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE to (sceneKey != SceneKey.Gone), - * SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED to (sceneKey == SceneKey.Shade), - * SYSUI_STATE_QUICK_SETTINGS_EXPANDED to (sceneKey == SceneKey.QuickSettings), - * SYSUI_STATE_BOUNCER_SHOWING to (sceneKey == SceneKey.Bouncer), - * SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING to (sceneKey == SceneKey.Lockscreen), + * SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE to (sceneKey != Scenes.Gone), + * SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED to (sceneKey == Scenes.Shade), + * SYSUI_STATE_QUICK_SETTINGS_EXPANDED to (sceneKey == Scenes.QuickSettings), + * SYSUI_STATE_BOUNCER_SHOWING to (sceneKey == Scenes.Bouncer), + * SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING to (sceneKey == Scenes.Lockscreen), * ) * ``` * diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java index 152f193be3f9..9f7d1b34151a 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java @@ -19,6 +19,7 @@ package com.android.systemui.navigationbar; import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE; import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR; + import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE_TAG; import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen; import static com.android.wm.shell.Flags.enableTaskbarNavbarUnification; @@ -28,7 +29,6 @@ import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.hardware.display.DisplayManager; import android.os.Bundle; -import android.os.Handler; import android.os.RemoteException; import android.os.Trace; import android.os.UserHandle; @@ -70,9 +70,11 @@ import dalvik.annotation.optimization.NeverCompile; import java.io.PrintWriter; import java.util.Optional; +import java.util.concurrent.Executor; import javax.inject.Inject; + @SysUISingleton public class NavigationBarControllerImpl implements ConfigurationController.ConfigurationListener, @@ -82,7 +84,7 @@ public class NavigationBarControllerImpl implements private static final String TAG = NavigationBarControllerImpl.class.getSimpleName(); private final Context mContext; - private final Handler mHandler; + private final Executor mExecutor; private final NavigationBarComponent.Factory mNavigationBarComponentFactory; private final SecureSettings mSecureSettings; private final DisplayTracker mDisplayTracker; @@ -119,7 +121,7 @@ public class NavigationBarControllerImpl implements NavigationModeController navigationModeController, SysUiState sysUiFlagsContainer, CommandQueue commandQueue, - @Main Handler mainHandler, + @Main Executor mainExecutor, ConfigurationController configurationController, NavBarHelper navBarHelper, TaskbarDelegate taskbarDelegate, @@ -133,7 +135,7 @@ public class NavigationBarControllerImpl implements SecureSettings secureSettings, DisplayTracker displayTracker) { mContext = context; - mHandler = mainHandler; + mExecutor = mainExecutor; mNavigationBarComponentFactory = navigationBarComponentFactory; mSecureSettings = secureSettings; mDisplayTracker = displayTracker; @@ -193,7 +195,7 @@ public class NavigationBarControllerImpl implements mNavMode = mode; updateAccessibilityButtonModeIfNeeded(); - mHandler.post(() -> { + mExecutor.execute(() -> { // create/destroy nav bar based on nav mode only in unfolded state if (oldMode != mNavMode) { updateNavbarForTaskbar(); diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt index 5b7eb454597c..deb0fed0ffc8 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt @@ -20,14 +20,15 @@ import android.appwidget.AppWidgetManager import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle -import com.android.systemui.compose.ComposeFacade.isComposeAvailable -import com.android.systemui.compose.ComposeFacade.setPeopleSpaceActivityContent +import com.android.compose.theme.PlatformTheme import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.people.ui.compose.PeopleScreen import com.android.systemui.people.ui.view.PeopleViewBinder import com.android.systemui.people.ui.view.PeopleViewBinder.bind import com.android.systemui.people.ui.viewmodel.PeopleViewModel @@ -65,13 +66,11 @@ constructor( } // Set the content of the activity, using either the View or Compose implementation. - if (featureFlags.isEnabled(Flags.COMPOSE_PEOPLE_SPACE) && isComposeAvailable()) { + if (featureFlags.isEnabled(Flags.COMPOSE_PEOPLE_SPACE)) { Log.d(TAG, "Using the Compose implementation of the PeopleSpaceActivity") - setPeopleSpaceActivityContent( - activity = this, - viewModel, - onResult = { finishActivity(it) }, - ) + setContent { + PlatformTheme { PeopleScreen(viewModel, onResult = { finishActivity(it) }) } + } } else { Log.d(TAG, "Using the View implementation of the PeopleSpaceActivity") val view = PeopleViewBinder.create(this) diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java index 741336277119..a000d63a2ee3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java @@ -47,7 +47,6 @@ import com.android.app.animation.Interpolators; import com.android.keyguard.BouncerPanelExpansionCalculator; import com.android.systemui.Dumpable; import com.android.systemui.animation.ShadeInterpolation; -import com.android.systemui.compose.ComposeFacade; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; @@ -301,8 +300,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl private void bindFooterActionsView(View root) { LinearLayout footerActionsView = root.findViewById(R.id.qs_footer_actions); - if (!mFeatureFlags.isEnabled(Flags.COMPOSE_QS_FOOTER_ACTIONS) - || !ComposeFacade.INSTANCE.isComposeAvailable()) { + if (!mFeatureFlags.isEnabled(Flags.COMPOSE_QS_FOOTER_ACTIONS)) { Log.d(TAG, "Binding the View implementation of the QS footer actions"); mFooterActionsView = footerActionsView; mFooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel, @@ -312,7 +310,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl // Compose is available, so let's use the Compose implementation of the footer actions. Log.d(TAG, "Binding the Compose implementation of the QS footer actions"); - View composeView = ComposeFacade.INSTANCE.createFooterActionsView(root.getContext(), + View composeView = QSUtils.createFooterActionsView(root.getContext(), mQSFooterActionsViewModel, mListeningAndVisibilityLifecycleOwner); mFooterActionsView = composeView; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt b/packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt index e42264f24e92..15c3f271469d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt @@ -1,7 +1,13 @@ package com.android.systemui.qs import android.content.Context +import android.view.View +import androidx.lifecycle.LifecycleOwner +import com.android.compose.theme.PlatformTheme +import com.android.compose.ui.platform.DensityAwareComposeView import com.android.internal.policy.SystemBarUtils +import com.android.systemui.qs.footer.ui.compose.FooterActions +import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel import com.android.systemui.util.LargeScreenUtils.shouldUseLargeScreenShadeHeader object QSUtils { @@ -21,4 +27,15 @@ object QSUtils { SystemBarUtils.getQuickQsOffsetHeight(context) } } -}
\ No newline at end of file + + @JvmStatic + fun createFooterActionsView( + context: Context, + viewModel: FooterActionsViewModel, + qsVisibilityLifecycleOwner: LifecycleOwner, + ): View { + return DensityAwareComposeView(context).apply { + setContent { PlatformTheme { FooterActions(viewModel, qsVisibilityLifecycleOwner) } } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java index 0dd0a60b128a..52cf4ec57e1d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java @@ -481,7 +481,8 @@ public class InternetDialogDelegate implements mSecondaryMobileTitleText.setTextAppearance( R.style.TextAppearance_InternetDialog_Active); - TextView mSecondaryMobileSummaryText = mDialogView.requireViewById(R.id.secondary_mobile_summary); + TextView mSecondaryMobileSummaryText = + mDialogView.requireViewById(R.id.secondary_mobile_summary); summary = getMobileNetworkSummary(autoSwitchNonDdsSubId); if (!TextUtils.isEmpty(summary)) { mSecondaryMobileSummaryText.setText( diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractor.kt new file mode 100644 index 000000000000..736e1a5cb9b6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractor.kt @@ -0,0 +1,94 @@ +/* + * 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.qs.tiles.impl.rotation.domain.interactor + +import android.Manifest +import android.content.pm.PackageManager +import android.content.res.Resources +import android.os.UserHandle +import com.android.systemui.camera.data.repository.CameraAutoRotateRepository +import com.android.systemui.camera.data.repository.CameraSensorPrivacyRepository +import com.android.systemui.dagger.qualifiers.Main +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.rotation.domain.model.RotationLockTileModel +import com.android.systemui.statusbar.policy.BatteryController +import com.android.systemui.statusbar.policy.RotationLockController +import com.android.systemui.util.kotlin.isBatteryPowerSaveEnabled +import com.android.systemui.util.kotlin.isRotationLockEnabled +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flowOf + +/** Observes rotation lock state changes providing the [RotationLockTileModel]. */ +class RotationLockTileDataInteractor +@Inject +constructor( + private val rotationLockController: RotationLockController, + private val batteryController: BatteryController, + private val cameraAutoRotateRepository: CameraAutoRotateRepository, + private val cameraSensorPrivacyRepository: CameraSensorPrivacyRepository, + private val packageManager: PackageManager, + @Main private val resources: Resources, +) : QSTileDataInteractor<RotationLockTileModel> { + + override fun tileData( + user: UserHandle, + triggers: Flow<DataUpdateTrigger> + ): Flow<RotationLockTileModel> = + combine( + rotationLockController.isRotationLockEnabled(), + cameraSensorPrivacyRepository.isEnabled(user), + batteryController.isBatteryPowerSaveEnabled(), + cameraAutoRotateRepository.isCameraAutoRotateSettingEnabled(user) + ) { + isRotationLockEnabled, + isCamPrivacySensorEnabled, + isBatteryPowerSaveEnabled, + isCameraAutoRotateEnabled, + -> + RotationLockTileModel( + isRotationLockEnabled, + isCameraRotationEnabled( + isBatteryPowerSaveEnabled, + isCamPrivacySensorEnabled, + isCameraAutoRotateEnabled + ), + ) + } + + override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true) + + private fun hasSufficientPermission(): Boolean { + val rotationPackage: String = packageManager.rotationResolverPackageName + return rotationPackage != null && + packageManager.checkPermission(Manifest.permission.CAMERA, rotationPackage) == + PackageManager.PERMISSION_GRANTED + } + + private fun isCameraRotationEnabled( + isBatteryPowerSaverModeOn: Boolean, + isCameraSensorPrivacyEnabled: Boolean, + isCameraAutoRotateEnabled: Boolean + ): Boolean = + resources.getBoolean(com.android.internal.R.bool.config_allowRotationResolver) && + !isBatteryPowerSaverModeOn && + !isCameraSensorPrivacyEnabled && + hasSufficientPermission() && + isCameraAutoRotateEnabled +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileUserActionInteractor.kt new file mode 100644 index 000000000000..8530926e68e0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileUserActionInteractor.kt @@ -0,0 +1,56 @@ +/* + * 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.qs.tiles.impl.rotation.domain.interactor + +import android.content.Intent +import android.provider.Settings +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.impl.rotation.domain.model.RotationLockTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction +import com.android.systemui.statusbar.policy.RotationLockController +import javax.inject.Inject + +/** Handles rotation lock tile clicks. */ +class RotationLockTileUserActionInteractor +@Inject +constructor( + private val controller: RotationLockController, + private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler, +) : QSTileUserActionInteractor<RotationLockTileModel> { + + override suspend fun handleInput(input: QSTileInput<RotationLockTileModel>) { + with(input) { + when (action) { + is QSTileUserAction.Click -> { + controller.setRotationLocked(!data.isRotationLocked, CALLER) + } + is QSTileUserAction.LongClick -> { + qsTileIntentUserActionHandler.handle( + action.view, + Intent(Settings.ACTION_AUTO_ROTATE_SETTINGS) + ) + } + } + } + } + + companion object { + private const val CALLER = "QSTileUserActionInteractor#handleInput" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKey.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/model/RotationLockTileModel.kt index 87332ae8e084..32e6cb8cb52c 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKey.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/model/RotationLockTileModel.kt @@ -14,13 +14,10 @@ * limitations under the License. */ -package com.android.systemui.scene.shared.model +package com.android.systemui.qs.tiles.impl.rotation.domain.model -/** - * Key for a transition. This can be used to specify which transition spec should be used when - * starting the transition between two scenes. - */ -data class TransitionKey( - val debugName: String, - val identity: Any = Object(), +/** Model for rotation lock tile */ +class RotationLockTileModel( + val isRotationLocked: Boolean, + val isCameraRotationEnabled: Boolean, ) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt new file mode 100644 index 000000000000..070cdef336e5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt @@ -0,0 +1,107 @@ +/* + * 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.qs.tiles.impl.rotation.ui.mapper + +import android.content.res.Resources +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper +import com.android.systemui.qs.tiles.impl.rotation.domain.model.RotationLockTileModel +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.statusbar.policy.DevicePostureController +import javax.inject.Inject + +/** Maps [RotationLockTileModel] to [QSTileState]. */ +class RotationLockTileMapper +@Inject +constructor( + @Main private val resources: Resources, + private val theme: Resources.Theme, + private val devicePostureController: DevicePostureController +) : QSTileDataToStateMapper<RotationLockTileModel> { + override fun map(config: QSTileConfig, data: RotationLockTileModel): QSTileState = + QSTileState.build(resources, theme, config.uiConfig) { + this.label = resources.getString(R.string.quick_settings_rotation_unlocked_label) + this.contentDescription = + resources.getString(R.string.accessibility_quick_settings_rotation) + + if (data.isRotationLocked) { + activationState = QSTileState.ActivationState.INACTIVE + this.secondaryLabel = EMPTY_SECONDARY_STRING + this.icon = { + Icon.Loaded( + resources.getDrawable(R.drawable.qs_auto_rotate_icon_off, theme), + contentDescription = null + ) + } + } else { + activationState = QSTileState.ActivationState.ACTIVE + this.secondaryLabel = + if (data.isCameraRotationEnabled) { + resources.getString(R.string.rotation_lock_camera_rotation_on) + } else { + EMPTY_SECONDARY_STRING + } + this.icon = { + Icon.Loaded( + resources.getDrawable(R.drawable.qs_auto_rotate_icon_on, theme), + contentDescription = null + ) + } + } + if (isDeviceFoldable()) { + this.secondaryLabel = getSecondaryLabelWithPosture(this.activationState) + } + this.stateDescription = this.secondaryLabel + this.sideViewIcon = QSTileState.SideViewIcon.None + supportedActions = + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) + } + + private fun isDeviceFoldable(): Boolean { + val intArray = resources.getIntArray(com.android.internal.R.array.config_foldedDeviceStates) + return intArray.isNotEmpty() + } + + private fun getSecondaryLabelWithPosture(activationState: QSTileState.ActivationState): String { + val stateNames = resources.getStringArray(R.array.tile_states_rotation) + val stateName = + stateNames[ + if (activationState == QSTileState.ActivationState.ACTIVE) ON_INDEX else OFF_INDEX] + val posture = + if ( + devicePostureController.devicePosture == + DevicePostureController.DEVICE_POSTURE_CLOSED + ) + resources.getString(R.string.quick_settings_rotation_posture_folded) + else resources.getString(R.string.quick_settings_rotation_posture_unfolded) + + return resources.getString( + R.string.rotation_tile_with_posture_secondary_label_template, + stateName, + posture + ) + } + + private companion object { + const val EMPTY_SECONDARY_STRING = "" + const val OFF_INDEX = 1 + const val ON_INDEX = 2 + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt index 17454a97f5d2..34f66b85def1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt @@ -17,14 +17,16 @@ package com.android.systemui.qs.ui.viewmodel import androidx.lifecycle.LifecycleOwner +import com.android.compose.animation.scene.Back +import com.android.compose.animation.scene.Swipe +import com.android.compose.animation.scene.SwipeDirection +import com.android.compose.animation.scene.UserAction +import com.android.compose.animation.scene.UserActionResult import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.FooterActionsController import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel import com.android.systemui.qs.ui.adapter.QSSceneAdapter -import com.android.systemui.scene.shared.model.Direction -import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.scene.shared.model.UserAction -import com.android.systemui.scene.shared.model.UserActionResult +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel import java.util.concurrent.atomic.AtomicBoolean @@ -45,13 +47,11 @@ constructor( val destinationScenes = qsSceneAdapter.isCustomizing.map { customizing -> if (customizing) { - mapOf<UserAction, UserActionResult>( - UserAction.Back to UserActionResult(SceneKey.QuickSettings) - ) + mapOf<UserAction, UserActionResult>(Back to UserActionResult(Scenes.QuickSettings)) } else { mapOf( - UserAction.Back to UserActionResult(SceneKey.Shade), - UserAction.Swipe(Direction.UP) to UserActionResult(SceneKey.Shade), + Back to UserActionResult(Scenes.Shade), + Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Shade), ) } } diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt index 80f11f1e1874..1c07d00e4195 100644 --- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt @@ -40,7 +40,7 @@ import com.android.systemui.flags.Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPR import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger import com.android.systemui.mediaprojection.SessionCreationSource import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver -import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog +import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate import com.android.systemui.qs.tiles.RecordIssueTile import com.android.systemui.res.R import com.android.systemui.screenrecord.RecordingService @@ -65,6 +65,7 @@ constructor( private val devicePolicyResolver: dagger.Lazy<ScreenCaptureDevicePolicyResolver>, private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger, private val userFileManager: UserFileManager, + private val screenCaptureDisabledDialogDelegate: ScreenCaptureDisabledDialogDelegate, @Assisted private val onStarted: Runnable, ) : SystemUIDialog.Delegate { @@ -124,7 +125,7 @@ constructor( .isScreenCaptureCompletelyDisabled(UserHandle.of(userTracker.userId)) ) { mainExecutor.execute { - ScreenCaptureDisabledDialog(context).show() + screenCaptureDisabledDialogDelegate.createDialog().show() screenRecordSwitch.isChecked = false } return@execute diff --git a/packages/SystemUI/src/com/android/systemui/rotationlock/RotationLockNewModule.kt b/packages/SystemUI/src/com/android/systemui/rotationlock/RotationLockNewModule.kt new file mode 100644 index 000000000000..eb64dd609a72 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/rotationlock/RotationLockNewModule.kt @@ -0,0 +1,73 @@ +/* + * 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.rotationlock + +import com.android.systemui.camera.CameraRotationModule +import com.android.systemui.qs.QsEventLogger +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory +import com.android.systemui.qs.tiles.impl.rotation.domain.interactor.RotationLockTileDataInteractor +import com.android.systemui.qs.tiles.impl.rotation.domain.interactor.RotationLockTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.rotation.domain.model.RotationLockTileModel +import com.android.systemui.qs.tiles.impl.rotation.ui.mapper.RotationLockTileMapper +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel +import com.android.systemui.res.R +import dagger.Module +import dagger.Provides +import dagger.multibindings.IntoMap +import dagger.multibindings.StringKey + +@Module(includes = [CameraRotationModule::class]) +interface RotationLockNewModule { + companion object { + private const val ROTATION_TILE_SPEC = "rotation" + + /** Inject rotation tile config */ + @Provides + @IntoMap + @StringKey(ROTATION_TILE_SPEC) + fun provideRotationTileConfig(uiEventLogger: QsEventLogger): QSTileConfig = + QSTileConfig( + tileSpec = TileSpec.create(ROTATION_TILE_SPEC), + uiConfig = + QSTileUIConfig.Resource( + iconRes = R.drawable.qs_auto_rotate_icon_off, + labelRes = R.string.quick_settings_rotation_unlocked_label, + ), + instanceId = uiEventLogger.getNewInstanceId(), + ) + + /** Inject Rotation tile into tileViewModelMap in QSModule */ + @Provides + @IntoMap + @StringKey(ROTATION_TILE_SPEC) + fun provideRotationTileViewModel( + factory: QSTileViewModelFactory.Static<RotationLockTileModel>, + mapper: RotationLockTileMapper, + stateInteractor: RotationLockTileDataInteractor, + userActionInteractor: RotationLockTileUserActionInteractor + ): QSTileViewModel = + factory.create( + TileSpec.create(ROTATION_TILE_SPEC), + userActionInteractor, + stateInteractor, + mapper, + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt index 356eb858d78f..afd0746f4696 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt @@ -18,7 +18,7 @@ package com.android.systemui.scene import com.android.systemui.scene.shared.flag.SceneContainerFlagsModule import com.android.systemui.scene.shared.model.SceneContainerConfig -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import dagger.Module import dagger.Provides @@ -44,11 +44,11 @@ object KeyguardlessSceneContainerFrameworkModule { // last one is top-most. sceneKeys = listOf( - SceneKey.Gone, - SceneKey.QuickSettings, - SceneKey.Shade, + Scenes.Gone, + Scenes.QuickSettings, + Scenes.Shade, ), - initialSceneKey = SceneKey.Gone, + initialSceneKey = Scenes.Gone, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt index 7d2468b2f016..62b0914fab79 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt @@ -22,7 +22,7 @@ import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInte import com.android.systemui.scene.domain.startable.SceneContainerStartable import com.android.systemui.scene.shared.flag.SceneContainerFlagsModule import com.android.systemui.scene.shared.model.SceneContainerConfig -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import dagger.Binds import dagger.Module import dagger.Provides @@ -67,14 +67,14 @@ interface SceneContainerFrameworkModule { // last one is top-most. sceneKeys = listOf( - SceneKey.Gone, - SceneKey.Communal, - SceneKey.Lockscreen, - SceneKey.Bouncer, - SceneKey.QuickSettings, - SceneKey.Shade, + Scenes.Gone, + Scenes.Communal, + Scenes.Lockscreen, + Scenes.Bouncer, + Scenes.QuickSettings, + Scenes.Shade, ), - initialSceneKey = SceneKey.Lockscreen, + initialSceneKey = Scenes.Lockscreen, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt index c10e51b68ba2..0665c9e1b802 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt @@ -18,7 +18,7 @@ package com.android.systemui.scene import com.android.systemui.scene.shared.flag.SceneContainerFlagsModule import com.android.systemui.scene.shared.model.SceneContainerConfig -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import dagger.Module import dagger.Provides @@ -44,11 +44,11 @@ object ShadelessSceneContainerFrameworkModule { // last one is top-most. sceneKeys = listOf( - SceneKey.Gone, - SceneKey.Lockscreen, - SceneKey.Bouncer, + Scenes.Gone, + Scenes.Lockscreen, + Scenes.Bouncer, ), - initialSceneKey = SceneKey.Lockscreen, + initialSceneKey = Scenes.Lockscreen, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt index e60dff183148..994b01216c22 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt @@ -18,12 +18,12 @@ package com.android.systemui.scene.data.repository +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.TransitionKey import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.SceneDataSource -import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.scene.shared.model.TransitionKey import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/PanelExpansionInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/PanelExpansionInteractor.kt index 36350f8af455..3a5ea5c6a064 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/PanelExpansionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/PanelExpansionInteractor.kt @@ -18,10 +18,11 @@ package com.android.systemui.scene.domain.interactor +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey import com.android.systemui.dagger.SysUISingleton import com.android.systemui.scene.shared.flag.SceneContainerFlag -import com.android.systemui.scene.shared.model.ObservableTransitionState -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.data.repository.ShadeRepository import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -53,7 +54,7 @@ constructor( when (state) { is ObservableTransitionState.Idle -> flowOf( - if (state.scene != SceneKey.Gone) { + if (state.scene != Scenes.Gone) { // When resting on a non-Gone scene, the panel is fully expanded. 1f } else { @@ -64,7 +65,7 @@ constructor( ) is ObservableTransitionState.Transition -> when { - state.fromScene == SceneKey.Gone -> + state.fromScene == Scenes.Gone -> if (state.toScene.isExpandable()) { // Moving from Gone to a scene that can animate-expand has a // panel @@ -77,7 +78,7 @@ constructor( // the panel fully expanded. flowOf(1f) } - state.toScene == SceneKey.Gone -> + state.toScene == Scenes.Gone -> if (state.fromScene.isExpandable()) { // Moving to Gone from a scene that can animate-expand has a // panel @@ -99,6 +100,6 @@ constructor( } private fun SceneKey.isExpandable(): Boolean { - return this == SceneKey.Shade || this == SceneKey.QuickSettings + return this == Scenes.Shade || this == Scenes.QuickSettings } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt index 6b7c672fbfe0..75bf131afdf9 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt @@ -16,14 +16,15 @@ package com.android.systemui.scene.domain.interactor +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.TransitionKey import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor import com.android.systemui.scene.data.repository.SceneContainerRepository import com.android.systemui.scene.shared.logger.SceneLogger -import com.android.systemui.scene.shared.model.ObservableTransitionState -import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.scene.shared.model.TransitionKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.util.kotlin.pairwiseBy import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -155,12 +156,13 @@ constructor( * desired scene. Once enough of the transition has occurred, the [currentScene] will become * [toScene] (unless the transition is canceled by user action or another call to this method). */ + @JvmOverloads fun changeScene( toScene: SceneKey, loggingReason: String, transitionKey: TransitionKey? = null, ) { - check(toScene != SceneKey.Gone || deviceUnlockedInteractor.isDeviceUnlocked.value) { + check(toScene != Scenes.Gone || deviceUnlockedInteractor.isDeviceUnlocked.value) { "Cannot change to the Gone scene while the device is locked. Logging reason for scene" + " change was: $loggingReason" } diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt index 1c37908235bd..c736707ecd2d 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt @@ -16,6 +16,7 @@ package com.android.systemui.scene.domain.interactor +import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -24,8 +25,7 @@ import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository import com.android.systemui.scene.shared.flag.SceneContainerFlags -import com.android.systemui.scene.shared.model.ObservableTransitionState -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.NotificationPresenter import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.init.NotificationsController @@ -77,12 +77,12 @@ constructor( .map { state -> when (state) { is ObservableTransitionState.Idle -> - state.scene == SceneKey.Shade || state.scene == SceneKey.Lockscreen + state.scene == Scenes.Shade || state.scene == Scenes.Lockscreen is ObservableTransitionState.Transition -> - state.toScene == SceneKey.Shade || - state.toScene == SceneKey.Lockscreen || - state.fromScene == SceneKey.Shade || - state.fromScene == SceneKey.Lockscreen + state.toScene == Scenes.Shade || + state.toScene == Scenes.Lockscreen || + state.fromScene == Scenes.Shade || + state.fromScene == Scenes.Lockscreen } } .distinctUntilChanged() 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 034f87f4c72f..6df57edd34c3 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 @@ -19,6 +19,8 @@ package com.android.systemui.scene.domain.startable import android.app.StatusBarManager +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey import com.android.systemui.CoreStartable import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel @@ -41,8 +43,7 @@ import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.scene.shared.logger.SceneLogger -import com.android.systemui.scene.shared.model.ObservableTransitionState -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled @@ -140,14 +141,14 @@ constructor( .mapNotNull { state -> when (state) { is ObservableTransitionState.Idle -> { - if (state.scene != SceneKey.Gone) { + if (state.scene != Scenes.Gone) { true to "scene is not Gone" } else { false to "scene is Gone" } } is ObservableTransitionState.Transition -> { - if (state.fromScene == SceneKey.Gone) { + if (state.fromScene == Scenes.Gone) { true to "scene transitioning away from Gone" } else { null @@ -180,9 +181,9 @@ constructor( applicationScope.launch { // TODO (b/308001302): Move this to a bouncer specific interactor. bouncerInteractor.onImeHiddenByUser.collectLatest { - if (sceneInteractor.currentScene.value == SceneKey.Bouncer) { + if (sceneInteractor.currentScene.value == Scenes.Bouncer) { sceneInteractor.changeScene( - toScene = SceneKey.Lockscreen, + toScene = Scenes.Lockscreen, loggingReason = "IME hidden", ) } @@ -196,13 +197,13 @@ constructor( when { isAnySimLocked -> { switchToScene( - targetSceneKey = SceneKey.Bouncer, + targetSceneKey = Scenes.Bouncer, loggingReason = "Need to authenticate locked SIM card." ) } isUnlocked && canSwipeToEnter == false -> { switchToScene( - targetSceneKey = SceneKey.Gone, + targetSceneKey = Scenes.Gone, loggingReason = "All SIM cards unlocked and device already" + " unlocked and lockscreen doesn't require a swipe to dismiss." @@ -210,7 +211,7 @@ constructor( } else -> { switchToScene( - targetSceneKey = SceneKey.Lockscreen, + targetSceneKey = Scenes.Lockscreen, loggingReason = "All SIM cards unlocked and device still locked" + " or lockscreen still requires a swipe to dismiss." @@ -231,8 +232,8 @@ constructor( transitionState.toScene, ) } - val isOnLockscreen = renderedScenes.contains(SceneKey.Lockscreen) - val isOnBouncer = renderedScenes.contains(SceneKey.Bouncer) + val isOnLockscreen = renderedScenes.contains(Scenes.Lockscreen) + val isOnBouncer = renderedScenes.contains(Scenes.Bouncer) if (!isUnlocked) { return@mapNotNull if (isOnLockscreen || isOnBouncer) { // Already on lockscreen or bouncer, no need to change scenes. @@ -240,7 +241,7 @@ constructor( } else { // The device locked while on a scene that's not Lockscreen or Bouncer, // go to Lockscreen. - SceneKey.Lockscreen to + Scenes.Lockscreen to "device locked in non-Lockscreen and non-Bouncer scene" } } @@ -250,7 +251,7 @@ constructor( when { isOnBouncer -> // When the device becomes unlocked in Bouncer, go to Gone. - SceneKey.Gone to "device was unlocked in Bouncer scene" + Scenes.Gone to "device was unlocked in Bouncer scene" isOnLockscreen -> // The lockscreen should be dismissed automatically in 2 scenarios: // 1. When face auth bypass is enabled and authentication happens while @@ -262,11 +263,11 @@ constructor( // authentication attempt. when { isBypassEnabled -> - SceneKey.Gone to + Scenes.Gone to "device has been unlocked on lockscreen with bypass" + " enabled" canSwipeToEnter == false -> - SceneKey.Gone to + Scenes.Gone to "device has been unlocked on lockscreen using an active" + " authentication mechanism" else -> null @@ -287,7 +288,7 @@ constructor( powerInteractor.isAsleep.collect { isAsleep -> if (isAsleep) { switchToScene( - targetSceneKey = SceneKey.Lockscreen, + targetSceneKey = Scenes.Lockscreen, loggingReason = "device is starting to sleep", ) } else { @@ -295,7 +296,7 @@ constructor( val isUnlocked = deviceEntryInteractor.isUnlocked.value if (isUnlocked && canSwipeToEnter == false) { switchToScene( - targetSceneKey = SceneKey.Gone, + targetSceneKey = Scenes.Gone, loggingReason = "device is waking up while unlocked without the ability" + " to swipe up on lockscreen to enter.", @@ -305,7 +306,7 @@ constructor( AuthenticationMethodModel.Sim ) { switchToScene( - targetSceneKey = SceneKey.Bouncer, + targetSceneKey = Scenes.Bouncer, loggingReason = "device is starting to wake up with a locked sim" ) } @@ -370,7 +371,7 @@ constructor( applicationScope.launch { sceneInteractor.currentScene - .map { it == SceneKey.Bouncer } + .map { it == Scenes.Bouncer } .distinctUntilChanged() .collect { switchedToBouncerScene -> if (switchedToBouncerScene) { @@ -390,7 +391,7 @@ constructor( falsingManager.addFalsingBeliefListener(listener) awaitClose { falsingManager.removeFalsingBeliefListener(listener) } } - .collect { switchToScene(SceneKey.Lockscreen, "Falsing detected.") } + .collect { switchToScene(Scenes.Lockscreen, "Falsing detected.") } } } @@ -403,7 +404,7 @@ constructor( } .distinctUntilChanged() .collect { sceneKey -> - windowController.setNotificationShadeFocusable(sceneKey != SceneKey.Gone) + windowController.setNotificationShadeFocusable(sceneKey != Scenes.Gone) } } } @@ -427,9 +428,9 @@ constructor( // // This is done here in order to match the legacy // implementation. The real reason why is lost to lore and myth. - SceneKey.Lockscreen -> true - SceneKey.Bouncer -> false - SceneKey.Shade -> false + Scenes.Lockscreen -> true + Scenes.Bouncer -> false + Scenes.Shade -> false else -> null } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt index 8408c51c86dc..1808d98cd692 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt @@ -24,7 +24,6 @@ import com.android.systemui.Flags.FLAG_SCENE_CONTAINER import com.android.systemui.Flags.keyguardBottomAreaRefactor import com.android.systemui.Flags.migrateClocksToBlueprint import com.android.systemui.Flags.sceneContainer -import com.android.systemui.compose.ComposeFacade import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FlagToken import com.android.systemui.flags.Flags.SCENE_CONTAINER_ENABLED @@ -47,9 +46,8 @@ object SceneContainerFlag { keyguardBottomAreaRefactor() && migrateClocksToBlueprint() && ComposeLockscreen.isEnabled && - MediaInSceneContainerFlag.isEnabled && - // NOTE: Changes should also be made in getSecondaryFlags and @EnableSceneContainer - ComposeFacade.isComposeAvailable() + MediaInSceneContainerFlag.isEnabled + // NOTE: Changes should also be made in getSecondaryFlags and @EnableSceneContainer /** * The main static flag, SCENE_CONTAINER_ENABLED. This is an explicit static flag check that @@ -74,11 +72,7 @@ object SceneContainerFlag { /** The full set of requirements for SceneContainer */ inline fun getAllRequirements(): Sequence<FlagToken> { - val composeRequirement = - FlagToken("ComposeFacade.isComposeAvailable()", ComposeFacade.isComposeAvailable()) - return sequenceOf(getMainStaticFlag(), getMainAconfigFlag()) + - getSecondaryFlags() + - composeRequirement + return sequenceOf(getMainStaticFlag(), getMainAconfigFlag()) + getSecondaryFlags() } /** Return all dependencies of this flag in pairs where [Pair.first] depends on [Pair.second] */ diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt index cbf7b3e7a971..f44779ade8db 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt @@ -16,10 +16,10 @@ package com.android.systemui.scene.shared.logger +import com.android.compose.animation.scene.SceneKey import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.SceneFrameworkLog -import com.android.systemui.scene.shared.model.SceneKey import javax.inject.Inject class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer: LogBuffer) { diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt deleted file mode 100644 index f704894e56e2..000000000000 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.scene.shared.model - -import kotlinx.coroutines.flow.Flow - -/** - * This is a fork of a class by the same name in the `com.android.compose.animation.scene` package. - * - * TODO(b/293899074): remove this fork, once we can compile Compose into System UI. - */ -sealed class ObservableTransitionState { - /** No transition/animation is currently running. */ - data class Idle(val scene: SceneKey) : ObservableTransitionState() - - /** There is a transition animating between two scenes. */ - data class Transition( - val fromScene: SceneKey, - val toScene: SceneKey, - val progress: Flow<Float>, - - /** - * Whether the transition was originally triggered by user input rather than being - * programmatic. If this value is initially true, it will remain true until the transition - * fully completes, even if the user input that triggered the transition has ended. Any - * sub-transitions launched by this one will inherit this value. For example, if the user - * drags a pointer but does not exceed the threshold required to transition to another - * scene, this value will remain true after the pointer is no longer touching the screen and - * will be true in any transition created to animate back to the original position. - */ - val isInitiatedByUserInput: Boolean, - - /** - * Whether user input is currently driving the transition. For example, if a user is - * dragging a pointer, this emits true. Once they lift their finger, this emits false while - * the transition completes/settles. - */ - val isUserInputOngoing: Flow<Boolean>, - ) : ObservableTransitionState() -} diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt index 05056c133ca3..939d5bc6588e 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt @@ -16,6 +16,9 @@ package com.android.systemui.scene.shared.model +import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.UserAction +import com.android.compose.animation.scene.UserActionResult import kotlinx.coroutines.flow.StateFlow /** @@ -53,39 +56,3 @@ interface Scene { */ val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> } - -/** Enumerates all scene framework supported user actions. */ -sealed interface UserAction { - - /** The user is scrolling, dragging, swiping, or flinging. */ - data class Swipe( - /** The direction of the swipe. */ - val direction: Direction, - /** - * The edge from which the swipe originated or `null`, if the swipe didn't start close to an - * edge. - */ - val fromEdge: Edge? = null, - /** The number of pointers that were used (for example, one or two fingers). */ - val pointerCount: Int = 1, - ) : UserAction - - /** The user has hit the back button or performed the back navigation gesture. */ - data object Back : UserAction -} - -/** Enumerates all known "cardinal" directions for user actions. */ -enum class Direction { - LEFT, - UP, - RIGHT, - DOWN, -} - -/** Enumerates all known edges from which a swipe can start. */ -enum class Edge { - LEFT, - TOP, - RIGHT, - BOTTOM, -} diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt index 8204edc33fe4..53cdaaab7478 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt @@ -16,6 +16,8 @@ package com.android.systemui.scene.shared.model +import com.android.compose.animation.scene.SceneKey + /** Models the configuration of the scene container. */ data class SceneContainerConfig( diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt index f7b45e547b7f..0e078d5d8064 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt @@ -16,6 +16,8 @@ package com.android.systemui.scene.shared.model +import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.TransitionKey import kotlinx.coroutines.flow.StateFlow /** Defines interface for classes that provide access to scene state. */ diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt index a50830c1f212..69dce83b7136 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt @@ -18,6 +18,8 @@ package com.android.systemui.scene.shared.model +import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.TransitionKey import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneKey.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt index 609d2b93a36e..73fcca8c6b7f 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneKey.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt @@ -16,41 +16,37 @@ package com.android.systemui.scene.shared.model +import com.android.compose.animation.scene.SceneKey + /** * Keys of all known scenes. * * PLEASE KEEP THE KEYS SORTED ALPHABETICALLY. */ -sealed class SceneKey( - private val loggingName: String, -) { +object Scenes { /** * The bouncer is the scene that displays authentication challenges like PIN, password, or * pattern. */ - object Bouncer : SceneKey("bouncer") + @JvmField val Bouncer = SceneKey("bouncer") /** The communal scene shows the glanceable hub when device is locked and docked. */ - object Communal : SceneKey("communal") + @JvmField val Communal = SceneKey("communal") /** * "Gone" is not a real scene but rather the absence of scenes when we want to skip showing any * content from the scene framework. */ - object Gone : SceneKey("gone") + @JvmField val Gone = SceneKey("gone") /** The lockscreen is the scene that shows when the device is locked. */ - object Lockscreen : SceneKey("lockscreen") + @JvmField val Lockscreen = SceneKey("lockscreen") /** The quick settings scene shows the quick setting tiles. */ - object QuickSettings : SceneKey("quick_settings") + @JvmField val QuickSettings = SceneKey("quick_settings") /** * The shade is the scene whose primary purpose is to show a scrollable list of notifications. */ - object Shade : SceneKey("shade") - - override fun toString(): String { - return loggingName - } + @JvmField val Shade = SceneKey("shade") } diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt index 926878c1870e..b91dd0451808 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt @@ -16,6 +16,8 @@ package com.android.systemui.scene.shared.model +import com.android.compose.animation.scene.TransitionKey + /** * Defines all known named transitions. * diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt index ee76c0582b9d..7c31ca269eb9 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt @@ -16,28 +16,43 @@ package com.android.systemui.scene.ui.view +import android.content.Context +import android.graphics.Point import android.view.View import android.view.ViewGroup import android.view.WindowInsets import androidx.activity.OnBackPressedDispatcher import androidx.activity.OnBackPressedDispatcherOwner import androidx.activity.setViewTreeOnBackPressedDispatcherOwner +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle -import com.android.systemui.compose.ComposeFacade +import com.android.compose.animation.scene.SceneKey +import com.android.compose.theme.PlatformTheme +import com.android.internal.policy.ScreenDecorationsUtils +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.ScreenDecorProvider import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.scene.shared.model.Scene import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.SceneDataSourceDelegator -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.ui.composable.ComposableScene +import com.android.systemui.scene.ui.composable.SceneContainer import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch object SceneWindowRootViewBinder { @@ -83,7 +98,7 @@ object SceneWindowRootViewBinder { ) view.addView( - ComposeFacade.createSceneContainerView( + createSceneContainerView( scope = this, context = view.context, viewModel = viewModel, @@ -120,4 +135,74 @@ object SceneWindowRootViewBinder { } } } + + private fun createSceneContainerView( + scope: CoroutineScope, + context: Context, + viewModel: SceneContainerViewModel, + windowInsets: StateFlow<WindowInsets?>, + sceneByKey: Map<SceneKey, Scene>, + dataSourceDelegator: SceneDataSourceDelegator, + ): View { + return ComposeView(context).apply { + setContent { + PlatformTheme { + ScreenDecorProvider( + displayCutout = displayCutoutFromWindowInsets(scope, context, windowInsets), + screenCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context) + ) { + SceneContainer( + viewModel = viewModel, + sceneByKey = + sceneByKey.mapValues { (_, scene) -> scene as ComposableScene }, + dataSourceDelegator = dataSourceDelegator, + ) + } + } + } + } + } + + // TODO(b/298525212): remove once Compose exposes window inset bounds. + private fun displayCutoutFromWindowInsets( + scope: CoroutineScope, + context: Context, + windowInsets: StateFlow<WindowInsets?>, + ): StateFlow<DisplayCutout> = + windowInsets + .map { + val boundingRect = it?.displayCutout?.boundingRectTop + val width = boundingRect?.let { boundingRect.right - boundingRect.left } ?: 0 + val left = boundingRect?.left?.toDp(context) ?: 0.dp + val top = boundingRect?.top?.toDp(context) ?: 0.dp + val right = boundingRect?.right?.toDp(context) ?: 0.dp + val bottom = boundingRect?.bottom?.toDp(context) ?: 0.dp + val location = + when { + width <= 0f -> CutoutLocation.NONE + left <= 0.dp -> CutoutLocation.LEFT + right >= getDisplayWidth(context) -> CutoutLocation.RIGHT + else -> CutoutLocation.CENTER + } + DisplayCutout( + left, + top, + right, + bottom, + location, + ) + } + .stateIn(scope, SharingStarted.WhileSubscribed(), DisplayCutout()) + + // TODO(b/298525212): remove once Compose exposes window inset bounds. + private fun getDisplayWidth(context: Context): Dp { + val point = Point() + checkNotNull(context.display).getRealSize(point) + return point.x.dp + } + + // TODO(b/298525212): remove once Compose exposes window inset bounds. + private fun Int.toDp(context: Context): Dp { + return (this.toFloat() / context.resources.displayMetrics.density).dp + } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt index 4c2c97981702..ea19020d84d4 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt @@ -25,7 +25,7 @@ import android.view.View import android.view.WindowInsets import android.widget.FrameLayout import androidx.core.view.updateMargins -import com.android.systemui.compose.ComposeFacade +import com.android.systemui.compose.ComposeInitializer import com.android.systemui.res.R /** A view that can serve as the root of the main SysUI window. */ @@ -45,16 +45,16 @@ open class WindowRootView( override fun onAttachedToWindow() { super.onAttachedToWindow() - if (ComposeFacade.isComposeAvailable() && isRoot()) { - ComposeFacade.composeInitializer().onAttachedToWindow(this) + if (isRoot()) { + ComposeInitializer.onAttachedToWindow(this) } } override fun onDetachedFromWindow() { super.onDetachedFromWindow() - if (ComposeFacade.isComposeAvailable() && isRoot()) { - ComposeFacade.composeInitializer().onDetachedFromWindow(this) + if (isRoot()) { + ComposeInitializer.onDetachedFromWindow(this) } } @@ -71,7 +71,6 @@ open class WindowRootView( override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets? { val insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()) - val displayCutout = rootWindowInsets.displayCutout if (fitsSystemWindows) { val paddingChanged = insets.top != paddingTop || insets.bottom != paddingBottom @@ -79,23 +78,22 @@ open class WindowRootView( if (paddingChanged) { setPadding(0, 0, 0, 0) } - - val pairInsets: Pair<Int, Int> = - layoutInsetsController.getinsets(windowInsets, displayCutout) - leftInset = pairInsets.first - rightInset = pairInsets.second - applyMargins() } else { val changed = paddingLeft != 0 || paddingRight != 0 || paddingTop != 0 || paddingBottom != 0 if (changed) { setPadding(0, 0, 0, 0) } - - leftInset = 0 - rightInset = 0 } + leftInset = 0 + rightInset = 0 + val displayCutout = rootWindowInsets.displayCutout + val pairInsets: Pair<Int, Int> = + layoutInsetsController.getinsets(windowInsets, displayCutout) + leftInset = pairInsets.first + rightInset = pairInsets.second + applyMargins() return windowInsets } diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt index 91861aa5c29a..231b28424691 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt @@ -17,13 +17,14 @@ package com.android.systemui.scene.ui.viewmodel import android.view.MotionEvent +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey import com.android.systemui.classifier.Classifier import com.android.systemui.classifier.domain.interactor.FalsingInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.scene.domain.interactor.SceneInteractor -import com.android.systemui.scene.shared.model.ObservableTransitionState -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow @@ -96,10 +97,10 @@ constructor( fun canChangeScene(toScene: SceneKey): Boolean { val interactionTypeOrNull = when (toScene) { - SceneKey.Bouncer -> Classifier.BOUNCER_UNLOCK - SceneKey.Gone -> Classifier.UNLOCK - SceneKey.Shade -> Classifier.NOTIFICATION_DRAG_DOWN - SceneKey.QuickSettings -> Classifier.QUICK_SETTINGS + Scenes.Bouncer -> Classifier.BOUNCER_UNLOCK + Scenes.Gone -> Classifier.UNLOCK + Scenes.Shade -> Classifier.NOTIFICATION_DRAG_DOWN + Scenes.QuickSettings -> Classifier.QUICK_SETTINGS else -> null } @@ -109,7 +110,7 @@ constructor( val isFalseTouch = falsingInteractor.isFalseTouch(interactionType) // Only enforce falsing if moving from the lockscreen scene to a new scene. - val fromLockscreenScene = currentScene.value == SceneKey.Lockscreen + val fromLockscreenScene = currentScene.value == Scenes.Lockscreen !fromLockscreenScene || !isFalseTouch } diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java index a4ba2a241275..8fe84c98866b 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java @@ -42,11 +42,9 @@ import com.android.systemui.flags.Flags; import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger; import com.android.systemui.mediaprojection.SessionCreationSource; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver; -import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog; +import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate; import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.settings.UserContextProvider; import com.android.systemui.settings.UserTracker; -import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.CallbackController; import dagger.Lazy; @@ -71,18 +69,19 @@ public class RecordingController private CountDownTimer mCountDownTimer = null; private final Executor mMainExecutor; private final BroadcastDispatcher mBroadcastDispatcher; - private final Context mContext; private final FeatureFlags mFlags; - private final UserContextProvider mUserContextProvider; private final UserTracker mUserTracker; private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger; - private final SystemUIDialog.Factory mDialogFactory; + private final ScreenCaptureDisabledDialogDelegate mScreenCaptureDisabledDialogDelegate; + private final ScreenRecordDialogDelegate.Factory mScreenRecordDialogFactory; + private final ScreenRecordPermissionDialogDelegate.Factory + mScreenRecordPermissionDialogDelegateFactory; protected static final String INTENT_UPDATE_STATE = "com.android.systemui.screenrecord.UPDATE_STATE"; protected static final String EXTRA_STATE = "extra_state"; - private CopyOnWriteArrayList<RecordingStateChangeCallback> mListeners = + private final CopyOnWriteArrayList<RecordingStateChangeCallback> mListeners = new CopyOnWriteArrayList<>(); private final Lazy<ScreenCaptureDevicePolicyResolver> mDevicePolicyResolver; @@ -115,24 +114,26 @@ public class RecordingController * Create a new RecordingController */ @Inject - public RecordingController(@Main Executor mainExecutor, + public RecordingController( + @Main Executor mainExecutor, BroadcastDispatcher broadcastDispatcher, - Context context, FeatureFlags flags, - UserContextProvider userContextProvider, Lazy<ScreenCaptureDevicePolicyResolver> devicePolicyResolver, UserTracker userTracker, MediaProjectionMetricsLogger mediaProjectionMetricsLogger, - SystemUIDialog.Factory dialogFactory) { + ScreenCaptureDisabledDialogDelegate screenCaptureDisabledDialogDelegate, + ScreenRecordDialogDelegate.Factory screenRecordDialogFactory, + ScreenRecordPermissionDialogDelegate.Factory + screenRecordPermissionDialogDelegateFactory) { mMainExecutor = mainExecutor; - mContext = context; mFlags = flags; mDevicePolicyResolver = devicePolicyResolver; mBroadcastDispatcher = broadcastDispatcher; - mUserContextProvider = userContextProvider; mUserTracker = userTracker; mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger; - mDialogFactory = dialogFactory; + mScreenCaptureDisabledDialogDelegate = screenCaptureDisabledDialogDelegate; + mScreenRecordDialogFactory = screenRecordDialogFactory; + mScreenRecordPermissionDialogDelegateFactory = screenRecordPermissionDialogDelegateFactory; BroadcastOptions options = BroadcastOptions.makeBasic(); options.setInteractive(true); @@ -163,27 +164,18 @@ public class RecordingController if (mFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES) && mDevicePolicyResolver.get() .isScreenCaptureCompletelyDisabled(getHostUserHandle())) { - return new ScreenCaptureDisabledDialog(mContext); + return mScreenCaptureDisabledDialogDelegate.createDialog(); } mMediaProjectionMetricsLogger.notifyProjectionInitiated( getHostUid(), SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER); - return flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING) - ? mDialogFactory.create(new ScreenRecordPermissionDialogDelegate( - getHostUserHandle(), - getHostUid(), - /* controller= */ this, - activityStarter, - mUserContextProvider, - onStartRecordingClicked, - mMediaProjectionMetricsLogger, - mDialogFactory)) - : new ScreenRecordDialog( - context, - /* controller= */ this, - mUserContextProvider, - onStartRecordingClicked); + return (flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING) + ? mScreenRecordPermissionDialogDelegateFactory + .create(this, getHostUserHandle(), getHostUid(), onStartRecordingClicked) + : mScreenRecordDialogFactory + .create(this, onStartRecordingClicked)) + .createDialog(); } /** diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialogDelegate.java index b98093e50920..9f1447b1f509 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialogDelegate.java @@ -49,52 +49,69 @@ import com.android.systemui.res.R; import com.android.systemui.settings.UserContextProvider; import com.android.systemui.statusbar.phone.SystemUIDialog; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; + import java.util.Arrays; import java.util.List; /** * Dialog to select screen recording options */ -public class ScreenRecordDialog extends SystemUIDialog { +public class ScreenRecordDialogDelegate implements SystemUIDialog.Delegate { private static final List<ScreenRecordingAudioSource> MODES = Arrays.asList(INTERNAL, MIC, MIC_AND_INTERNAL); private static final long DELAY_MS = 3000; private static final long INTERVAL_MS = 1000; - private final RecordingController mController; + private final SystemUIDialog.Factory mSystemUIDialogFactory; private final UserContextProvider mUserContextProvider; - @Nullable + private final RecordingController mController; private final Runnable mOnStartRecordingClicked; private Switch mTapsSwitch; private Switch mAudioSwitch; private Spinner mOptions; - public ScreenRecordDialog(Context context, - RecordingController controller, - UserContextProvider userContextProvider, - @Nullable Runnable onStartRecordingClicked) { - super(context); - mController = controller; + @AssistedFactory + public interface Factory { + ScreenRecordDialogDelegate create( + RecordingController recordingController, + @Nullable Runnable onStartRecordingClicked + ); + } + + @AssistedInject + public ScreenRecordDialogDelegate( + SystemUIDialog.Factory systemUIDialogFactory, + UserContextProvider userContextProvider, + @Assisted RecordingController controller, + @Assisted @Nullable Runnable onStartRecordingClicked) { + mSystemUIDialogFactory = systemUIDialogFactory; mUserContextProvider = userContextProvider; + mController = controller; mOnStartRecordingClicked = onStartRecordingClicked; } @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + public SystemUIDialog createDialog() { + return mSystemUIDialogFactory.create(this); + } - Window window = getWindow(); + @Override + public void onCreate(SystemUIDialog dialog, Bundle savedInstanceState) { + Window window = dialog.getWindow(); window.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS); window.setGravity(Gravity.CENTER); - setTitle(R.string.screenrecord_title); + dialog.setTitle(R.string.screenrecord_title); - setContentView(R.layout.screen_record_dialog); + dialog.setContentView(R.layout.screen_record_dialog); - TextView cancelBtn = findViewById(R.id.button_cancel); - cancelBtn.setOnClickListener(v -> dismiss()); - TextView startBtn = findViewById(R.id.button_start); + TextView cancelBtn = dialog.findViewById(R.id.button_cancel); + cancelBtn.setOnClickListener(v -> dialog.dismiss()); + TextView startBtn = dialog.findViewById(R.id.button_start); startBtn.setOnClickListener(v -> { if (mOnStartRecordingClicked != null) { // Note that it is important to run this callback before dismissing, so that the @@ -104,13 +121,13 @@ public class ScreenRecordDialog extends SystemUIDialog { // Start full-screen recording requestScreenCapture(/* captureTarget= */ null); - dismiss(); + dialog.dismiss(); }); - mAudioSwitch = findViewById(R.id.screenrecord_audio_switch); - mTapsSwitch = findViewById(R.id.screenrecord_taps_switch); - mOptions = findViewById(R.id.screen_recording_options); - ArrayAdapter a = new ScreenRecordingAdapter(getContext().getApplicationContext(), + mAudioSwitch = dialog.findViewById(R.id.screenrecord_audio_switch); + mTapsSwitch = dialog.findViewById(R.id.screenrecord_taps_switch); + mOptions = dialog.findViewById(R.id.screen_recording_options); + ArrayAdapter a = new ScreenRecordingAdapter(dialog.getContext().getApplicationContext(), android.R.layout.simple_spinner_dropdown_item, MODES); a.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt index 3eb26f498921..ba775cd3cd82 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt @@ -46,17 +46,20 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R import com.android.systemui.settings.UserContextProvider import com.android.systemui.statusbar.phone.SystemUIDialog +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject /** Dialog to select screen recording options */ -class ScreenRecordPermissionDialogDelegate( - private val hostUserHandle: UserHandle, - private val hostUid: Int, - private val controller: RecordingController, +class ScreenRecordPermissionDialogDelegate @AssistedInject constructor( + @Assisted private val hostUserHandle: UserHandle, + @Assisted private val hostUid: Int, + @Assisted private val controller: RecordingController, private val activityStarter: ActivityStarter, private val userContextProvider: UserContextProvider, - private val onStartRecordingClicked: Runnable?, + @Assisted private val onStartRecordingClicked: Runnable?, mediaProjectionMetricsLogger: MediaProjectionMetricsLogger, - private val systemUIDialogFactory: SystemUIDialog.Factory + private val systemUIDialogFactory: SystemUIDialog.Factory, ) : BaseMediaProjectionPermissionDialogDelegate<SystemUIDialog>( createOptionList(), @@ -65,8 +68,19 @@ class ScreenRecordPermissionDialogDelegate( mediaProjectionMetricsLogger, R.drawable.ic_screenrecord, R.color.screenrecord_icon_color - ), - SystemUIDialog.Delegate { + ), SystemUIDialog.Delegate { + + + @AssistedFactory + interface Factory { + fun create( + recordingController: RecordingController, + hostUserHandle: UserHandle, + hostUid: Int, + onStartRecordingClicked: Runnable? + ): ScreenRecordPermissionDialogDelegate + } + private lateinit var tapsSwitch: Switch private lateinit var tapsSwitchContainer: ViewGroup private lateinit var tapsView: View diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt new file mode 100644 index 000000000000..2294fc0be520 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt @@ -0,0 +1,159 @@ +/* + * 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.screenshot + +import android.animation.Animator +import android.app.Notification +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Rect +import android.graphics.drawable.Drawable +import android.view.Display +import android.view.LayoutInflater +import android.view.ScrollCaptureResponse +import android.view.View +import android.view.ViewTreeObserver +import android.view.WindowInsets +import android.window.OnBackInvokedDispatcher +import com.android.internal.logging.UiEventLogger +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.res.R + +/** + * Legacy implementation of screenshot view methods. Just proxies the calls down into the original + * ScreenshotView. + */ +class LegacyScreenshotViewProxy(context: Context) : ScreenshotViewProxy { + override val view: ScreenshotView = + LayoutInflater.from(context).inflate(R.layout.screenshot, null) as ScreenshotView + override val internalInsetsListener: ViewTreeObserver.OnComputeInternalInsetsListener + override val screenshotPreview: View + + override var defaultDisplay: Int = Display.DEFAULT_DISPLAY + set(value) { + view.setDefaultDisplay(value) + } + override var defaultTimeoutMillis: Long = 6000 + set(value) { + view.setDefaultTimeoutMillis(value) + } + override var onKeyListener: View.OnKeyListener? = null + set(value) { + view.setOnKeyListener(value) + } + override var flags: FeatureFlags? = null + set(value) { + view.setFlags(value) + } + override var packageName: String = "" + set(value) { + view.setPackageName(value) + } + override var logger: UiEventLogger? = null + set(value) { + view.setUiEventLogger(value) + } + override var callbacks: ScreenshotView.ScreenshotViewCallback? = null + set(value) { + view.setCallbacks(value) + } + override var screenshot: ScreenshotData? = null + set(value) { + view.setScreenshot(value) + } + + override val isAttachedToWindow + get() = view.isAttachedToWindow + override val isDismissing + get() = view.isDismissing + override val isPendingSharedTransition + get() = view.isPendingSharedTransition + + init { + internalInsetsListener = view + screenshotPreview = view.screenshotPreview + } + + override fun reset() = view.reset() + override fun updateInsets(insets: WindowInsets) = view.updateInsets(insets) + override fun updateOrientation(insets: WindowInsets) = view.updateOrientation(insets) + + override fun badgeScreenshot(userBadgedIcon: Drawable) = view.badgeScreenshot(userBadgedIcon) + + override fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator = + view.createScreenshotDropInAnimation(screenRect, showFlash) + + override fun addQuickShareChip(quickShareAction: Notification.Action) = + view.addQuickShareChip(quickShareAction) + + override fun setChipIntents(imageData: ScreenshotController.SavedImageData) = + view.setChipIntents(imageData) + + override fun animateDismissal() = view.animateDismissal() + + override fun showScrollChip(packageName: String, onClick: Runnable) = + view.showScrollChip(packageName, onClick) + + override fun hideScrollChip() = view.hideScrollChip() + + override fun prepareScrollingTransition( + response: ScrollCaptureResponse, + screenBitmap: Bitmap, + newScreenshot: Bitmap, + screenshotTakenInPortrait: Boolean + ) = + view.prepareScrollingTransition( + response, + screenBitmap, + newScreenshot, + screenshotTakenInPortrait + ) + + override fun startLongScreenshotTransition( + transitionDestination: Rect, + onTransitionEnd: Runnable, + longScreenshot: ScrollCaptureController.LongScreenshot + ) = view.startLongScreenshotTransition(transitionDestination, onTransitionEnd, longScreenshot) + + override fun restoreNonScrollingUi() = view.restoreNonScrollingUi() + + override fun stopInputListening() = view.stopInputListening() + + override fun requestFocus() { + view.requestFocus() + } + + override fun announceForAccessibility(string: String) = view.announceForAccessibility(string) + + override fun addOnAttachStateChangeListener(listener: View.OnAttachStateChangeListener) = + view.addOnAttachStateChangeListener(listener) + + override fun findOnBackInvokedDispatcher(): OnBackInvokedDispatcher? = + view.findOnBackInvokedDispatcher() + + override fun getViewTreeObserver(): ViewTreeObserver = view.viewTreeObserver + + override fun post(runnable: Runnable) { + view.post(runnable) + } + + class Factory : ScreenshotViewProxy.Factory { + override fun getProxy(context: Context): ScreenshotViewProxy { + return LegacyScreenshotViewProxy(context) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index ee3e7ba9e8b3..13448d258a2c 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -65,7 +65,6 @@ import android.view.Display; import android.view.IRemoteAnimationFinishedCallback; import android.view.IRemoteAnimationRunner; import android.view.KeyEvent; -import android.view.LayoutInflater; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationTarget; import android.view.ScrollCaptureResponse; @@ -165,7 +164,7 @@ public class ScreenshotController { /** * Structure returned by the SaveImageInBackgroundTask */ - static class SavedImageData { + public static class SavedImageData { public Uri uri; public List<Notification.Action> smartActions; public Notification.Action quickShareAction; @@ -237,6 +236,7 @@ public class ScreenshotController { private final WindowContext mContext; private final FeatureFlags mFlags; + private final ScreenshotViewProxy mViewProxy; private final ScreenshotNotificationsController mNotificationsController; private final ScreenshotSmartActions mScreenshotSmartActions; private final UiEventLogger mUiEventLogger; @@ -272,7 +272,6 @@ public class ScreenshotController { respondToKeyDismissal(); }; - private ScreenshotView mScreenshotView; private final MessageContainerController mMessageContainerController; private Bitmap mScreenBitmap; private SaveImageInBackgroundTask mSaveInBgTask; @@ -305,6 +304,7 @@ public class ScreenshotController { ScreenshotController( Context context, FeatureFlags flags, + ScreenshotViewProxy.Factory viewProxyFactory, ScreenshotSmartActions screenshotSmartActions, ScreenshotNotificationsController.Factory screenshotNotificationsControllerFactory, ScrollCaptureClient scrollCaptureClient, @@ -360,6 +360,8 @@ public class ScreenshotController { mMessageContainerController = messageContainerController; mAssistContentRequester = assistContentRequester; + mViewProxy = viewProxyFactory.getProxy(mContext); + mAccessibilityManager = AccessibilityManager.getInstance(mContext); // Setup the window that we are going to use @@ -461,7 +463,7 @@ public class ScreenshotController { // The window is focusable by default setWindowFocusable(true); - mScreenshotView.requestFocus(); + mViewProxy.requestFocus(); enqueueScrollCaptureRequest(screenshot.getUserHandle()); @@ -485,10 +487,10 @@ public class ScreenshotController { mMessageContainerController.onScreenshotTaken(screenshot); }); - mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon( + mViewProxy.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon( mContext.getDrawable(R.drawable.overlay_badge_background), screenshot.getUserHandle())); - mScreenshotView.setScreenshot(screenshot); + mViewProxy.setScreenshot(screenshot); // ignore system bar insets for the purpose of window layout mWindow.getDecorView().setOnApplyWindowInsetsListener( @@ -503,31 +505,31 @@ public class ScreenshotController { void prepareViewForNewScreenshot(ScreenshotData screenshot, String oldPackageName) { withWindowAttached(() -> { if (mUserManager.isManagedProfile(screenshot.getUserHandle().getIdentifier())) { - mScreenshotView.announceForAccessibility(mContext.getResources().getString( + mViewProxy.announceForAccessibility(mContext.getResources().getString( R.string.screenshot_saving_work_profile_title)); } else { - mScreenshotView.announceForAccessibility( + mViewProxy.announceForAccessibility( mContext.getResources().getString(R.string.screenshot_saving_title)); } }); - mScreenshotView.reset(); + mViewProxy.reset(); - if (mScreenshotView.isAttachedToWindow()) { + if (mViewProxy.isAttachedToWindow()) { // if we didn't already dismiss for another reason - if (!mScreenshotView.isDismissing()) { + if (!mViewProxy.isDismissing()) { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED, 0, oldPackageName); } if (DEBUG_WINDOW) { Log.d(TAG, "saveScreenshot: screenshotView is already attached, resetting. " - + "(dismissing=" + mScreenshotView.isDismissing() + ")"); + + "(dismissing=" + mViewProxy.isDismissing() + ")"); } } - mScreenshotView.setPackageName(mPackageName); + mViewProxy.setPackageName(mPackageName); - mScreenshotView.updateOrientation( + mViewProxy.updateOrientation( mWindowManager.getCurrentWindowMetrics().getWindowInsets()); } @@ -539,7 +541,7 @@ public class ScreenshotController { Log.d(TAG, "dismissScreenshot"); } // If we're already animating out, don't restart the animation - if (mScreenshotView.isDismissing()) { + if (mViewProxy.isDismissing()) { if (DEBUG_DISMISS) { Log.v(TAG, "Already dismissing, ignoring duplicate command"); } @@ -547,11 +549,11 @@ public class ScreenshotController { } mUiEventLogger.log(event, 0, mPackageName); mScreenshotHandler.cancelTimeout(); - mScreenshotView.animateDismissal(); + mViewProxy.animateDismissal(); } boolean isPendingSharedTransition() { - return mScreenshotView.isPendingSharedTransition(); + return mViewProxy.isPendingSharedTransition(); } // Any cleanup needed when the service is being destroyed. @@ -576,7 +578,7 @@ public class ScreenshotController { private void releaseMediaPlayer() { if (mScreenshotSoundController == null) return; - mScreenshotSoundController.releaseScreenshotSound(); + mScreenshotSoundController.releaseScreenshotSoundAsync(); } private void respondToKeyDismissal() { @@ -591,18 +593,15 @@ public class ScreenshotController { Log.d(TAG, "reloadAssets()"); } - // Inflate the screenshot layout - mScreenshotView = (ScreenshotView) - LayoutInflater.from(mContext).inflate(R.layout.screenshot, null); - mMessageContainerController.setView(mScreenshotView); - mScreenshotView.addOnAttachStateChangeListener( + mMessageContainerController.setView(mViewProxy.getView()); + mViewProxy.addOnAttachStateChangeListener( new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(@NonNull View v) { if (DEBUG_INPUT) { Log.d(TAG, "Registering Predictive Back callback"); } - mScreenshotView.findOnBackInvokedDispatcher().registerOnBackInvokedCallback( + mViewProxy.findOnBackInvokedDispatcher().registerOnBackInvokedCallback( OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback); } @@ -611,11 +610,12 @@ public class ScreenshotController { if (DEBUG_INPUT) { Log.d(TAG, "Unregistering Predictive Back callback"); } - mScreenshotView.findOnBackInvokedDispatcher() + mViewProxy.findOnBackInvokedDispatcher() .unregisterOnBackInvokedCallback(mOnBackInvokedCallback); } }); - mScreenshotView.init(mUiEventLogger, new ScreenshotView.ScreenshotViewCallback() { + mViewProxy.setLogger(mUiEventLogger); + mViewProxy.setCallbacks(new ScreenshotView.ScreenshotViewCallback() { @Override public void onUserInteraction() { if (DEBUG_INPUT) { @@ -640,11 +640,12 @@ public class ScreenshotController { // TODO(159460485): Remove this when focus is handled properly in the system setWindowFocusable(false); } - }, mFlags); - mScreenshotView.setDefaultDisplay(mDisplayId); - mScreenshotView.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis()); + }); + mViewProxy.setFlags(mFlags); + mViewProxy.setDefaultDisplay(mDisplayId); + mViewProxy.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis()); - mScreenshotView.setOnKeyListener((v, keyCode, event) -> { + mViewProxy.setOnKeyListener((v, keyCode, event) -> { if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) { if (DEBUG_INPUT) { Log.d(TAG, "onKeyEvent: " + keyCode); @@ -658,23 +659,24 @@ public class ScreenshotController { if (DEBUG_WINDOW) { Log.d(TAG, "adding OnComputeInternalInsetsListener"); } - mScreenshotView.getViewTreeObserver().addOnComputeInternalInsetsListener(mScreenshotView); + mViewProxy.getViewTreeObserver().addOnComputeInternalInsetsListener( + mViewProxy.getInternalInsetsListener()); if (DEBUG_WINDOW) { - Log.d(TAG, "setContentView: " + mScreenshotView); + Log.d(TAG, "setContentView: " + mViewProxy.getView()); } - setContentView(mScreenshotView); + setContentView(mViewProxy.getView()); } private void prepareAnimation(Rect screenRect, boolean showFlash, Runnable onAnimationComplete) { - mScreenshotView.getViewTreeObserver().addOnPreDrawListener( + mViewProxy.getViewTreeObserver().addOnPreDrawListener( new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { if (DEBUG_WINDOW) { Log.d(TAG, "onPreDraw: startAnimation"); } - mScreenshotView.getViewTreeObserver().removeOnPreDrawListener(this); + mViewProxy.getViewTreeObserver().removeOnPreDrawListener(this); startAnimation(screenRect, showFlash, onAnimationComplete); return true; } @@ -694,13 +696,13 @@ public class ScreenshotController { if (mConfigChanges.applyNewConfig(mContext.getResources())) { // Hide the scroll chip until we know it's available in this // orientation - mScreenshotView.hideScrollChip(); + mViewProxy.hideScrollChip(); // Delay scroll capture eval a bit to allow the underlying activity // to set up in the new orientation. mScreenshotHandler.postDelayed(() -> { requestScrollCapture(owner); }, 150); - mScreenshotView.updateInsets( + mViewProxy.updateInsets( mWindowManager.getCurrentWindowMetrics().getWindowInsets()); // Screenshot animation calculations won't be valid anymore, // so just end @@ -759,16 +761,16 @@ public class ScreenshotController { + mLastScrollCaptureResponse.getWindowTitle() + "]"); final ScrollCaptureResponse response = mLastScrollCaptureResponse; - mScreenshotView.showScrollChip(response.getPackageName(), /* onClick */ () -> { + mViewProxy.showScrollChip(response.getPackageName(), /* onClick */ () -> { DisplayMetrics displayMetrics = new DisplayMetrics(); getDisplay().getRealMetrics(displayMetrics); Bitmap newScreenshot = mImageCapture.captureDisplay(mDisplayId, new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels)); - mScreenshotView.prepareScrollingTransition(response, mScreenBitmap, newScreenshot, + mViewProxy.prepareScrollingTransition(response, mScreenBitmap, newScreenshot, mScreenshotTakenInPortrait); // delay starting scroll capture to make sure the scrim is up before the app moves - mScreenshotView.post(() -> runBatchScrollCapture(response, owner)); + mViewProxy.post(() -> runBatchScrollCapture(response, owner)); }); } catch (InterruptedException | ExecutionException e) { Log.e(TAG, "requestScrollCapture failed", e); @@ -794,19 +796,19 @@ public class ScreenshotController { return; } catch (InterruptedException | ExecutionException e) { Log.e(TAG, "Exception", e); - mScreenshotView.restoreNonScrollingUi(); + mViewProxy.restoreNonScrollingUi(); return; } if (longScreenshot.getHeight() == 0) { - mScreenshotView.restoreNonScrollingUi(); + mViewProxy.restoreNonScrollingUi(); return; } mLongScreenshotHolder.setLongScreenshot(longScreenshot); mLongScreenshotHolder.setTransitionDestinationCallback( (transitionDestination, onTransitionEnd) -> { - mScreenshotView.startLongScreenshotTransition( + mViewProxy.startLongScreenshotTransition( transitionDestination, onTransitionEnd, longScreenshot); // TODO: Do this via ActionIntentExecutor instead. @@ -882,16 +884,14 @@ public class ScreenshotController { } mWindowManager.removeViewImmediate(decorView); } - // Ensure that we remove the input monitor - if (mScreenshotView != null) { - mScreenshotView.stopInputListening(); - } + + mViewProxy.stopInputListening(); } private void playCameraSoundIfNeeded() { if (mScreenshotSoundController == null) return; // the controller is not-null only on the default display controller - mScreenshotSoundController.playCameraSound(); + mScreenshotSoundController.playScreenshotSoundAsync(); } /** @@ -932,7 +932,7 @@ public class ScreenshotController { } mScreenshotAnimation = - mScreenshotView.createScreenshotDropInAnimation(screenRect, showFlash); + mViewProxy.createScreenshotDropInAnimation(screenRect, showFlash); if (onAnimationComplete != null) { mScreenshotAnimation.addListener(new AnimatorListenerAdapter() { @Override @@ -975,7 +975,7 @@ public class ScreenshotController { }; Pair<ActivityOptions, ExitTransitionCoordinator> transition = ActivityOptions.startSharedElementAnimation(mWindow, callbacks, null, - Pair.create(mScreenshotView.getScreenshotPreview(), + Pair.create(mViewProxy.getScreenshotPreview(), ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME)); return transition; @@ -999,7 +999,7 @@ public class ScreenshotController { mCurrentRequestCallback.onFinish(); mCurrentRequestCallback = null; } - mScreenshotView.reset(); + mViewProxy.reset(); removeWindow(); mScreenshotHandler.cancelTimeout(); } @@ -1067,7 +1067,7 @@ public class ScreenshotController { } private void doPostAnimation(ScreenshotController.SavedImageData imageData) { - mScreenshotView.setChipIntents(imageData); + mViewProxy.setChipIntents(imageData); } /** @@ -1084,11 +1084,11 @@ public class ScreenshotController { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); - mScreenshotView.addQuickShareChip(quickShareData.quickShareAction); + mViewProxy.addQuickShareChip(quickShareData.quickShareAction); } }); } else { - mScreenshotView.addQuickShareChip(quickShareData.quickShareAction); + mViewProxy.addQuickShareChip(quickShareData.quickShareAction); } }); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt index 2c0bddecc58e..d3a7fc4a3e4a 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt @@ -21,22 +21,34 @@ import android.util.Log import com.android.app.tracing.coroutines.async import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background -import com.google.errorprone.annotations.CanIgnoreReturnValue import javax.inject.Inject import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Deferred import kotlinx.coroutines.TimeoutCancellationException +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout /** Controls sound reproduction after a screenshot is taken. */ interface ScreenshotSoundController { /** Reproduces the camera sound. */ - @CanIgnoreReturnValue fun playCameraSound(): Deferred<Unit> + suspend fun playScreenshotSound() - /** Releases the sound. [playCameraSound] behaviour is undefined after this has been called. */ - @CanIgnoreReturnValue fun releaseScreenshotSound(): Deferred<Unit> + /** + * Releases the sound. [playScreenshotSound] behaviour is undefined after this has been called. + */ + suspend fun releaseScreenshotSound() + + /** Reproduces the camera sound. Used for compatibility with Java code. */ + fun playScreenshotSoundAsync() + + /** + * Releases the sound. [playScreenshotSound] behaviour is undefined after this has been called. + * Used for compatibility with Java code. + */ + fun releaseScreenshotSoundAsync() } class ScreenshotSoundControllerImpl @@ -47,8 +59,8 @@ constructor( @Background private val bgDispatcher: CoroutineDispatcher ) : ScreenshotSoundController { - val player: Deferred<MediaPlayer?> = - coroutineScope.async("loadCameraSound", bgDispatcher) { + private val player: Deferred<MediaPlayer?> = + coroutineScope.async("loadScreenshotSound", bgDispatcher) { try { soundProvider.getScreenshotSound() } catch (e: IllegalStateException) { @@ -57,8 +69,8 @@ constructor( } } - override fun playCameraSound(): Deferred<Unit> { - return coroutineScope.async("playCameraSound", bgDispatcher) { + override suspend fun playScreenshotSound() { + withContext(bgDispatcher) { try { player.await()?.start() } catch (e: IllegalStateException) { @@ -68,8 +80,8 @@ constructor( } } - override fun releaseScreenshotSound(): Deferred<Unit> { - return coroutineScope.async("releaseScreenshotSound", bgDispatcher) { + override suspend fun releaseScreenshotSound() { + withContext(bgDispatcher) { try { withTimeout(1.seconds) { player.await()?.release() } } catch (e: TimeoutCancellationException) { @@ -79,6 +91,14 @@ constructor( } } + override fun playScreenshotSoundAsync() { + coroutineScope.launch { playScreenshotSound() } + } + + override fun releaseScreenshotSoundAsync() { + coroutineScope.launch { releaseScreenshotSound() } + } + private companion object { const val TAG = "ScreenshotSoundControllerImpl" } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java index be30a1576b21..8a8766dbab94 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java @@ -102,7 +102,7 @@ import java.util.ArrayList; public class ScreenshotView extends FrameLayout implements ViewTreeObserver.OnComputeInternalInsetsListener { - interface ScreenshotViewCallback { + public interface ScreenshotViewCallback { void onUserInteraction(); void onAction(Intent intent, UserHandle owner, boolean overrideTransition); @@ -426,15 +426,15 @@ public class ScreenshotView extends FrameLayout implements return mScreenshotPreview; } - /** - * Set up the logger and callback on dismissal. - * - * Note: must be called before any other (non-constructor) method or null pointer exceptions - * may occur. - */ - void init(UiEventLogger uiEventLogger, ScreenshotViewCallback callbacks, FeatureFlags flags) { + void setUiEventLogger(UiEventLogger uiEventLogger) { mUiEventLogger = uiEventLogger; + } + + void setCallbacks(ScreenshotViewCallback callbacks) { mCallbacks = callbacks; + } + + void setFlags(FeatureFlags flags) { mFlags = flags; } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt new file mode 100644 index 000000000000..0064521bd3a4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt @@ -0,0 +1,89 @@ +/* + * 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.screenshot + +import android.animation.Animator +import android.app.Notification +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Rect +import android.graphics.drawable.Drawable +import android.view.ScrollCaptureResponse +import android.view.View +import android.view.View.OnKeyListener +import android.view.ViewGroup +import android.view.ViewTreeObserver +import android.view.WindowInsets +import android.window.OnBackInvokedDispatcher +import com.android.internal.logging.UiEventLogger +import com.android.systemui.flags.FeatureFlags + +/** Abstraction of the surface between ScreenshotController and ScreenshotView */ +interface ScreenshotViewProxy { + val view: ViewGroup + val internalInsetsListener: ViewTreeObserver.OnComputeInternalInsetsListener + val screenshotPreview: View + + var defaultDisplay: Int + var defaultTimeoutMillis: Long + var onKeyListener: OnKeyListener? + var flags: FeatureFlags? + var packageName: String + var logger: UiEventLogger? + var callbacks: ScreenshotView.ScreenshotViewCallback? + var screenshot: ScreenshotData? + + val isAttachedToWindow: Boolean + val isDismissing: Boolean + val isPendingSharedTransition: Boolean + + fun reset() + fun updateInsets(insets: WindowInsets) + fun updateOrientation(insets: WindowInsets) + fun badgeScreenshot(userBadgedIcon: Drawable) + fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator + fun addQuickShareChip(quickShareAction: Notification.Action) + fun setChipIntents(imageData: ScreenshotController.SavedImageData) + fun animateDismissal() + + fun showScrollChip(packageName: String, onClick: Runnable) + fun hideScrollChip() + fun prepareScrollingTransition( + response: ScrollCaptureResponse, + screenBitmap: Bitmap, + newScreenshot: Bitmap, + screenshotTakenInPortrait: Boolean + ) + fun startLongScreenshotTransition( + transitionDestination: Rect, + onTransitionEnd: Runnable, + longScreenshot: ScrollCaptureController.LongScreenshot + ) + fun restoreNonScrollingUi() + + fun stopInputListening() + fun requestFocus() + fun announceForAccessibility(string: String) + fun addOnAttachStateChangeListener(listener: View.OnAttachStateChangeListener) + fun findOnBackInvokedDispatcher(): OnBackInvokedDispatcher? + fun getViewTreeObserver(): ViewTreeObserver + fun post(runnable: Runnable) + + interface Factory { + fun getProxy(context: Context): ScreenshotViewProxy + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java index bb34ede2cf5e..8a2678c8ab09 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java @@ -75,7 +75,7 @@ public class ScrollCaptureController { private String mWindowOwner; private volatile boolean mCancelled; - static class LongScreenshot { + public static class LongScreenshot { private final ImageTileSet mImageTileSet; private final Session mSession; // TODO: Add UserHandle so LongScreenshots can adhere to work profile screenshot policy diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java index 3797b8b41e5a..a00c81d43b9e 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java @@ -20,6 +20,7 @@ import android.app.Service; import com.android.systemui.screenshot.ImageCapture; import com.android.systemui.screenshot.ImageCaptureImpl; +import com.android.systemui.screenshot.LegacyScreenshotViewProxy; import com.android.systemui.screenshot.RequestProcessor; import com.android.systemui.screenshot.ScreenshotPolicy; import com.android.systemui.screenshot.ScreenshotPolicyImpl; @@ -29,12 +30,14 @@ import com.android.systemui.screenshot.ScreenshotSoundController; import com.android.systemui.screenshot.ScreenshotSoundControllerImpl; import com.android.systemui.screenshot.ScreenshotSoundProvider; import com.android.systemui.screenshot.ScreenshotSoundProviderImpl; +import com.android.systemui.screenshot.ScreenshotViewProxy; import com.android.systemui.screenshot.TakeScreenshotService; import com.android.systemui.screenshot.appclips.AppClipsScreenshotHelperService; import com.android.systemui.screenshot.appclips.AppClipsService; import dagger.Binds; import dagger.Module; +import dagger.Provides; import dagger.multibindings.ClassKey; import dagger.multibindings.IntoMap; @@ -81,4 +84,9 @@ public abstract class ScreenshotModule { @Binds abstract ScreenshotSoundController bindScreenshotSoundController( ScreenshotSoundControllerImpl screenshotSoundProviderImpl); + + @Provides + static ScreenshotViewProxy.Factory providesScreenshotViewProxyFactory() { + return new LegacyScreenshotViewProxy.Factory(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java index 6af9b739da52..e92630fc67a2 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java @@ -55,15 +55,15 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.util.settings.SecureSettings; -import java.util.concurrent.Executor; - import dagger.assisted.Assisted; import dagger.assisted.AssistedFactory; import dagger.assisted.AssistedInject; +import java.util.concurrent.Executor; + public class BrightnessController implements ToggleSlider.Listener, MirroredBrightnessController { private static final String TAG = "CentralSurfaces.BrightnessController"; - private static final int SLIDER_ANIMATION_DURATION = 3000; + private static final int SLIDER_ANIMATION_DURATION = 1000; private static final int MSG_UPDATE_SLIDER = 1; private static final int MSG_ATTACH_LISTENER = 2; diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt index df845f559f2e..d3869baf16a2 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt @@ -23,11 +23,12 @@ import android.view.GestureDetector import android.view.MotionEvent import android.view.View import android.view.ViewGroup +import androidx.compose.ui.platform.ComposeView +import com.android.compose.theme.PlatformTheme import com.android.internal.annotations.VisibleForTesting import com.android.systemui.communal.domain.interactor.CommunalInteractor +import com.android.systemui.communal.ui.compose.CommunalContainer import com.android.systemui.communal.ui.viewmodel.CommunalViewModel -import com.android.systemui.compose.ComposeFacade.createCommunalContainer -import com.android.systemui.compose.ComposeFacade.isComposeAvailable import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.res.R @@ -35,7 +36,6 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.util.kotlin.collectFlow import javax.inject.Inject import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf /** * Controller that's responsible for the glanceable hub container view and its touch handling. @@ -107,8 +107,7 @@ constructor( private var shadeShowing = false /** Returns a flow that tracks whether communal hub is available. */ - fun communalAvailable(): Flow<Boolean> = - if (isComposeAvailable()) communalInteractor.isCommunalAvailable else flowOf(false) + fun communalAvailable(): Flow<Boolean> = communalInteractor.isCommunalAvailable /** * Creates the container view containing the glanceable hub UI. @@ -118,7 +117,11 @@ constructor( fun initView( context: Context, ): View { - return initView(createCommunalContainer(context, communalViewModel)) + return initView( + ComposeView(context).apply { + setContent { PlatformTheme { CommunalContainer(viewModel = communalViewModel) } } + } + ) } /** Override for testing. */ diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 7bcb1da5c2bf..2fd438be9610 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -111,8 +111,6 @@ import com.android.keyguard.dagger.KeyguardUserSwitcherComponent; import com.android.systemui.DejankUtils; import com.android.systemui.Dumpable; import com.android.systemui.Gefingerpoken; -import com.android.systemui.animation.ActivityTransitionAnimator; -import com.android.systemui.animation.TransitionAnimator; import com.android.systemui.biometrics.AuthController; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants; @@ -219,7 +217,6 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; import com.android.systemui.statusbar.phone.TapAgainViewController; import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; -import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController; @@ -259,10 +256,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private static final boolean DEBUG_DRAWABLE = false; /** The parallax amount of the quick settings translation when dragging down the panel. */ public static final float QS_PARALLAX_AMOUNT = 0.175f; - private static final long ANIMATION_DELAY_ICON_FADE_IN = - ActivityTransitionAnimator.TIMINGS.getTotalDuration() - - CollapsedStatusBarFragment.FADE_IN_DURATION - - CollapsedStatusBarFragment.FADE_IN_DELAY - 48; private static final int NO_FIXED_DURATION = -1; private static final long SHADE_OPEN_SPRING_OUT_DURATION = 350L; private static final long SHADE_OPEN_SPRING_BACK_DURATION = 400L; @@ -358,7 +351,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private final AccessibilityDelegate mAccessibilityDelegate = new ShadeAccessibilityDelegate(); private final NotificationGutsManager mGutsManager; private final AlternateBouncerInteractor mAlternateBouncerInteractor; - private final QuickSettingsController mQsController; + private final QuickSettingsControllerImpl mQsController; private final NaturalScrollingSettingObserver mNaturalScrollingSettingObserver; private final TouchHandler mTouchHandler = new TouchHandler(); @@ -463,7 +456,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump */ private float mLinearDarkAmount; private boolean mPulsing; - private boolean mHideIconsDuringLaunchAnimation = true; private int mStackScrollerMeasuringPass; /** Non-null if a heads-up notification's position is being tracked. */ @Nullable @@ -734,7 +726,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump TapAgainViewController tapAgainViewController, NavigationModeController navigationModeController, NavigationBarController navigationBarController, - QuickSettingsController quickSettingsController, + QuickSettingsControllerImpl quickSettingsController, FragmentService fragmentService, IStatusBarService statusBarService, ContentResolver contentResolver, @@ -1296,9 +1288,10 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mView.getContext().getDisplay()); mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController(); mKeyguardStatusViewController.init(); + } - mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled); - mKeyguardStatusViewController.getView().addOnLayoutChangeListener( + mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled); + mKeyguardStatusViewController.getView().addOnLayoutChangeListener( (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { int oldHeight = oldBottom - oldTop; if (v.getHeight() != oldHeight) { @@ -1306,8 +1299,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } }); - updateClockAppearance(); - } + updateClockAppearance(); } @Override @@ -1334,9 +1326,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private void onSplitShadeEnabledChanged() { mShadeLog.logSplitShadeChanged(mSplitShadeEnabled); - if (!migrateClocksToBlueprint()) { - mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled); - } + mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled); // Reset any left over overscroll state. It is a rare corner case but can happen. mQsController.setOverScrollAmount(0); mScrimController.setNotificationsOverScrollAmount(0); @@ -1451,13 +1441,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mStatusBarStateListener.onDozeAmountChanged(mStatusBarStateController.getDozeAmount(), mStatusBarStateController.getInterpolatedDozeAmount()); - if (!migrateClocksToBlueprint()) { - mKeyguardStatusViewController.setKeyguardStatusViewVisibility( - mBarState, - false, - false, - mBarState); - } + mKeyguardStatusViewController.setKeyguardStatusViewVisibility( + mBarState, + false, + false, + mBarState); if (mKeyguardQsUserSwitchController != null) { mKeyguardQsUserSwitchController.setKeyguardQsUserSwitchVisibility( mBarState, @@ -1677,11 +1665,13 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mKeyguardStatusViewController.setLockscreenClockY( mClockPositionAlgorithm.getExpandedPreferredClockY()); } - if (!(migrateClocksToBlueprint() || keyguardBottomAreaRefactor())) { + if (keyguardBottomAreaRefactor()) { + mKeyguardInteractor.setClockPosition( + mClockPositionResult.clockX, mClockPositionResult.clockY); + } else { mKeyguardBottomAreaInteractor.setClockPosition( mClockPositionResult.clockX, mClockPositionResult.clockY); } - boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending(); boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange; @@ -1759,11 +1749,13 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } private void updateKeyguardStatusViewAlignment(boolean animate) { + boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered(); + ConstraintLayout layout; if (migrateClocksToBlueprint()) { - return; + layout = mKeyguardViewConfigurator.getKeyguardRootView(); + } else { + layout = mNotificationContainerParent; } - boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered(); - ConstraintLayout layout = mNotificationContainerParent; mKeyguardStatusViewController.updateAlignment( layout, mSplitShadeEnabled, shouldBeCentered, animate); mKeyguardUnfoldTransition.ifPresent(t -> t.setStatusViewCentered(shouldBeCentered)); @@ -2053,7 +2045,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mView.animate().cancel(); } - @Override public void expandToQs() { if (mQsController.isExpansionEnabled()) { mQsController.setExpandImmediate(true); @@ -2820,7 +2811,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mQsController.setListening(listening); } - @Override public void expand(boolean animate) { if (isFullyCollapsed() || isCollapsing()) { mInstantExpanding = true; @@ -3159,10 +3149,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return mUnlockedScreenOffAnimationController.isAnimationPlaying(); } - @Override public boolean shouldHideStatusBarIconsWhenExpanded() { if (isLaunchingActivity()) { - return mHideIconsDuringLaunchAnimation; + return false; } if (mHeadsUpAppearanceController != null && mHeadsUpAppearanceController.shouldBeVisible()) { @@ -3260,18 +3249,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } @Override - public void applyLaunchAnimationProgress(float linearProgress) { - boolean hideIcons = TransitionAnimator.getProgress(ActivityTransitionAnimator.TIMINGS, - linearProgress, ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f; - if (hideIcons != mHideIconsDuringLaunchAnimation) { - mHideIconsDuringLaunchAnimation = hideIcons; - if (!hideIcons) { - mCommandQueue.recomputeDisableFlags(mDisplayId, true /* animate */); - } - } - } - - @Override public void performHapticFeedback(int constant) { mVibratorHelper.performHapticFeedback(mView, constant); } @@ -3339,9 +3316,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump /** Updates the views to the initial state for the fold to AOD animation. */ @Override public void prepareFoldToAodAnimation() { - if (migrateClocksToBlueprint()) { - return; - } // Force show AOD UI even if we are not locked showAodUi(); @@ -3363,9 +3337,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @Override public void startFoldToAodAnimation(Runnable startAction, Runnable endAction, Runnable cancelAction) { - if (migrateClocksToBlueprint()) { - return; - } final ViewPropertyAnimator viewAnimator = mView.animate(); viewAnimator.cancel(); viewAnimator @@ -3401,9 +3372,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump /** Cancels fold to AOD transition and resets view state. */ @Override public void cancelFoldToAodAnimation() { - if (migrateClocksToBlueprint()) { - return; - } cancelAnimation(); resetAlpha(); resetTranslation(); @@ -3482,7 +3450,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump ipw.print("mInterpolatedDarkAmount="); ipw.println(mInterpolatedDarkAmount); ipw.print("mLinearDarkAmount="); ipw.println(mLinearDarkAmount); ipw.print("mPulsing="); ipw.println(mPulsing); - ipw.print("mHideIconsDuringLaunchAnimation="); ipw.println(mHideIconsDuringLaunchAnimation); ipw.print("mStackScrollerMeasuringPass="); ipw.println(mStackScrollerMeasuringPass); ipw.print("mPanelAlpha="); ipw.println(mPanelAlpha); ipw.print("mBottomAreaShadeAlpha="); ipw.println(mBottomAreaShadeAlpha); @@ -4174,7 +4141,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return mShadeRepository.getLegacyIsClosing().getValue(); } - @Override public void collapseWithDuration(int animationDuration) { mFixedDuration = animationDuration; collapse(false /* delayed */, 1.0f /* speedUpFactor */); @@ -4480,13 +4446,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } } - if (!migrateClocksToBlueprint()) { - mKeyguardStatusViewController.setKeyguardStatusViewVisibility( - statusBarState, - keyguardFadingAway, - goingToFullShade, - mBarState); - } + mKeyguardStatusViewController.setKeyguardStatusViewVisibility( + statusBarState, + keyguardFadingAway, + goingToFullShade, + mBarState); if (!keyguardBottomAreaRefactor()) { setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade); diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java index 8f9cef37dac7..f7fed537a167 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java @@ -60,7 +60,6 @@ import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.res.R; -import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.scene.ui.view.WindowRootViewComponent; import com.android.systemui.settings.UserTracker; @@ -507,7 +506,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW private void applyFitsSystemWindows(NotificationShadeWindowState state) { boolean fitsSystemWindows = !state.isKeyguardShowingAndNotOccluded(); - if (!SceneContainerFlag.isEnabled() && mWindowRootView != null + if (mWindowRootView != null && mWindowRootView.getFitsSystemWindows() != fitsSystemWindows) { mWindowRootView.setFitsSystemWindows(fitsSystemWindows); mWindowRootView.requestApplyInsets(); diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.kt b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.kt new file mode 100644 index 000000000000..c96a339b560e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.kt @@ -0,0 +1,51 @@ +/* + * 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.shade + +interface QuickSettingsController { + /** Returns whether or not QuickSettings is expanded. */ + val expanded: Boolean + + /** Returns whether or not QuickSettings is being customized. */ + val isCustomizing: Boolean + + /** Returns Whether we should intercept a gesture to open Quick Settings. */ + @Deprecated("specific to legacy touch handling") + fun shouldQuickSettingsIntercept(x: Float, y: Float, yDiff: Float): Boolean + + /** Closes the Qs customizer. */ + fun closeQsCustomizer() + + /** + * This method closes QS but in split shade it should be used only in special cases: to make + * sure QS closes when shade is closed as well. Otherwise it will result in QS disappearing from + * split shade + */ + @Deprecated("specific to legacy split shade") fun closeQs() + + /** Calculate top padding for notifications */ + @Deprecated("specific to legacy DebugDrawable") + fun calculateNotificationsTopPadding( + isShadeExpanding: Boolean, + keyguardNotificationStaticPadding: Int, + expandedFraction: Float, + ): Float + + /** Calculate height of QS panel */ + @Deprecated("specific to legacy DebugDrawable") + fun calculatePanelHeightExpanded(stackScrollerPadding: Int): Int +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java index a5c055350999..19d98a0bb83c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java @@ -118,7 +118,7 @@ import javax.inject.Inject; * TODO (b/264460656) make this dumpable */ @SysUISingleton -public class QuickSettingsController implements Dumpable { +public class QuickSettingsControllerImpl implements QuickSettingsController, Dumpable { public static final String TAG = "QuickSettingsController"; public static final int SHADE_BACK_ANIM_SCALE_MULTIPLIER = 100; @@ -252,7 +252,7 @@ public class QuickSettingsController implements Dumpable { /** * The window width currently in effect -- used together with - * {@link QuickSettingsController#mCachedGestureInsets} to decide whether a back gesture should + * {@link QuickSettingsControllerImpl#mCachedGestureInsets} to decide whether a back gesture should * receive a horizontal swipe inwards from the left/right vertical edge of the screen. * We cache this on ACTION_DOWN, and query it during both ACTION_DOWN and ACTION_MOVE events. */ @@ -304,7 +304,7 @@ public class QuickSettingsController implements Dumpable { private final QS.ScrollListener mQsScrollListener = this::onScroll; @Inject - public QuickSettingsController( + public QuickSettingsControllerImpl( Lazy<NotificationPanelViewController> panelViewControllerLazy, NotificationPanelView panelView, QsFrameTranslateController qsFrameTranslateController, @@ -399,23 +399,23 @@ public class QuickSettingsController implements Dumpable { mQs = qs; } - public void setExpansionHeightListener(ExpansionHeightListener listener) { + void setExpansionHeightListener(ExpansionHeightListener listener) { mExpansionHeightListener = listener; } - public void setQsStateUpdateListener(QsStateUpdateListener listener) { + void setQsStateUpdateListener(QsStateUpdateListener listener) { mQsStateUpdateListener = listener; } - public void setApplyClippingImmediatelyListener(ApplyClippingImmediatelyListener listener) { + void setApplyClippingImmediatelyListener(ApplyClippingImmediatelyListener listener) { mApplyClippingImmediatelyListener = listener; } - public void setFlingQsWithoutClickListener(FlingQsWithoutClickListener listener) { + void setFlingQsWithoutClickListener(FlingQsWithoutClickListener listener) { mFlingQsWithoutClickListener = listener; } - public void setExpansionHeightSetToMaxListener(ExpansionHeightSetToMaxListener callback) { + void setExpansionHeightSetToMaxListener(ExpansionHeightSetToMaxListener callback) { mExpansionHeightSetToMaxListener = callback; } @@ -507,15 +507,11 @@ public class QuickSettingsController implements Dumpable { && mPanelView.getRootWindowInsets().isVisible(ime()); } - public boolean isExpansionEnabled() { + boolean isExpansionEnabled() { return mExpansionEnabledPolicy && mExpansionEnabledAmbient && !isRemoteInputActiveWithKeyboardUp(); } - public float getTransitioningToFullShadeProgress() { - return mTransitioningToFullShadeProgress; - } - /** */ @VisibleForTesting boolean isExpandImmediate() { @@ -536,7 +532,7 @@ public class QuickSettingsController implements Dumpable { * Computes (and caches) the gesture insets for the current window. Intended to be called * on ACTION_DOWN, and safely queried repeatedly thereafter during ACTION_MOVE events. */ - public void updateGestureInsetsCache() { + void updateGestureInsetsCache() { WindowManager wm = this.mPanelView.getContext().getSystemService(WindowManager.class); WindowMetrics windowMetrics = wm.getCurrentWindowMetrics(); mCachedGestureInsets = windowMetrics.getWindowInsets().getInsets( @@ -548,7 +544,7 @@ public class QuickSettingsController implements Dumpable { * Returns whether x coordinate lies in the vertical edges of the screen * (the only place where a back gesture can be initiated). */ - public boolean shouldBackBypassQuickSettings(float touchX) { + boolean shouldBackBypassQuickSettings(float touchX) { return (touchX < mCachedGestureInsets.left) || (touchX > mCachedWindowWidth - mCachedGestureInsets.right); } @@ -592,6 +588,7 @@ public class QuickSettingsController implements Dumpable { return twoFingerDrag || stylusButtonClickDrag || mouseButtonClickDrag; } + @Override public boolean getExpanded() { return mShadeRepository.getLegacyIsQsExpanded().getValue(); } @@ -601,7 +598,7 @@ public class QuickSettingsController implements Dumpable { return mShadeRepository.getLegacyQsTracking().getValue(); } - public boolean getFullyExpanded() { + boolean getFullyExpanded() { return mFullyExpanded; } @@ -623,27 +620,28 @@ public class QuickSettingsController implements Dumpable { return mQs != null; } + @Override public boolean isCustomizing() { return isQsFragmentCreated() && mQs.isCustomizing(); } - public float getExpansionHeight() { + float getExpansionHeight() { return mExpansionHeight; } - public boolean getExpandedWhenExpandingStarted() { + boolean getExpandedWhenExpandingStarted() { return mExpandedWhenExpandingStarted; } - public int getMinExpansionHeight() { + int getMinExpansionHeight() { return mMinExpansionHeight; } - public boolean isFullyExpandedAndTouchesDisallowed() { + boolean isFullyExpandedAndTouchesDisallowed() { return isQsFragmentCreated() && getFullyExpanded() && disallowTouches(); } - public int getMaxExpansionHeight() { + int getMaxExpansionHeight() { return mMaxExpansionHeight; } @@ -654,13 +652,14 @@ public class QuickSettingsController implements Dumpable { return !mTouchAboveFalsingThreshold; } - public int getFalsingThreshold() { + int getFalsingThreshold() { return mFalsingThreshold; } /** * Returns Whether we should intercept a gesture to open Quick Settings. */ + @Override public boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) { boolean keyguardShowing = mBarState == KEYGUARD; if (!isExpansionEnabled() || mCollapsedOnDown || (keyguardShowing @@ -717,7 +716,7 @@ public class QuickSettingsController implements Dumpable { * @param downY the y location where the touch started * Returns true if the panel could be collapsed because it stared on QQS */ - public boolean canPanelCollapseOnQQS(float downX, float downY) { + boolean canPanelCollapseOnQQS(float downX, float downY) { if (mCollapsedOnDown || mBarState == KEYGUARD || getExpanded()) { return false; } @@ -727,6 +726,7 @@ public class QuickSettingsController implements Dumpable { } /** Closes the Qs customizer. */ + @Override public void closeQsCustomizer() { if (mQs != null) { mQs.closeCustomizer(); @@ -734,7 +734,7 @@ public class QuickSettingsController implements Dumpable { } /** Returns whether touches from the notification panel should be disallowed */ - public boolean disallowTouches() { + boolean disallowTouches() { if (mQs != null) { return mQs.disallowPanelTouches(); } else { @@ -754,15 +754,11 @@ public class QuickSettingsController implements Dumpable { } } - public void setDozing(boolean dozing) { + void setDozing(boolean dozing) { mDozing = dozing; } - /** - * This method closes QS but in split shade it should be used only in special cases: to make - * sure QS closes when shade is closed as well. Otherwise it will result in QS disappearing - * from split shade - */ + @Override public void closeQs() { if (mSplitShadeEnabled) { mShadeLog.d("Closing QS while in split shade"); @@ -794,7 +790,7 @@ public class QuickSettingsController implements Dumpable { } /** update Qs height state */ - public void setExpansionHeight(float height) { + void setExpansionHeight(float height) { int maxHeight = getMaxExpansionHeight(); height = Math.min(Math.max( height, getMinExpansionHeight()), maxHeight); @@ -817,7 +813,7 @@ public class QuickSettingsController implements Dumpable { } /** */ - public void setHeightOverrideToDesiredHeight() { + void setHeightOverrideToDesiredHeight() { if (isSizeChangeAnimationRunning() && isQsFragmentCreated()) { mQs.setHeightOverride(mQs.getDesiredHeight()); } @@ -919,7 +915,7 @@ public class QuickSettingsController implements Dumpable { } /** Sets Qs ScrimEnabled and updates QS state. */ - public void setScrimEnabled(boolean scrimEnabled) { + void setScrimEnabled(boolean scrimEnabled) { boolean changed = mScrimEnabled != scrimEnabled; mScrimEnabled = scrimEnabled; if (changed) { @@ -995,7 +991,7 @@ public class QuickSettingsController implements Dumpable { } /** update expanded state of QS */ - public void updateExpansion() { + void updateExpansion() { if (mQs == null) return; final float squishiness; if ((isExpandImmediate() || getExpanded()) && !mSplitShadeEnabled) { @@ -1053,13 +1049,13 @@ public class QuickSettingsController implements Dumpable { // mTransitioningToFullShadeProgress > 0 means we're doing regular lockscreen to shade // transition. If that's not the case we should follow QS expansion fraction for when // user is pulling from the same top to go directly to expanded QS - return getTransitioningToFullShadeProgress() > 0 + return mTransitioningToFullShadeProgress > 0 ? mLockscreenShadeTransitionController.getQSDragProgress() : computeExpansionFraction(); } /** */ - public void updateExpansionEnabledAmbient() { + void updateExpansionEnabledAmbient() { final float scrollRangeToTop = mAmbientState.getTopPadding() - mQuickQsHeaderHeight; mExpansionEnabledAmbient = mSplitShadeEnabled || (mAmbientState.getScrollY() <= scrollRangeToTop); @@ -1081,7 +1077,7 @@ public class QuickSettingsController implements Dumpable { } /** Calculate fraction of current QS expansion state */ - public float computeExpansionFraction() { + float computeExpansionFraction() { if (mAnimatingHiddenFromCollapsed) { // When hiding QS from collapsed state, the expansion can sometimes temporarily // be larger than 0 because of the timing, leading to flickers. @@ -1112,7 +1108,7 @@ public class QuickSettingsController implements Dumpable { } /** Called when shade starts expanding. */ - public void onExpandingStarted(boolean qsFullyExpanded) { + void onExpandingStarted(boolean qsFullyExpanded) { mNotificationStackScrollLayoutController.onExpansionStarted(); mExpandedWhenExpandingStarted = qsFullyExpanded; mMediaHierarchyManager.setCollapsingShadeFromQS(mExpandedWhenExpandingStarted @@ -1363,7 +1359,7 @@ public class QuickSettingsController implements Dumpable { } } - /** Calculate top padding for notifications */ + @Override public float calculateNotificationsTopPadding(boolean isShadeExpanding, int keyguardNotificationStaticPadding, float expandedFraction) { float topPadding; @@ -1404,7 +1400,7 @@ public class QuickSettingsController implements Dumpable { } } - /** Calculate height of QS panel */ + @Override public int calculatePanelHeightExpanded(int stackScrollerPadding) { float notificationHeight = @@ -1576,7 +1572,6 @@ public class QuickSettingsController implements Dumpable { } } - @VisibleForTesting boolean isTrackingBlocked() { return mConflictingExpansionGesture && getExpanded(); } @@ -1591,7 +1586,7 @@ public class QuickSettingsController implements Dumpable { } /** handles touches in Qs panel area */ - public boolean handleTouch(MotionEvent event, boolean isFullyCollapsed, + boolean handleTouch(MotionEvent event, boolean isFullyCollapsed, boolean isShadeOrQsHeightAnimationRunning) { if (isSplitShadeAndTouchXOutsideQs(event.getX())) { return false; @@ -1747,7 +1742,7 @@ public class QuickSettingsController implements Dumpable { } /** intercepts touches on Qs panel area. */ - public boolean onIntercept(MotionEvent event) { + boolean onIntercept(MotionEvent event) { int pointerIndex = event.findPointerIndex(mTrackingPointer); if (pointerIndex < 0) { pointerIndex = 0; @@ -1858,7 +1853,7 @@ public class QuickSettingsController implements Dumpable { * * @param animateAway Do not stop when QS becomes QQS. Fling until QS isn't visible anymore. */ - public void animateCloseQs(boolean animateAway) { + void animateCloseQs(boolean animateAway) { if (mExpansionAnimator != null) { if (!mAnimatorExpand) { return; @@ -1877,7 +1872,7 @@ public class QuickSettingsController implements Dumpable { } /** @see #flingQs(float, int, Runnable, boolean) */ - public void flingQs(float vel, int type) { + void flingQs(float vel, int type) { flingQs(vel, type, null /* onFinishRunnable */, false /* isClick */); } @@ -2144,7 +2139,7 @@ public class QuickSettingsController implements Dumpable { } /** */ - public FragmentHostManager.FragmentListener getQsFragmentListener() { + FragmentHostManager.FragmentListener getQsFragmentListener() { return new QsFragmentListener(); } diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt new file mode 100644 index 000000000000..b8250cc284bd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt @@ -0,0 +1,67 @@ +/* + * 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.shade + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.plugins.qs.QSContainerController +import com.android.systemui.qs.ui.adapter.QSSceneAdapter +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import javax.inject.Inject + +@SysUISingleton +class QuickSettingsControllerSceneImpl +@Inject +constructor( + private val shadeInteractor: ShadeInteractor, + private val qsSceneAdapter: QSSceneAdapter, + private val qsContainerController: QSContainerController, +) : QuickSettingsController { + + override val expanded: Boolean + get() = shadeInteractor.isQsExpanded.value + + override val isCustomizing: Boolean + get() = qsSceneAdapter.isCustomizing.value + + @Deprecated("specific to legacy touch handling") + override fun shouldQuickSettingsIntercept(x: Float, y: Float, yDiff: Float): Boolean { + throw UnsupportedOperationException() + } + + override fun closeQsCustomizer() { + qsContainerController.setCustomizerShowing(false) + } + + @Deprecated("specific to legacy split shade") + override fun closeQs() { + // Do nothing + } + + @Deprecated("specific to legacy DebugDrawable") + override fun calculateNotificationsTopPadding( + isShadeExpanding: Boolean, + keyguardNotificationStaticPadding: Int, + expandedFraction: Float + ): Float { + throw UnsupportedOperationException() + } + + @Deprecated("specific to legacy DebugDrawable") + override fun calculatePanelHeightExpanded(stackScrollerPadding: Int): Int { + throw UnsupportedOperationException() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java index ec4b23a56483..0a57b64b1ecf 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java @@ -78,6 +78,14 @@ public interface ShadeController extends CoreStartable { */ void animateCollapseShade(int flags, boolean force, boolean delayed, float speedUpFactor); + /** + * Collapses the shade with an animation duration in milliseconds. + * + * @deprecated use animateCollapseShade with a speed up factor instead + */ + @Deprecated + void collapseWithDuration(int animationDuration); + /** Expand the shade with an animation. */ void animateExpandShade(); diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt index 08a0c934702d..093690ffb881 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt @@ -33,6 +33,7 @@ open class ShadeControllerEmptyImpl @Inject constructor() : ShadeController { delayed: Boolean, speedUpFactor: Float ) {} + override fun collapseWithDuration(animationDuration: Int) {} override fun animateExpandShade() {} override fun animateExpandQs() {} override fun postAnimateCollapseShade() {} diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java index e6555f2e0993..d99d607879cc 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java @@ -147,6 +147,11 @@ public final class ShadeControllerImpl extends BaseShadeControllerImpl { } @Override + public void collapseWithDuration(int animationDuration) { + mNpvc.get().collapseWithDuration(animationDuration); + } + + @Override protected void expandToNotifications() { getNpvc().expandToNotifications(); } @@ -221,7 +226,6 @@ public final class ShadeControllerImpl extends BaseShadeControllerImpl { } } - @Override public void collapseShade(boolean animate) { if (animate) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt index 6a2a6a417f5a..27168a799086 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt @@ -17,6 +17,7 @@ package com.android.systemui.shade import android.view.MotionEvent +import com.android.compose.animation.scene.SceneKey import com.android.systemui.assist.AssistManager import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background @@ -25,7 +26,7 @@ import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.log.LogBuffer import com.android.systemui.log.dagger.ShadeTouchLog import com.android.systemui.scene.domain.interactor.SceneInteractor -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.TransitionKeys.CollapseShadeInstantly import com.android.systemui.scene.shared.model.TransitionKeys.SlightlyFasterShadeCollapse import com.android.systemui.shade.ShadeController.ShadeVisibilityListener @@ -96,7 +97,7 @@ constructor( } override fun instantCollapseShade() { - // TODO(b/315921512) add support for instant transition + // TODO(b/325602936) add support for instant transition sceneInteractor.changeScene( getCollapseDestinationScene(), "hide shade", @@ -121,7 +122,7 @@ constructor( // release focus immediately to kick off focus change transition notificationShadeWindowController.setNotificationShadeFocusable(false) notificationStackScrollLayout.cancelExpandHelper() - sceneInteractor.changeScene(SceneKey.Shade, "ShadeController.animateExpandShade") + sceneInteractor.changeScene(Scenes.Shade, "ShadeController.animateExpandShade") if (delayed) { scope.launch { delay(125) @@ -133,6 +134,11 @@ constructor( } } + override fun collapseWithDuration(animationDuration: Int) { + // TODO(b/300258424) inline this. The only caller uses the default duration. + animateCollapseShade() + } + private fun animateCollapseShadeInternal() { sceneInteractor.changeScene( getCollapseDestinationScene(), @@ -143,9 +149,9 @@ constructor( private fun getCollapseDestinationScene(): SceneKey { return if (deviceEntryInteractor.isDeviceEntered.value) { - SceneKey.Gone + Scenes.Gone } else { - SceneKey.Lockscreen + Scenes.Lockscreen } } @@ -183,11 +189,11 @@ constructor( } override fun expandToNotifications() { - sceneInteractor.changeScene(SceneKey.Shade, "ShadeController.animateExpandShade") + sceneInteractor.changeScene(Scenes.Shade, "ShadeController.animateExpandShade") } override fun expandToQs() { - sceneInteractor.changeScene(SceneKey.QuickSettings, "ShadeController.animateExpandQs") + sceneInteractor.changeScene(Scenes.QuickSettings, "ShadeController.animateExpandQs") } override fun setVisibilityListener(listener: ShadeVisibilityListener) { @@ -237,7 +243,7 @@ constructor( } override fun isExpandedVisible(): Boolean { - return sceneInteractor.currentScene.value != SceneKey.Gone + return sceneInteractor.currentScene.value != Scenes.Gone } override fun onStatusBarTouch(event: MotionEvent) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt index 5632766f2633..504dbfdafd0b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt @@ -17,6 +17,8 @@ package com.android.systemui.shade import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.plugins.qs.QSContainerController +import com.android.systemui.qs.ui.adapter.QSSceneAdapterImpl import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.shade.data.repository.PrivacyChipRepository import com.android.systemui.shade.data.repository.PrivacyChipRepositoryImpl @@ -111,6 +113,26 @@ abstract class ShadeModule { sceneContainerOff.get() } } + + @Provides + @SysUISingleton + fun provideQuickSettingsController( + sceneContainerFlags: SceneContainerFlags, + sceneContainerOn: Provider<QuickSettingsControllerSceneImpl>, + sceneContainerOff: Provider<QuickSettingsControllerImpl>, + ): QuickSettingsController { + return if (sceneContainerFlags.isEnabled()) { + sceneContainerOn.get() + } else { + sceneContainerOff.get() + } + } + + @Provides + @SysUISingleton + fun providesQSContainerController(impl: QSSceneAdapterImpl): QSContainerController { + return impl + } } @Binds diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt index 44c6a82d93ca..7a1637eeab15 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt @@ -31,12 +31,6 @@ import java.util.function.Consumer * @see NotificationPanelViewController */ interface ShadeViewController { - /** Expand the shade either animated or instantly. */ - fun expand(animate: Boolean) - - /** Animates to an expanded shade with QS expanded. If the shade starts expanded, expands QS. */ - fun expandToQs() - /** Returns whether the shade is expanding or collapsing itself or quick settings. */ val isExpandingOrCollapsing: Boolean @@ -58,9 +52,6 @@ interface ShadeViewController { /** Collapses the shade. */ fun collapse(animate: Boolean, delayed: Boolean, speedUpFactor: Float) - /** Collapses the shade with an animation duration in milliseconds. */ - fun collapseWithDuration(animationDuration: Int) - /** Collapses the shade instantly without animation. */ fun instantCollapse() @@ -102,9 +93,6 @@ interface ShadeViewController { /** Returns the StatusBarState. */ val barState: Int - /** Sets the amount of progress in the status bar launch animation. */ - fun applyLaunchAnimationProgress(linearProgress: Float) - /** Sets the alpha value of the shade to a value between 0 and 255. */ fun setAlpha(alpha: Int, animate: Boolean) diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt index 7a181f106514..3be3f6b1441d 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt @@ -28,8 +28,6 @@ import javax.inject.Inject /** Empty implementation of ShadeViewController for variants with no shade. */ class ShadeViewControllerEmptyImpl @Inject constructor() : ShadeViewController, ShadeBackActionInteractor, ShadeLockscreenInteractor { - override fun expand(animate: Boolean) {} - override fun expandToQs() {} override fun expandToNotifications() {} override val isExpandingOrCollapsing: Boolean = false override val isExpanded: Boolean = false @@ -37,7 +35,6 @@ class ShadeViewControllerEmptyImpl @Inject constructor() : override val isShadeFullyExpanded: Boolean = false override fun collapse(delayed: Boolean, speedUpFactor: Float) {} override fun collapse(animate: Boolean, delayed: Boolean, speedUpFactor: Float) {} - override fun collapseWithDuration(animationDuration: Int) {} override fun instantCollapse() {} override fun animateCollapseQs(fullyCollapse: Boolean) {} override fun canBeCollapsed(): Boolean = false @@ -55,7 +52,6 @@ class ShadeViewControllerEmptyImpl @Inject constructor() : override fun dozeTimeTick() {} override fun resetViews(animate: Boolean) {} override val barState: Int = 0 - override fun applyLaunchAnimationProgress(linearProgress: Float) {} override fun closeUserSwitcherIfOpen(): Boolean { return false } diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt index 1ee6d3845553..eaac8ae9dd3a 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt @@ -16,10 +16,10 @@ package com.android.systemui.shade.domain.interactor +import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.dagger.SysUISingleton import com.android.systemui.scene.domain.interactor.SceneInteractor -import com.android.systemui.scene.shared.model.ObservableTransitionState -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.data.repository.ShadeAnimationRepository import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -44,10 +44,10 @@ constructor( is ObservableTransitionState.Idle -> flowOf(false) is ObservableTransitionState.Transition -> if ( - (state.fromScene == SceneKey.Shade && - state.toScene != SceneKey.QuickSettings) || - (state.fromScene == SceneKey.QuickSettings && - state.toScene != SceneKey.Shade) + (state.fromScene == Scenes.Shade && + state.toScene != Scenes.QuickSettings) || + (state.fromScene == Scenes.QuickSettings && + state.toScene != Scenes.Shade) ) { state.isUserInputOngoing.map { !it } } else { diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt index a2e25983e68f..3a8ba7a0696b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt @@ -18,7 +18,7 @@ package com.android.systemui.shade.domain.interactor import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.scene.domain.interactor.SceneInteractor -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -36,12 +36,12 @@ constructor( val key = if (fullyCollapse) { if (deviceEntryInteractor.isDeviceEntered.value) { - SceneKey.Gone + Scenes.Gone } else { - SceneKey.Lockscreen + Scenes.Lockscreen } } else { - SceneKey.Shade + Scenes.Shade } sceneInteractor.changeScene(key, "animateCollapseQs") } diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt index 08f2c402f9e9..67cac3d4111c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt @@ -16,11 +16,12 @@ package com.android.systemui.shade.domain.interactor +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.scene.domain.interactor.SceneInteractor -import com.android.systemui.scene.shared.model.ObservableTransitionState -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -45,9 +46,9 @@ constructor( sceneInteractor: SceneInteractor, sharedNotificationContainerInteractor: SharedNotificationContainerInteractor, ) : BaseShadeInteractor { - override val shadeExpansion: Flow<Float> = sceneBasedExpansion(sceneInteractor, SceneKey.Shade) + override val shadeExpansion: Flow<Float> = sceneBasedExpansion(sceneInteractor, Scenes.Shade) - private val sceneBasedQsExpansion = sceneBasedExpansion(sceneInteractor, SceneKey.QuickSettings) + private val sceneBasedQsExpansion = sceneBasedExpansion(sceneInteractor, Scenes.QuickSettings) override val qsExpansion: StateFlow<Float> = combine( @@ -75,7 +76,7 @@ constructor( when (state) { is ObservableTransitionState.Idle -> false is ObservableTransitionState.Transition -> - state.toScene == SceneKey.QuickSettings && state.fromScene != SceneKey.Shade + state.toScene == Scenes.QuickSettings && state.fromScene != Scenes.Shade } } .distinctUntilChanged() @@ -84,7 +85,7 @@ constructor( sceneInteractor.transitionState .map { state -> when (state) { - is ObservableTransitionState.Idle -> state.scene == SceneKey.QuickSettings + is ObservableTransitionState.Idle -> state.scene == Scenes.QuickSettings is ObservableTransitionState.Transition -> false } } @@ -100,10 +101,10 @@ constructor( .stateIn(scope, SharingStarted.Eagerly, false) override val isUserInteractingWithShade: Flow<Boolean> = - sceneBasedInteracting(sceneInteractor, SceneKey.Shade) + sceneBasedInteracting(sceneInteractor, Scenes.Shade) override val isUserInteractingWithQs: Flow<Boolean> = - sceneBasedInteracting(sceneInteractor, SceneKey.QuickSettings) + sceneBasedInteracting(sceneInteractor, Scenes.QuickSettings) /** * Returns a flow that uses scene transition progress to and from a scene that is pulled down diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt index 21a782e43b78..1f78ae8b6e99 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt @@ -19,7 +19,7 @@ package com.android.systemui.shade.domain.interactor import com.android.keyguard.LockIconViewController import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.scene.domain.interactor.SceneInteractor -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.ShadeLockscreenInteractor import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -87,7 +87,7 @@ constructor( private fun changeToShadeScene() { sceneInteractor.changeScene( - SceneKey.Shade, + Scenes.Shade, "ShadeLockscreenInteractorImpl.expandToNotifications", ) } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt index 38358a8f244e..c9aa51c31060 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt @@ -16,12 +16,13 @@ package com.android.systemui.shade.ui.viewmodel +import com.android.compose.animation.scene.SceneKey import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.media.controls.domain.pipeline.MediaDataManager import com.android.systemui.qs.ui.adapter.QSSceneAdapter -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -67,7 +68,7 @@ constructor( /** Whether or not the shade container should be clickable. */ val isClickable: StateFlow<Boolean> = upDestinationSceneKey - .map { it == SceneKey.Lockscreen } + .map { it == Scenes.Lockscreen } .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), @@ -82,9 +83,9 @@ constructor( canSwipeToDismiss: Boolean?, ): SceneKey { return when { - canSwipeToDismiss == true -> SceneKey.Lockscreen - isUnlocked -> SceneKey.Gone - else -> SceneKey.Lockscreen + canSwipeToDismiss == true -> Scenes.Lockscreen + isUnlocked -> Scenes.Gone + else -> Scenes.Lockscreen } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java index a4741a509d72..ef4e5308e18e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java @@ -482,42 +482,42 @@ public final class KeyboardShortcutListSearch { context.getString(R.string.keyboard_shortcut_group_system), new ArrayList<>()); List<ShortcutKeyGroupMultiMappingInfo> infoList = Arrays.asList( - /* Access notification shade: Meta + N */ + /* Access list of all apps and search (i.e. Search/Launcher): Meta */ new ShortcutKeyGroupMultiMappingInfo( - context.getString(R.string.group_system_access_notification_shade), + context.getString(R.string.group_system_access_all_apps_search), Arrays.asList( - Pair.create(KeyEvent.KEYCODE_N, KeyEvent.META_META_ON))), - /* Take a full screenshot: Meta + Ctrl + S */ + Pair.create(KeyEvent.KEYCODE_UNKNOWN, KeyEvent.META_META_ON))), + /* Access home screen: Meta + H, Meta + Enter */ new ShortcutKeyGroupMultiMappingInfo( - context.getString(R.string.group_system_full_screenshot), + context.getString(R.string.group_system_access_home_screen), Arrays.asList( - Pair.create( - KeyEvent.KEYCODE_S, - KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON))), - /* Access list of system / apps shortcuts: Meta + / */ + Pair.create(KeyEvent.KEYCODE_H, KeyEvent.META_META_ON), + Pair.create(KeyEvent.KEYCODE_ENTER, KeyEvent.META_META_ON))), + /* Overview of open apps: Meta + Tab */ new ShortcutKeyGroupMultiMappingInfo( - context.getString(R.string.group_system_access_system_app_shortcuts), + context.getString(R.string.group_system_overview_open_apps), Arrays.asList( - Pair.create(KeyEvent.KEYCODE_SLASH, KeyEvent.META_META_ON))), + Pair.create(KeyEvent.KEYCODE_TAB, KeyEvent.META_META_ON))), /* Back: go back to previous state (back button) */ - /* Meta + Escape, Meta + Grave, Meta + backspace, Meta + left arrow */ + /* Meta + Escape, Meta + backspace, Meta + left arrow */ new ShortcutKeyGroupMultiMappingInfo( context.getString(R.string.group_system_go_back), Arrays.asList( Pair.create(KeyEvent.KEYCODE_ESCAPE, KeyEvent.META_META_ON), Pair.create(KeyEvent.KEYCODE_DEL, KeyEvent.META_META_ON), Pair.create(KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.META_META_ON))), - /* Access home screen: Meta + H, Meta + Enter */ + /* Take a full screenshot: Meta + Ctrl + S */ new ShortcutKeyGroupMultiMappingInfo( - context.getString(R.string.group_system_access_home_screen), + context.getString(R.string.group_system_full_screenshot), Arrays.asList( - Pair.create(KeyEvent.KEYCODE_H, KeyEvent.META_META_ON), - Pair.create(KeyEvent.KEYCODE_ENTER, KeyEvent.META_META_ON))), - /* Overview of open apps: Meta + Tab */ + Pair.create( + KeyEvent.KEYCODE_S, + KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON))), + /* Access list of system / apps shortcuts: Meta + / */ new ShortcutKeyGroupMultiMappingInfo( - context.getString(R.string.group_system_overview_open_apps), + context.getString(R.string.group_system_access_system_app_shortcuts), Arrays.asList( - Pair.create(KeyEvent.KEYCODE_TAB, KeyEvent.META_META_ON))), + Pair.create(KeyEvent.KEYCODE_SLASH, KeyEvent.META_META_ON))), /* Cycle through recent apps (forward): Alt + Tab */ new ShortcutKeyGroupMultiMappingInfo( context.getString(R.string.group_system_cycle_forward), @@ -530,26 +530,16 @@ public final class KeyboardShortcutListSearch { Pair.create( KeyEvent.KEYCODE_TAB, KeyEvent.META_SHIFT_ON | KeyEvent.META_ALT_ON))), - /* Access list of all apps and search (i.e. Search/Launcher): Meta */ - new ShortcutKeyGroupMultiMappingInfo( - context.getString(R.string.group_system_access_all_apps_search), - Arrays.asList( - Pair.create(KeyEvent.KEYCODE_UNKNOWN, KeyEvent.META_META_ON))), /* Hide and (re)show taskbar: Meta + T */ new ShortcutKeyGroupMultiMappingInfo( context.getString(R.string.group_system_hide_reshow_taskbar), Arrays.asList( Pair.create(KeyEvent.KEYCODE_T, KeyEvent.META_META_ON))), - /* Access system settings: Meta + I */ - new ShortcutKeyGroupMultiMappingInfo( - context.getString(R.string.group_system_access_system_settings), - Arrays.asList( - Pair.create(KeyEvent.KEYCODE_I, KeyEvent.META_META_ON))), - /* Access Google Assistant: Meta + A */ + /* Access notification shade: Meta + N */ new ShortcutKeyGroupMultiMappingInfo( - context.getString(R.string.group_system_access_google_assistant), + context.getString(R.string.group_system_access_notification_shade), Arrays.asList( - Pair.create(KeyEvent.KEYCODE_A, KeyEvent.META_META_ON))), + Pair.create(KeyEvent.KEYCODE_N, KeyEvent.META_META_ON))), /* Lock screen: Meta + L */ new ShortcutKeyGroupMultiMappingInfo( context.getString(R.string.group_system_lock_screen), @@ -561,7 +551,17 @@ public final class KeyboardShortcutListSearch { Arrays.asList( Pair.create( KeyEvent.KEYCODE_N, - KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON))) + KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON))), + /* Access system settings: Meta + I */ + new ShortcutKeyGroupMultiMappingInfo( + context.getString(R.string.group_system_access_system_settings), + Arrays.asList( + Pair.create(KeyEvent.KEYCODE_I, KeyEvent.META_META_ON))), + /* Access Google Assistant: Meta + A */ + new ShortcutKeyGroupMultiMappingInfo( + context.getString(R.string.group_system_access_google_assistant), + Arrays.asList( + Pair.create(KeyEvent.KEYCODE_A, KeyEvent.META_META_ON))) ); for (ShortcutKeyGroupMultiMappingInfo info : infoList) { systemGroup.addItem(info.getShortcutMultiMappingInfo()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java index 36fc9bb3a2da..e0dd7f0603e5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java @@ -36,6 +36,7 @@ import android.view.animation.Interpolator; import androidx.annotation.NonNull; import com.android.app.animation.Interpolators; +import com.android.compose.animation.scene.SceneKey; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.InteractionJankMonitor; @@ -49,7 +50,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController.StateList import com.android.systemui.res.R; import com.android.systemui.scene.domain.interactor.SceneInteractor; import com.android.systemui.scene.shared.flag.SceneContainerFlag; -import com.android.systemui.scene.shared.model.SceneKey; +import com.android.systemui.scene.shared.model.Scenes; import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.policy.CallbackController; @@ -659,11 +660,11 @@ public class StatusBarStateControllerImpl implements } private static final Map<SceneKey, Integer> sStatusBarStateByLockedSceneKey = Map.of( - SceneKey.Lockscreen.INSTANCE, StatusBarState.KEYGUARD, - SceneKey.Bouncer.INSTANCE, StatusBarState.KEYGUARD, - SceneKey.Communal.INSTANCE, StatusBarState.KEYGUARD, - SceneKey.Shade.INSTANCE, StatusBarState.SHADE_LOCKED, - SceneKey.QuickSettings.INSTANCE, StatusBarState.SHADE_LOCKED + Scenes.Lockscreen, StatusBarState.KEYGUARD, + Scenes.Bouncer, StatusBarState.KEYGUARD, + Scenes.Communal, StatusBarState.KEYGUARD, + Scenes.Shade, StatusBarState.SHADE_LOCKED, + Scenes.QuickSettings, StatusBarState.SHADE_LOCKED ); /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 8d5302231fc1..5f3a83aa35e0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -1761,7 +1761,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * Constructs an ExpandableNotificationRow. Used by layout inflation. * * @param context passed to image resolver - * @param attrs attributes used to initialize parent view + * @param attrs attributes used to initialize parent view */ public ExpandableNotificationRow(Context context, AttributeSet attrs) { this(context, attrs, context); @@ -1775,9 +1775,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * AsyncLayoutFactory} in {@link RowInflaterTask}. * * @param context context context of the view - * @param attrs attributes used to initialize parent view - * @param entry notification that the row will be associated to (determines the user for the - * ImageResolver) + * @param attrs attributes used to initialize parent view + * @param entry notification that the row will be associated to (determines the user for the + * ImageResolver) */ public ExpandableNotificationRow(Context context, AttributeSet attrs, NotificationEntry entry) { this(context, attrs, userContextForEntry(context, entry)); @@ -2028,7 +2028,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return traceTag; } - return traceTag + "(" + getEntry().getNotificationStyle() + ")"; + return traceTag + "(" + getEntry().getNotificationStyle() + ")"; } @Override @@ -3083,6 +3083,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView onStartedRunnable.run(); } } + @Override public void onAnimationEnd(Animator animation) { ExpandableNotificationRow.super.performRemoveAnimation( @@ -3777,6 +3778,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView pw.print(", privateShowing: " + (showingLayout == mPrivateLayout)); pw.print(", mShowNoBackground: " + mShowNoBackground); pw.println(); + if (NotificationContentView.INCLUDE_HEIGHTS_TO_DUMP) { + dumpHeights(pw); + } showingLayout.dump(pw, args); if (getViewState() != null) { @@ -3820,6 +3824,34 @@ public class ExpandableNotificationRow extends ActivatableNotificationView }); } + private void dumpHeights(IndentingPrintWriter pw) { + pw.print("Heights: "); + pw.print("intrinsic", getIntrinsicHeight()); + pw.print("actual", getActualHeight()); + pw.print("maxContent", getMaxContentHeight()); + pw.print("maxExpanded", getMaxExpandHeight()); + pw.print("collapsed", getCollapsedHeight()); + pw.print("headsup", getHeadsUpHeight()); + pw.print("headsup without header", getHeadsUpHeightWithoutHeader()); + pw.print("minHeight", getMinHeight()); + pw.print("pinned headsup", getPinnedHeadsUpHeight( + true /* atLeastMinHeight */)); + pw.println(); + pw.print("Intrinsic Height Factors: "); + pw.print("isUserLocked()", isUserLocked()); + pw.print("isChildInGroup()", isChildInGroup()); + pw.print("isGroupExpanded()", isGroupExpanded()); + pw.print("sensitive", mSensitive); + pw.print("hideSensitiveForIntrinsicHeight", mHideSensitiveForIntrinsicHeight); + pw.print("isSummaryWithChildren", mIsSummaryWithChildren); + pw.print("canShowHeadsUp()", canShowHeadsUp()); + pw.print("isHeadsUpState()", isHeadsUpState()); + pw.print("isPinned()", isPinned()); + pw.print("headsupDisappearRunning", mHeadsupDisappearRunning); + pw.print("isExpanded()", isExpanded()); + pw.println(); + } + private void logKeepInParentChildDetached(ExpandableNotificationRow child) { if (mLogger != null) { mLogger.logKeepInParentChildDetached(child.getEntry(), getEntry()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index d308fa583f71..c17ee3991713 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -833,143 +833,143 @@ public class NotificationContentInflater implements NotificationRowContentBinder @Nullable InflationCallback endListener, NotificationEntry entry, ExpandableNotificationRow row, NotificationContentInflaterLogger logger) { Assert.isMainThread(); + if (!runningInflations.isEmpty()) { + return false; + } NotificationContentView privateLayout = row.getPrivateLayout(); NotificationContentView publicLayout = row.getPublicLayout(); - if (runningInflations.isEmpty()) { - logger.logAsyncTaskProgress(entry, "finishing"); - boolean setRepliesAndActions = true; - if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) { - if (result.inflatedContentView != null) { - // New view case - privateLayout.setContractedChild(result.inflatedContentView); - remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED, - result.newContentView); - } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)) { - // Reinflation case. Only update if it's still cached (i.e. view has not been - // freed while inflating). - remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED, - result.newContentView); - } - setRepliesAndActions = true; + logger.logAsyncTaskProgress(entry, "finishing"); + boolean setRepliesAndActions = true; + if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) { + if (result.inflatedContentView != null) { + // New view case + privateLayout.setContractedChild(result.inflatedContentView); + remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED, + result.newContentView); + } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)) { + // Reinflation case. Only update if it's still cached (i.e. view has not been + // freed while inflating). + remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED, + result.newContentView); } + setRepliesAndActions = true; + } - if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) { - if (result.inflatedExpandedView != null) { - privateLayout.setExpandedChild(result.inflatedExpandedView); - remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED, - result.newExpandedView); - } else if (result.newExpandedView == null) { - privateLayout.setExpandedChild(null); - remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED); - } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)) { - remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED, - result.newExpandedView); - } - if (result.newExpandedView != null) { - privateLayout.setExpandedInflatedSmartReplies( - result.expandedInflatedSmartReplies); - } else { - privateLayout.setExpandedInflatedSmartReplies(null); - } - row.setExpandable(result.newExpandedView != null); - setRepliesAndActions = true; + if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) { + if (result.inflatedExpandedView != null) { + privateLayout.setExpandedChild(result.inflatedExpandedView); + remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED, + result.newExpandedView); + } else if (result.newExpandedView == null) { + privateLayout.setExpandedChild(null); + remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED); + } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)) { + remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED, + result.newExpandedView); } + if (result.newExpandedView != null) { + privateLayout.setExpandedInflatedSmartReplies( + result.expandedInflatedSmartReplies); + } else { + privateLayout.setExpandedInflatedSmartReplies(null); + } + row.setExpandable(result.newExpandedView != null); + setRepliesAndActions = true; + } - if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) { - if (result.inflatedHeadsUpView != null) { - privateLayout.setHeadsUpChild(result.inflatedHeadsUpView); - remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP, - result.newHeadsUpView); - } else if (result.newHeadsUpView == null) { - privateLayout.setHeadsUpChild(null); - remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP); - } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)) { - remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP, - result.newHeadsUpView); - } - if (result.newHeadsUpView != null) { - privateLayout.setHeadsUpInflatedSmartReplies( - result.headsUpInflatedSmartReplies); - } else { - privateLayout.setHeadsUpInflatedSmartReplies(null); - } - setRepliesAndActions = true; + if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) { + if (result.inflatedHeadsUpView != null) { + privateLayout.setHeadsUpChild(result.inflatedHeadsUpView); + remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP, + result.newHeadsUpView); + } else if (result.newHeadsUpView == null) { + privateLayout.setHeadsUpChild(null); + remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP); + } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)) { + remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP, + result.newHeadsUpView); } + if (result.newHeadsUpView != null) { + privateLayout.setHeadsUpInflatedSmartReplies( + result.headsUpInflatedSmartReplies); + } else { + privateLayout.setHeadsUpInflatedSmartReplies(null); + } + setRepliesAndActions = true; + } - if (AsyncHybridViewInflation.isEnabled() - && (reInflateFlags & FLAG_CONTENT_VIEW_SINGLE_LINE) != 0) { - HybridNotificationView viewHolder = result.mInflatedSingleLineViewHolder; - SingleLineViewModel viewModel = result.mInflatedSingleLineViewModel; - if (viewHolder != null && viewModel != null) { - if (viewModel.isConversation()) { - SingleLineConversationViewBinder.bind( - result.mInflatedSingleLineViewModel, - result.mInflatedSingleLineViewHolder - ); - } else { - SingleLineViewBinder.bind(result.mInflatedSingleLineViewModel, - result.mInflatedSingleLineViewHolder); - } - privateLayout.setSingleLineView(result.mInflatedSingleLineViewHolder); + if (AsyncHybridViewInflation.isEnabled() + && (reInflateFlags & FLAG_CONTENT_VIEW_SINGLE_LINE) != 0) { + HybridNotificationView viewHolder = result.mInflatedSingleLineViewHolder; + SingleLineViewModel viewModel = result.mInflatedSingleLineViewModel; + if (viewHolder != null && viewModel != null) { + if (viewModel.isConversation()) { + SingleLineConversationViewBinder.bind( + result.mInflatedSingleLineViewModel, + result.mInflatedSingleLineViewHolder + ); + } else { + SingleLineViewBinder.bind(result.mInflatedSingleLineViewModel, + result.mInflatedSingleLineViewHolder); } + privateLayout.setSingleLineView(result.mInflatedSingleLineViewHolder); } + } - if (setRepliesAndActions) { - privateLayout.setInflatedSmartReplyState(result.inflatedSmartReplyState); - } + if (setRepliesAndActions) { + privateLayout.setInflatedSmartReplyState(result.inflatedSmartReplyState); + } - if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) { - if (result.inflatedPublicView != null) { - publicLayout.setContractedChild(result.inflatedPublicView); - remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC, - result.newPublicView); - } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)) { - remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC, - result.newPublicView); - } + if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) { + if (result.inflatedPublicView != null) { + publicLayout.setContractedChild(result.inflatedPublicView); + remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC, + result.newPublicView); + } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)) { + remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC, + result.newPublicView); } + } - if (AsyncGroupHeaderViewInflation.isEnabled()) { - if ((reInflateFlags & FLAG_GROUP_SUMMARY_HEADER) != 0) { - if (result.mInflatedGroupHeaderView != null) { - row.setIsLowPriority(false); - row.setGroupHeader(/* headerView= */ result.mInflatedGroupHeaderView); - remoteViewCache.putCachedView(entry, FLAG_GROUP_SUMMARY_HEADER, - result.mNewGroupHeaderView); - } else if (remoteViewCache.hasCachedView(entry, FLAG_GROUP_SUMMARY_HEADER)) { - // Re-inflation case. Only update if it's still cached (i.e. view has not - // been freed while inflating). - remoteViewCache.putCachedView(entry, FLAG_GROUP_SUMMARY_HEADER, - result.mNewGroupHeaderView); - } + if (AsyncGroupHeaderViewInflation.isEnabled()) { + if ((reInflateFlags & FLAG_GROUP_SUMMARY_HEADER) != 0) { + if (result.mInflatedGroupHeaderView != null) { + row.setIsLowPriority(false); + row.setGroupHeader(/* headerView= */ result.mInflatedGroupHeaderView); + remoteViewCache.putCachedView(entry, FLAG_GROUP_SUMMARY_HEADER, + result.mNewGroupHeaderView); + } else if (remoteViewCache.hasCachedView(entry, FLAG_GROUP_SUMMARY_HEADER)) { + // Re-inflation case. Only update if it's still cached (i.e. view has not + // been freed while inflating). + remoteViewCache.putCachedView(entry, FLAG_GROUP_SUMMARY_HEADER, + result.mNewGroupHeaderView); } + } - if ((reInflateFlags & FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER) != 0) { - if (result.mInflatedLowPriorityGroupHeaderView != null) { - // New view case, set row to low priority - row.setIsLowPriority(true); - row.setLowPriorityGroupHeader( - /* headerView= */ result.mInflatedLowPriorityGroupHeaderView); - remoteViewCache.putCachedView(entry, FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER, - result.mNewLowPriorityGroupHeaderView); - } else if (remoteViewCache.hasCachedView(entry, - FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER)) { - // Re-inflation case. Only update if it's still cached (i.e. view has not - // been freed while inflating). - remoteViewCache.putCachedView(entry, FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER, - result.mNewGroupHeaderView); - } + if ((reInflateFlags & FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER) != 0) { + if (result.mInflatedLowPriorityGroupHeaderView != null) { + // New view case, set row to low priority + row.setIsLowPriority(true); + row.setLowPriorityGroupHeader( + /* headerView= */ result.mInflatedLowPriorityGroupHeaderView); + remoteViewCache.putCachedView(entry, FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER, + result.mNewLowPriorityGroupHeaderView); + } else if (remoteViewCache.hasCachedView(entry, + FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER)) { + // Re-inflation case. Only update if it's still cached (i.e. view has not + // been freed while inflating). + remoteViewCache.putCachedView(entry, FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER, + result.mNewGroupHeaderView); } } + } - entry.headsUpStatusBarText = result.headsUpStatusBarText; - entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic; - if (endListener != null) { - endListener.onAsyncInflationFinished(entry); - } - return true; + entry.headsUpStatusBarText = result.headsUpStatusBarText; + entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic; + if (endListener != null) { + endListener.onAsyncInflationFinished(entry); } - return false; + return true; } private static RemoteViews createExpandedView(Notification.Builder builder, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 50bc3d31aa42..137e1b2ab809 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -70,6 +70,7 @@ import com.android.systemui.statusbar.policy.SmartReplyStateInflaterKt; import com.android.systemui.statusbar.policy.SmartReplyView; import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent; import com.android.systemui.util.Compile; +import com.android.systemui.util.DumpUtilsKt; import java.io.PrintWriter; import java.util.ArrayList; @@ -97,6 +98,8 @@ public class NotificationContentView extends FrameLayout implements Notification private static final int UNDEFINED = -1; + protected static final boolean INCLUDE_HEIGHTS_TO_DUMP = true; + private final Rect mClipBounds = new Rect(); private int mMinContractedHeight; @@ -2196,6 +2199,11 @@ public class NotificationContentView extends FrameLayout implements Notification pw.print("null"); } pw.println(); + + if (INCLUDE_HEIGHTS_TO_DUMP) { + dumpContentDimensions(DumpUtilsKt.asIndenting(pw)); + } + pw.println("mBubblesEnabledForUser: " + mBubblesEnabledForUser); pw.print("RemoteInputViews { "); @@ -2216,6 +2224,69 @@ public class NotificationContentView extends FrameLayout implements Notification pw.println(" }"); } + private String visibleTypeToString(int visibleType) { + return switch (visibleType) { + case VISIBLE_TYPE_CONTRACTED -> "CONTRACTED"; + case VISIBLE_TYPE_EXPANDED -> "EXPANDED"; + case VISIBLE_TYPE_HEADSUP -> "HEADSUP"; + case VISIBLE_TYPE_SINGLELINE -> "SINGLELINE"; + default -> "NONE"; + }; + } + + /** Add content views to dump */ + private void dumpContentDimensions(IndentingPrintWriter pw) { + pw.print("ContentDimensions: "); + pw.print("visibleType(String)", visibleTypeToString(mVisibleType)); + pw.print("measured width", getMeasuredWidth()); + pw.print("measured height", getMeasuredHeight()); + pw.print("maxHeight", getMaxHeight()); + pw.print("minHeight", getMinHeight()); + pw.println(); + pw.println("ChildViews:"); + DumpUtilsKt.withIncreasedIndent(pw, () -> { + final View contractedChild = mContractedChild; + if (contractedChild != null) { + dumpChildViewDimensions(pw, contractedChild, "Contracted Child:"); + pw.println(); + } + + final View expandedChild = mExpandedChild; + if (expandedChild != null) { + dumpChildViewDimensions(pw, expandedChild, "Expanded Child:"); + pw.println(); + } + + final View headsUpChild = mHeadsUpChild; + if (headsUpChild != null) { + dumpChildViewDimensions(pw, headsUpChild, "HeadsUp Child:"); + pw.println(); + } + final View singleLineView = mSingleLineView; + if (singleLineView != null) { + dumpChildViewDimensions(pw, singleLineView, "Single Line View:"); + pw.println(); + } + + }); + final int expandedRemoteInputHeight = getExtraRemoteInputHeight(mExpandedRemoteInput); + final int headsUpRemoteInputHeight = getExtraRemoteInputHeight(mHeadsUpRemoteInput); + pw.print("expandedRemoteInputHeight", expandedRemoteInputHeight); + pw.print("headsUpRemoteInputHeight", headsUpRemoteInputHeight); + pw.println(); + } + + private void dumpChildViewDimensions(IndentingPrintWriter pw, View view, + String name) { + pw.print(name + " "); + DumpUtilsKt.withIncreasedIndent(pw, () -> { + pw.print("width", view.getWidth()); + pw.print("height", view.getHeight()); + pw.print("measuredWidth", view.getMeasuredWidth()); + pw.print("measuredHeight", view.getMeasuredHeight()); + }); + } + /** Add any existing SmartReplyView to the dump */ public void dumpSmartReplies(IndentingPrintWriter pw) { if (mHeadsUpSmartReplyView != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java index 9445d56ab509..ea3036e35c1b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java @@ -31,6 +31,7 @@ import androidx.asynclayoutinflater.view.AsyncLayoutInflater; import com.android.systemui.res.R; import com.android.systemui.statusbar.InflationTask; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.util.time.SystemClock; import javax.inject.Inject; @@ -46,9 +47,14 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf private NotificationEntry mEntry; private boolean mCancelled; private Throwable mInflateOrigin; + private final SystemClock mSystemClock; + private final RowInflaterTaskLogger mLogger; + private long mInflateStartTimeMs; @Inject - public RowInflaterTask() { + public RowInflaterTask(SystemClock systemClock, RowInflaterTaskLogger logger) { + mSystemClock = systemClock; + mLogger = logger; } /** @@ -61,29 +67,49 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf } mListener = listener; AsyncLayoutInflater inflater = com.android.systemui.Flags.notificationRowUserContext() - ? new AsyncLayoutInflater(context, new RowAsyncLayoutInflater(entry)) + ? new AsyncLayoutInflater(context, makeRowInflater(entry)) : new AsyncLayoutInflater(context); mEntry = entry; entry.setInflationTask(this); + + mLogger.logInflateStart(entry); + mInflateStartTimeMs = mSystemClock.elapsedRealtime(); inflater.inflate(R.layout.status_bar_notification_row, parent, this); } + private RowAsyncLayoutInflater makeRowInflater(NotificationEntry entry) { + return new RowAsyncLayoutInflater(entry, mSystemClock, mLogger); + } + @VisibleForTesting static class RowAsyncLayoutInflater implements AsyncLayoutFactory { private final NotificationEntry mEntry; + private final SystemClock mSystemClock; + private final RowInflaterTaskLogger mLogger; - RowAsyncLayoutInflater(NotificationEntry entry) { + RowAsyncLayoutInflater(NotificationEntry entry, SystemClock systemClock, + RowInflaterTaskLogger logger) { mEntry = entry; + mSystemClock = systemClock; + mLogger = logger; } @Nullable @Override public View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) { - if (name.equals(ExpandableNotificationRow.class.getName())) { - return new ExpandableNotificationRow(context, attrs, mEntry); + if (!name.equals(ExpandableNotificationRow.class.getName())) { + return null; } - return null; + + final long startMs = mSystemClock.elapsedRealtime(); + final ExpandableNotificationRow row = + new ExpandableNotificationRow(context, attrs, mEntry); + final long elapsedMs = mSystemClock.elapsedRealtime() - startMs; + + mLogger.logCreatedRow(mEntry, elapsedMs); + + return row; } @Nullable @@ -101,6 +127,9 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf @Override public void onInflateFinished(View view, int resid, ViewGroup parent) { + final long elapsedMs = mSystemClock.elapsedRealtime() - mInflateStartTimeMs; + mLogger.logInflateFinish(mEntry, elapsedMs, mCancelled); + if (!mCancelled) { try { mEntry.onInflationTaskFinished(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTaskLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTaskLogger.kt new file mode 100644 index 000000000000..94da6bec799b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTaskLogger.kt @@ -0,0 +1,62 @@ +/* + * 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.statusbar.notification.row + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.dagger.NotifInflationLog +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.logKey +import javax.inject.Inject + +class RowInflaterTaskLogger @Inject constructor(@NotifInflationLog private val buffer: LogBuffer) { + fun logInflateStart(entry: NotificationEntry) { + buffer.log( + TAG, + LogLevel.DEBUG, + { str1 = entry.logKey }, + { "started row inflation for $str1" } + ) + } + + fun logCreatedRow(entry: NotificationEntry, elapsedMs: Long) { + buffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = entry.logKey + long1 = elapsedMs + }, + { "created row in $long1 ms for $str1" } + ) + } + + fun logInflateFinish(entry: NotificationEntry, elapsedMs: Long, cancelled: Boolean) { + buffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = entry.logKey + long1 = elapsedMs + bool1 = cancelled + }, + { "finished ${if (bool1) "cancelled " else ""}row inflation in $long1 ms for $str1" } + ) + } +} + +private const val TAG = "RowInflaterTask" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 9c03405304ef..27db84f6715e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -319,6 +319,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable = new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { + if (SceneContainerFlag.isEnabled() && !mChildrenUpdateRequested) { + getViewTreeObserver().removeOnPreDrawListener(this); + return true; + } updateForcedScroll(); updateChildren(); mChildrenUpdateRequested = false; @@ -680,7 +684,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable res.getBoolean(R.bool.config_drawNotificationBackground); setOutlineProvider(mOutlineProvider); - boolean willDraw = mShouldDrawNotificationBackground || mDebugLines; + // We could set this whenever we 'requestChildUpdate' much like the viewTreeObserver, but + // that adds a bunch of complexity, and drawing nothing isn't *that* expensive. + boolean willDraw = SceneContainerFlag.isEnabled() + || mShouldDrawNotificationBackground || mDebugLines; setWillNotDraw(!willDraw); mBackgroundPaint.setAntiAlias(true); if (mDebugLines) { @@ -816,7 +823,18 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } + private void onJustBeforeDraw() { + if (SceneContainerFlag.isEnabled()) { + if (mChildrenUpdateRequested) { + updateForcedScroll(); + updateChildren(); + mChildrenUpdateRequested = false; + } + } + } + protected void onDraw(Canvas canvas) { + onJustBeforeDraw(); if (mShouldDrawNotificationBackground && (mSections[0].getCurrentBounds().top < mSections[mSections.length - 1].getCurrentBounds().bottom diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackInteractor.kt new file mode 100644 index 000000000000..9cd46f5b3552 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackInteractor.kt @@ -0,0 +1,54 @@ +/* + * 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.statusbar.notification.stack.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.power.domain.interactor.PowerInteractor +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map + +/** Interactor exposing states related to the stack's context */ +@SysUISingleton +class NotificationStackInteractor +@Inject +constructor( + keyguardInteractor: KeyguardInteractor, + powerInteractor: PowerInteractor, +) { + val isShowingOnLockscreen: Flow<Boolean> = + combine( + // Non-notification UI elements of the notification list should not be visible + // on the lockscreen (incl. AOD and bouncer), except if the shade is opened on + // top. See b/219680200 for the footer and b/228790482, b/267060171 for the + // empty shade. + // TODO(b/323187006): There's a plan to eventually get rid of StatusBarState + // entirely, so this will have to be replaced at some point. + keyguardInteractor.statusBarState.map { it == StatusBarState.KEYGUARD }, + // The StatusBarState is unfortunately not updated quickly enough when the power + // button is pressed, so this is necessary in addition to the KEYGUARD check to + // cover the transition to AOD while going to sleep (b/190227875). + powerInteractor.isAsleep, + ) { (isOnKeyguard, isAsleep) -> + isOnKeyguard || isAsleep + } + .distinctUntilChanged() +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt index 7b502564ecb1..c85a18a8a896 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt @@ -16,9 +16,6 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.keyguard.shared.model.StatusBarState -import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor @@ -26,6 +23,7 @@ import com.android.systemui.statusbar.notification.domain.interactor.SeenNotific import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel +import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackInteractor import com.android.systemui.statusbar.policy.domain.interactor.UserSetupInteractor import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor import com.android.systemui.util.kotlin.combine @@ -51,8 +49,7 @@ constructor( val footer: Optional<FooterViewModel>, val logger: Optional<NotificationLoggerViewModel>, activeNotificationsInteractor: ActiveNotificationsInteractor, - keyguardInteractor: KeyguardInteractor, - powerInteractor: PowerInteractor, + notificationStackInteractor: NotificationStackInteractor, remoteInputInteractor: RemoteInputInteractor, seenNotificationsInteractor: SeenNotificationsInteractor, shadeInteractor: ShadeInteractor, @@ -71,7 +68,7 @@ constructor( } else { combine( activeNotificationsInteractor.areAnyNotificationsPresent, - isShowingOnLockscreen, + notificationStackInteractor.isShowingOnLockscreen, ) { hasNotifications, isShowingOnLockscreen -> hasNotifications || !isShowingOnLockscreen } @@ -86,7 +83,7 @@ constructor( combine( activeNotificationsInteractor.areAnyNotificationsPresent, shadeInteractor.isQsFullscreen, - isShowingOnLockscreen, + notificationStackInteractor.isShowingOnLockscreen, ) { hasNotifications, isQsFullScreen, isShowingOnLockscreen -> when { hasNotifications -> false @@ -109,7 +106,7 @@ constructor( combine( activeNotificationsInteractor.areAnyNotificationsPresent, userSetupInteractor.isUserSetUp, - isShowingOnLockscreen, + notificationStackInteractor.isShowingOnLockscreen, shadeInteractor.qsExpansion, shadeInteractor.isQsFullscreen, remoteInputInteractor.isRemoteInputActive, @@ -177,29 +174,6 @@ constructor( SHOW_WITH_ANIMATION(visible = true, canAnimate = true) } - private val isShowingOnLockscreen: Flow<Boolean> by lazy { - if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { - flowOf(false) - } else { - combine( - // Non-notification UI elements of the notification list should not be visible - // on the lockscreen (incl. AOD and bouncer), except if the shade is opened on - // top. See b/219680200 for the footer and b/228790482, b/267060171 for the - // empty shade. - // TODO(b/323187006): There's a plan to eventually get rid of StatusBarState - // entirely, so this will have to be replaced at some point. - keyguardInteractor.statusBarState.map { it == StatusBarState.KEYGUARD }, - // The StatusBarState is unfortunately not updated quickly enough when the power - // button is pressed, so this is necessary in addition to the KEYGUARD check to - // cover the transition to AOD while going to sleep (b/190227875). - powerInteractor.isAsleep, - ) { (isOnKeyguard, isAsleep) -> - isOnKeyguard || isAsleep - } - .distinctUntilChanged() - } - } - // TODO(b/308591475): This should be tracked separately by the empty shade. val areNotificationsHiddenInShade: Flow<Boolean> by lazy { if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt index 3a0f03f70e1c..8d1cdfac90c5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt @@ -17,13 +17,15 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel +import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dump.DumpManager import com.android.systemui.scene.domain.interactor.SceneInteractor -import com.android.systemui.scene.shared.model.ObservableTransitionState -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor +import com.android.systemui.util.kotlin.FlowDumperImpl import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow @@ -35,10 +37,11 @@ import kotlinx.coroutines.flow.distinctUntilChanged class NotificationStackAppearanceViewModel @Inject constructor( + dumpManager: DumpManager, stackAppearanceInteractor: NotificationStackAppearanceInteractor, shadeInteractor: ShadeInteractor, sceneInteractor: SceneInteractor, -) { +) : FlowDumperImpl(dumpManager) { /** * The expansion fraction of the notification stack. It should go from 0 to 1 when transitioning * from Gone to Shade scenes, and remain at 1 when in Lockscreen or Shade scenes and while @@ -51,7 +54,7 @@ constructor( ) { shadeExpansion, transitionState -> when (transitionState) { is ObservableTransitionState.Idle -> { - if (transitionState.scene == SceneKey.Lockscreen) { + if (transitionState.scene == Scenes.Lockscreen) { 1f } else { shadeExpansion @@ -59,10 +62,10 @@ constructor( } is ObservableTransitionState.Transition -> { if ( - (transitionState.fromScene == SceneKey.Shade && - transitionState.toScene == SceneKey.QuickSettings) || - (transitionState.fromScene == SceneKey.QuickSettings && - transitionState.toScene == SceneKey.Shade) + (transitionState.fromScene == Scenes.Shade && + transitionState.toScene == Scenes.QuickSettings) || + (transitionState.fromScene == Scenes.QuickSettings && + transitionState.toScene == Scenes.Shade) ) { 1f } else { @@ -72,10 +75,12 @@ constructor( } } .distinctUntilChanged() + .dumpWhileCollecting("expandFraction") /** The bounds of the notification stack in the current scene. */ - val stackBounds: Flow<NotificationContainerBounds> = stackAppearanceInteractor.stackBounds + val stackBounds: Flow<NotificationContainerBounds> = + stackAppearanceInteractor.stackBounds.dumpValue("stackBounds") /** The y-coordinate in px of top of the contents of the notification stack. */ - val contentTop: StateFlow<Float> = stackAppearanceInteractor.contentTop + val contentTop: StateFlow<Float> = stackAppearanceInteractor.contentTop.dumpValue("contentTop") } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index b4c88c5f9e79..f5237938ebfa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -23,6 +23,7 @@ import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState @@ -60,6 +61,7 @@ import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToLockscreenTran import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor +import com.android.systemui.util.kotlin.FlowDumperImpl import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -88,6 +90,7 @@ class SharedNotificationContainerViewModel @Inject constructor( private val interactor: SharedNotificationContainerInteractor, + dumpManager: DumpManager, @Application applicationScope: CoroutineScope, private val keyguardInteractor: KeyguardInteractor, private val keyguardTransitionInteractor: KeyguardTransitionInteractor, @@ -116,7 +119,7 @@ constructor( private val primaryBouncerToLockscreenTransitionViewModel: PrimaryBouncerToLockscreenTransitionViewModel, private val aodBurnInViewModel: AodBurnInViewModel, -) { +) : FlowDumperImpl(dumpManager) { private val statesForConstrainedNotifications: Set<KeyguardState> = setOf(AOD, LOCKSCREEN, DOZING, ALTERNATE_BOUNCER, PRIMARY_BOUNCER) @@ -126,6 +129,7 @@ constructor( .map { it.transitionState == STARTED || it.transitionState == RUNNING } .distinctUntilChanged() .onStart { emit(false) } + .dumpWhileCollecting("lockscreenToGlanceableHubRunning") private val glanceableHubToLockscreenRunning = keyguardTransitionInteractor @@ -133,6 +137,7 @@ constructor( .map { it.transitionState == STARTED || it.transitionState == RUNNING } .distinctUntilChanged() .onStart { emit(false) } + .dumpWhileCollecting("glanceableHubToLockscreenRunning") /** * Shade locked is a legacy concept, but necessary to mimic current functionality. Listen for @@ -148,8 +153,10 @@ constructor( isShadeLocked && (isQsExpanded || isShadeExpanded) } .distinctUntilChanged() + .dumpWhileCollecting("isShadeLocked") - val shadeCollapseFadeInComplete = MutableStateFlow(false) + private val shadeCollapseFadeInComplete = MutableStateFlow(false) + .dumpValue("shadeCollapseFadeInComplete") val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> = interactor.configurationBasedDimensions @@ -171,6 +178,7 @@ constructor( ) } .distinctUntilChanged() + .dumpWhileCollecting("configurationBasedDimensions") /** If the user is visually on one of the unoccluded lockscreen states. */ val isOnLockscreen: Flow<Boolean> = @@ -186,6 +194,7 @@ constructor( constrainedNotificationState || transitioningToOrFromLockscreen } .distinctUntilChanged() + .dumpWhileCollecting("isOnLockscreen") /** Are we purely on the keyguard without the shade/qs? */ val isOnLockscreenWithoutShade: Flow<Boolean> = @@ -204,6 +213,7 @@ constructor( started = SharingStarted.Eagerly, initialValue = false, ) + .dumpValue("isOnLockscreenWithoutShade") /** Are we purely on the glanceable hub without the shade/qs? */ val isOnGlanceableHubWithoutShade: Flow<Boolean> = @@ -222,6 +232,7 @@ constructor( started = SharingStarted.WhileSubscribed(), initialValue = false, ) + .dumpWhileCollecting("isOnGlanceableHubWithoutShade") /** * Fade in if the user swipes the shade back up, not if collapsed by going to AOD. This is @@ -284,6 +295,7 @@ constructor( started = SharingStarted.WhileSubscribed(), initialValue = false, ) + .dumpWhileCollecting("shadeCollapseFadeIn") /** * The container occupies the entire screen, and must be positioned relative to other elements. @@ -322,6 +334,7 @@ constructor( started = SharingStarted.Lazily, initialValue = NotificationContainerBounds(), ) + .dumpValue("bounds") /** * Ensure view is visible when the shade/qs are expanded. Also, as QS is expanding, fade out @@ -345,6 +358,7 @@ constructor( } } .onStart { emit(0f) } + .dumpWhileCollecting("alphaForShadeAndQsExpansion") private val alphaWhenGoneAndShadeState: Flow<Float> = combineTransform( @@ -357,6 +371,7 @@ constructor( emit(1f) } } + .dumpWhileCollecting("alphaWhenGoneAndShadeState") fun expansionAlpha(viewState: ViewStateAccessor): Flow<Float> { // All transition view models are mututally exclusive, and safe to merge @@ -389,7 +404,9 @@ constructor( isOnLockscreenWithoutShade, shadeCollapseFadeIn, alphaForShadeAndQsExpansion, - keyguardInteractor.dismissAlpha, + keyguardInteractor.dismissAlpha.dumpWhileCollecting( + "keyguardInteractor.keyguardAlpha" + ), ) { isOnLockscreenWithoutShade, shadeCollapseFadeIn, @@ -405,6 +422,7 @@ constructor( }, ) .distinctUntilChanged() + .dumpWhileCollecting("expansionAlpha") } /** @@ -438,6 +456,7 @@ constructor( } } } + .dumpWhileCollecting("glanceableHubAlpha") /** * Under certain scenarios, such as swiping up on the lockscreen, the container will need to be @@ -458,6 +477,7 @@ constructor( 0f } } + .dumpWhileCollecting("translationY") } /** @@ -469,6 +489,7 @@ constructor( lockscreenToGlanceableHubTransitionViewModel.notificationTranslationX, glanceableHubToLockscreenTransitionViewModel.notificationTranslationX, ) + .dumpWhileCollecting("translationX") /** * When on keyguard, there is limited space to display notifications so calculate how many could @@ -510,6 +531,7 @@ constructor( } } .distinctUntilChanged() + .dumpWhileCollecting("maxNotifications") } fun notificationStackChanged() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt index 270b94b1893e..23a080b7b931 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt @@ -45,8 +45,8 @@ import com.android.systemui.plugins.ActivityStarter.OnDismissAction import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.shade.ShadeController -import com.android.systemui.shade.ShadeViewController import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor +import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.SysuiStatusBarStateController @@ -71,7 +71,7 @@ constructor( private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>, private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>, private val shadeControllerLazy: Lazy<ShadeController>, - private val shadeViewControllerLazy: Lazy<ShadeViewController>, + private val commandQueue: CommandQueue, private val shadeAnimationInteractor: ShadeAnimationInteractor, private val statusBarKeyguardViewManagerLazy: Lazy<StatusBarKeyguardViewManager>, private val notifShadeWindowControllerLazy: Lazy<NotificationShadeWindowController>, @@ -853,10 +853,11 @@ constructor( if (dismissShade) { return StatusBarTransitionAnimatorController( animationController, - shadeViewControllerLazy.get(), shadeAnimationInteractor, shadeControllerLazy.get(), notifShadeWindowControllerLazy.get(), + commandQueue, + displayId, isLaunchForActivity ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java index 3669ba851005..48d3157b1968 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java @@ -309,14 +309,13 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba if (mVibrateOnOpening) { vibrateOnNavigationKeyDown(); } - mShadeViewController.expand(true /* animate */); + mShadeController.animateExpandShade(); mNotificationStackScrollLayoutController.setWillExpand(true); mHeadsUpManager.unpinAll(true /* userUnpinned */); mMetricsLogger.count("panel_open", 1); } else if (!mQsController.getExpanded() && !mShadeViewController.isExpandingOrCollapsing()) { - mQsController.flingQs(0 /* velocity */, - ShadeViewController.FLING_EXPAND); + mShadeController.animateExpandQs(); mMetricsLogger.count("panel_open_qs", 1); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt index 68a0e9cc1bf8..f29ec8f38c27 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt @@ -91,7 +91,7 @@ private constructor( if (event.source == InputDevice.SOURCE_MOUSE) { if (event.action == MotionEvent.ACTION_UP) { v.performClick() - shadeViewController.expand(/* animate= */ true) + shadeController.animateExpandShade() } return true } 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 d1055c77ab8f..ee844345d2ec 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -81,6 +81,9 @@ import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.navigationbar.TaskbarDelegate; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.scene.domain.interactor.SceneInteractor; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; +import com.android.systemui.scene.shared.model.Scenes; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeExpansionChangeEvent; import com.android.systemui.shade.ShadeExpansionListener; @@ -157,6 +160,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private final AlternateBouncerInteractor mAlternateBouncerInteractor; private final BouncerView mPrimaryBouncerView; private final Lazy<ShadeController> mShadeController; + private final Lazy<SceneInteractor> mSceneInteractorLazy; // Local cache of expansion events, to avoid duplicates private float mFraction = -1f; @@ -381,7 +385,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb Lazy<KeyguardDismissActionInteractor> keyguardDismissActionInteractorLazy, SelectedUserInteractor selectedUserInteractor, Lazy<KeyguardSurfaceBehindInteractor> surfaceBehindInteractor, - JavaAdapter javaAdapter + JavaAdapter javaAdapter, + Lazy<SceneInteractor> sceneInteractorLazy ) { mContext = context; mViewMediatorCallback = callback; @@ -415,6 +420,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mSelectedUserInteractor = selectedUserInteractor; mSurfaceBehindInteractor = surfaceBehindInteractor; mJavaAdapter = javaAdapter; + mSceneInteractorLazy = sceneInteractorLazy; } KeyguardTransitionInteractor mKeyguardTransitionInteractor; @@ -633,8 +639,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb public void show(Bundle options) { Trace.beginSection("StatusBarKeyguardViewManager#show"); mNotificationShadeWindowController.setKeyguardShowing(true); - mKeyguardStateController.notifyKeyguardState(true, - mKeyguardStateController.isOccluded()); + if (SceneContainerFlag.isEnabled()) { + mSceneInteractorLazy.get().changeScene( + Scenes.Lockscreen, "StatusBarKeyguardViewManager.show"); + } + mKeyguardStateController.notifyKeyguardState(true, mKeyguardStateController.isOccluded()); reset(true /* hideBouncerWhenShowing */); SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED, SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index 5610ed926f70..b5ab4e3eb462 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -63,6 +63,7 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeViewController; import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor; +import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationClickNotifier; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationPresenter; @@ -105,6 +106,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit private final NotificationVisibilityProvider mVisibilityProvider; private final HeadsUpManager mHeadsUpManager; private final ActivityStarter mActivityStarter; + private final CommandQueue mCommandQueue; private final NotificationClickNotifier mClickNotifier; private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private final KeyguardManager mKeyguardManager; @@ -143,6 +145,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit NotificationVisibilityProvider visibilityProvider, HeadsUpManager headsUpManager, ActivityStarter activityStarter, + CommandQueue commandQueue, NotificationClickNotifier clickNotifier, StatusBarKeyguardViewManager statusBarKeyguardViewManager, KeyguardManager keyguardManager, @@ -175,6 +178,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit mVisibilityProvider = visibilityProvider; mHeadsUpManager = headsUpManager; mActivityStarter = activityStarter; + mCommandQueue = commandQueue; mClickNotifier = clickNotifier; mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mKeyguardManager = keyguardManager; @@ -444,10 +448,11 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit ActivityTransitionAnimator.Controller animationController = new StatusBarTransitionAnimatorController( mNotificationAnimationProvider.getAnimatorController(row, null), - mShadeViewController, mShadeAnimationInteractor, mShadeController, mNotificationShadeWindowController, + mCommandQueue, + mDisplayId, isActivityIntent); mActivityTransitionAnimator.startPendingIntentWithAnimation( animationController, @@ -486,10 +491,11 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit ActivityTransitionAnimator.Controller animationController = new StatusBarTransitionAnimatorController( mNotificationAnimationProvider.getAnimatorController(row), - mShadeViewController, mShadeAnimationInteractor, mShadeController, mNotificationShadeWindowController, + mCommandQueue, + mDisplayId, true /* isActivityIntent */); mActivityTransitionAnimator.startIntentWithAnimation( @@ -537,10 +543,11 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit viewController == null ? null : new StatusBarTransitionAnimatorController( viewController, - mShadeViewController, mShadeAnimationInteractor, mShadeController, mNotificationShadeWindowController, + mCommandQueue, + mDisplayId, true /* isActivityIntent */); mActivityTransitionAnimator.startIntentWithAnimation( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTransitionAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTransitionAnimatorController.kt index 7e907d80d277..705a11df83fc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTransitionAnimatorController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTransitionAnimatorController.kt @@ -3,10 +3,13 @@ package com.android.systemui.statusbar.phone import android.view.View import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.animation.TransitionAnimator +import com.android.systemui.animation.TransitionAnimator.Companion.getProgress +import com.android.systemui.dagger.qualifiers.DisplayId import com.android.systemui.shade.ShadeController -import com.android.systemui.shade.ShadeViewController import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor +import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.NotificationShadeWindowController +import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment /** * A [ActivityTransitionAnimator.Controller] that takes care of collapsing the status bar at the @@ -14,12 +17,15 @@ import com.android.systemui.statusbar.NotificationShadeWindowController */ class StatusBarTransitionAnimatorController( private val delegate: ActivityTransitionAnimator.Controller, - private val shadeViewController: ShadeViewController, private val shadeAnimationInteractor: ShadeAnimationInteractor, private val shadeController: ShadeController, private val notificationShadeWindowController: NotificationShadeWindowController, + private val commandQueue: CommandQueue, + @DisplayId private val displayId: Int, private val isLaunchForActivity: Boolean = true ) : ActivityTransitionAnimator.Controller by delegate { + private var hideIconsDuringLaunchAnimation: Boolean = true + // Always sync the opening window with the shade, given that we draw a hole punch in the shade // of the same size and position as the opening app to make it visible. override val openingWindowSyncView: View? @@ -38,7 +44,7 @@ class StatusBarTransitionAnimatorController( delegate.onTransitionAnimationStart(isExpandingFullyAbove) shadeAnimationInteractor.setIsLaunchingActivity(true) if (!isExpandingFullyAbove) { - shadeViewController.collapseWithDuration( + shadeController.collapseWithDuration( ActivityTransitionAnimator.TIMINGS.totalDuration.toInt() ) } @@ -56,7 +62,19 @@ class StatusBarTransitionAnimatorController( linearProgress: Float ) { delegate.onTransitionAnimationProgress(state, progress, linearProgress) - shadeViewController.applyLaunchAnimationProgress(linearProgress) + val hideIcons = + getProgress( + ActivityTransitionAnimator.TIMINGS, + linearProgress, + ANIMATION_DELAY_ICON_FADE_IN, + 100 + ) == 0.0f + if (hideIcons != hideIconsDuringLaunchAnimation) { + hideIconsDuringLaunchAnimation = hideIcons + if (!hideIcons) { + commandQueue.recomputeDisableFlags(displayId, true /* animate */) + } + } } override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) { @@ -64,4 +82,12 @@ class StatusBarTransitionAnimatorController( shadeAnimationInteractor.setIsLaunchingActivity(false) shadeController.onLaunchAnimationCancelled(isLaunchForActivity) } + + companion object { + val ANIMATION_DELAY_ICON_FADE_IN = + (ActivityTransitionAnimator.TIMINGS.totalDuration - + CollapsedStatusBarFragment.FADE_IN_DURATION - + CollapsedStatusBarFragment.FADE_IN_DELAY - + 48) + } } 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 f12a09b1062c..82d9fc7d0152 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java @@ -93,7 +93,7 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh /** * @deprecated Don't subclass SystemUIDialog. Please subclass {@link Delegate} and pass it to - * {@link Factory#create(DialogDelegate)} to create a custom dialog. + * {@link Factory#create(Delegate)} to create a custom dialog. */ @Deprecated public SystemUIDialog(Context context) { diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt index d19a3364d502..5c53ff98b777 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt @@ -16,8 +16,6 @@ package com.android.systemui.unfold -import android.animation.Animator -import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator import android.annotation.BinderThread import android.content.Context @@ -25,6 +23,7 @@ import android.os.Handler import android.os.SystemProperties import android.util.Log import android.view.animation.DecelerateInterpolator +import androidx.core.animation.addListener import com.android.internal.foldables.FoldLockSettingAvailabilityProvider import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.display.data.repository.DeviceStateRepository @@ -37,25 +36,17 @@ import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Comp import com.android.systemui.unfold.dagger.UnfoldBg import com.android.systemui.util.animation.data.repository.AnimationStatusRepository import javax.inject.Inject -import kotlin.coroutines.resume import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.android.asCoroutineDispatcher -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.launch -import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withTimeout -@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) class FoldLightRevealOverlayAnimation @Inject constructor( @@ -70,9 +61,6 @@ constructor( private val revealProgressValueAnimator: ValueAnimator = ValueAnimator.ofFloat(ALPHA_OPAQUE, ALPHA_TRANSPARENT) - private val areAnimationEnabled: Flow<Boolean> - get() = animationStatusRepository.areAnimationsEnabled() - private lateinit var controller: FullscreenLightRevealAnimationController @Volatile private var readyCallback: CompletableDeferred<Runnable>? = null @@ -101,30 +89,33 @@ constructor( applicationScope.launch(bgHandler.asCoroutineDispatcher()) { deviceStateRepository.state - .map { it == DeviceStateRepository.DeviceState.FOLDED } + .map { it != DeviceStateRepository.DeviceState.FOLDED } .distinctUntilChanged() - .flatMapLatest { isFolded -> - flow<Nothing> { - if (!areAnimationEnabled.first() || !isFolded) { - return@flow - } - withTimeout(WAIT_FOR_ANIMATION_TIMEOUT_MS) { - readyCallback = CompletableDeferred() - val onReady = readyCallback?.await() - readyCallback = null - controller.addOverlay(ALPHA_OPAQUE, onReady) - waitForScreenTurnedOn() - } - playFoldLightRevealOverlayAnimation() - } - .catchTimeoutAndLog() - .onCompletion { - val onReady = readyCallback?.takeIf { it.isCompleted }?.getCompleted() - onReady?.run() + .filter { isUnfolded -> isUnfolded } + .collect { controller.ensureOverlayRemoved() } + } + + applicationScope.launch(bgHandler.asCoroutineDispatcher()) { + deviceStateRepository.state + .filter { + animationStatusRepository.areAnimationsEnabled().first() && + it == DeviceStateRepository.DeviceState.FOLDED + } + .collect { + try { + withTimeout(WAIT_FOR_ANIMATION_TIMEOUT_MS) { + readyCallback = CompletableDeferred() + val onReady = readyCallback?.await() readyCallback = null + controller.addOverlay(ALPHA_OPAQUE, onReady) + waitForScreenTurnedOn() + playFoldLightRevealOverlayAnimation() } + } catch (e: TimeoutCancellationException) { + Log.e(TAG, "Fold light reveal animation timed out") + ensureOverlayRemovedInternal() + } } - .collect {} } } @@ -137,34 +128,19 @@ constructor( powerInteractor.screenPowerState.filter { it == ScreenPowerState.SCREEN_ON }.first() } - private suspend fun playFoldLightRevealOverlayAnimation() { + private fun ensureOverlayRemovedInternal() { + revealProgressValueAnimator.cancel() + controller.ensureOverlayRemoved() + } + + private fun playFoldLightRevealOverlayAnimation() { revealProgressValueAnimator.duration = ANIMATION_DURATION revealProgressValueAnimator.interpolator = DecelerateInterpolator() revealProgressValueAnimator.addUpdateListener { animation -> controller.updateRevealAmount(animation.animatedFraction) } - revealProgressValueAnimator.startAndAwaitCompletion() - } - - private suspend fun ValueAnimator.startAndAwaitCompletion(): Unit = - suspendCancellableCoroutine { continuation -> - val listener = - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - continuation.resume(Unit) - removeListener(this) - } - } - addListener(listener) - continuation.invokeOnCancellation { removeListener(listener) } - start() - } - - private fun <T> Flow<T>.catchTimeoutAndLog() = catch { exception -> - when (exception) { - is TimeoutCancellationException -> Log.e(TAG, "Fold light reveal animation timed out") - else -> throw exception - } + revealProgressValueAnimator.addListener(onEnd = { controller.ensureOverlayRemoved() }) + revealProgressValueAnimator.start() } private companion object { diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt new file mode 100644 index 000000000000..0128eb762296 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt @@ -0,0 +1,37 @@ +/* + * 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.util.kotlin + +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.statusbar.policy.BatteryController +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.onStart + +fun BatteryController.isBatteryPowerSaveEnabled(): Flow<Boolean> { + return conflatedCallbackFlow { + val batteryCallback = + object : BatteryController.BatteryStateChangeCallback { + override fun onPowerSaveChanged(isPowerSave: Boolean) { + trySend(isPowerSave) + } + } + addCallback(batteryCallback) + awaitClose { removeCallback(batteryCallback) } + } + .onStart { emit(isPowerSave) } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/RotationLockControllerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/RotationLockControllerExt.kt new file mode 100644 index 000000000000..22cc8dd7745d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/RotationLockControllerExt.kt @@ -0,0 +1,35 @@ +/* + * 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.util.kotlin + +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.statusbar.policy.RotationLockController +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.onStart + +fun RotationLockController.isRotationLockEnabled(): Flow<Boolean> { + return conflatedCallbackFlow { + val rotationLockCallback = + RotationLockController.RotationLockControllerCallback { rotationLocked, _ -> + trySend(rotationLocked) + } + addCallback(rotationLockCallback) + awaitClose { removeCallback(rotationLockCallback) } + } + .onStart { emit(isRotationLocked) } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt index 170b32c1d0ea..2ab59984d06f 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt @@ -16,14 +16,11 @@ package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor -import android.content.Intent -import android.provider.Settings import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.animation.DialogCuj import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.animation.Expandable import com.android.systemui.media.dialog.MediaOutputDialogFactory -import com.android.systemui.plugins.ActivityStarter import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope import javax.inject.Inject @@ -34,17 +31,8 @@ class MediaOutputActionsInteractor @Inject constructor( private val mediaOutputDialogFactory: MediaOutputDialogFactory, - private val activityStarter: ActivityStarter, ) { - fun onDeviceClick(expandable: Expandable) { - activityStarter.startActivity( - Intent(Settings.ACTION_BLUETOOTH_SETTINGS), - true, - expandable.activityTransitionController(), - ) - } - fun onBarClick(session: MediaDeviceSession, expandable: Expandable) { when (session) { is MediaDeviceSession.Active -> { diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt index e518ed022792..37bf661454c5 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt @@ -26,13 +26,13 @@ sealed interface DeviceIconViewModel { val iconColor: Color val backgroundColor: Color - class IsPlaying( + data class IsPlaying( override val icon: Icon, override val iconColor: Color, override val backgroundColor: Color, ) : DeviceIconViewModel - class IsNotPlaying( + data class IsNotPlaying( override val icon: Icon, override val iconColor: Color, override val backgroundColor: Color, diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt index 85d6c9e341ac..37661b53c98a 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt @@ -113,11 +113,6 @@ constructor( private fun MediaDeviceSession.isPlaying(): Boolean = this is MediaDeviceSession.Active && playbackState?.isActive == true - fun onDeviceClick(expandable: Expandable) { - actionsInteractor.onDeviceClick(expandable) - volumePanelViewModel.dismissPanel() - } - fun onBarClick(expandable: Expandable) { actionsInteractor.onBarClick(mediaDeviceSession.value, expandable) volumePanelViewModel.dismissPanel() diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/flag/VolumePanelFlag.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/flag/VolumePanelFlag.kt index d90a9c75deec..485f4b5cbfd7 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/flag/VolumePanelFlag.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/flag/VolumePanelFlag.kt @@ -17,23 +17,18 @@ package com.android.systemui.volume.panel.shared.flag import com.android.systemui.Flags -import com.android.systemui.compose.ComposeFacade import com.android.systemui.flags.RefactorFlagUtils import javax.inject.Inject /** Provides a flag to check for the new Compose based Volume Panel availability. */ class VolumePanelFlag @Inject constructor() { - /** - * Returns true when the new Volume Panel is available and false the otherwise. The new panel - * can only be available when [ComposeFacade.isComposeAvailable] is true. - */ + /** Returns true when the new Volume Panel is available and false the otherwise. */ fun canUseNewVolumePanel(): Boolean { - return ComposeFacade.isComposeAvailable() && Flags.newVolumePanel() + return Flags.newVolumePanel() } fun assertNewVolumePanel() { - require(ComposeFacade.isComposeAvailable()) RefactorFlagUtils.assertInNewMode(Flags.newVolumePanel(), Flags.FLAG_NEW_VOLUME_PANEL) } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt index 53e1b8b5bb70..d430e65770fd 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt @@ -18,11 +18,12 @@ package com.android.systemui.volume.panel.ui.activity import android.os.Bundle import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.viewModels -import com.android.systemui.compose.ComposeFacade import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.volume.panel.shared.flag.VolumePanelFlag +import com.android.systemui.volume.panel.ui.composable.VolumePanelRoot import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel import javax.inject.Inject import javax.inject.Provider @@ -44,7 +45,7 @@ constructor( volumePanelFlag.assertNewVolumePanel() - ComposeFacade.setVolumePanelActivityContent(this, viewModel) { finish() } + setContent { VolumePanelRoot(viewModel = viewModel, onDismiss = ::finish) } } override fun onContentChanged() { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index 0f8a81399be8..3d0d8fb4abe4 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -21,8 +21,8 @@ import android.view.View import android.view.ViewTreeObserver import android.widget.FrameLayout import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase import com.android.systemui.Flags as AConfigFlags +import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository @@ -319,7 +319,10 @@ class ClockEventControllerTest : SysuiTestCase() { fun listenForDozeAmountTransition_updatesClockDozeAmount() = runBlocking(IMMEDIATE) { val transitionStep = MutableStateFlow(TransitionStep()) - whenever(keyguardTransitionInteractor.dozeAmountTransition).thenReturn(transitionStep) + whenever(keyguardTransitionInteractor.lockscreenToAodTransition) + .thenReturn(transitionStep) + whenever(keyguardTransitionInteractor.aodToLockscreenTransition) + .thenReturn(transitionStep) val job = underTest.listenForDozeAmountTransition(this) transitionStep.value = @@ -336,6 +339,48 @@ class ClockEventControllerTest : SysuiTestCase() { } @Test + fun listenForTransitionToAodFromGone_updatesClockDozeAmountToOne() = + runBlocking(IMMEDIATE) { + val transitionStep = MutableStateFlow(TransitionStep()) + whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.AOD)) + .thenReturn(transitionStep) + + val job = underTest.listenForAnyStateToAodTransition(this) + transitionStep.value = + TransitionStep( + from = KeyguardState.GONE, + to = KeyguardState.AOD, + transitionState = TransitionState.STARTED, + ) + yield() + + verify(animations, times(2)).doze(1f) + + job.cancel() + } + + @Test + fun listenForTransitionToAodFromLockscreen_neverUpdatesClockDozeAmount() = + runBlocking(IMMEDIATE) { + val transitionStep = MutableStateFlow(TransitionStep()) + whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.AOD)) + .thenReturn(transitionStep) + + val job = underTest.listenForAnyStateToAodTransition(this) + transitionStep.value = + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + transitionState = TransitionState.STARTED, + ) + yield() + + verify(animations, never()).doze(1f) + + job.cancel() + } + + @Test fun unregisterListeners_validate() = runBlocking(IMMEDIATE) { underTest.unregisterListeners() diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java index 90587d7386ce..13fb42ce8c3e 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java @@ -16,6 +16,8 @@ package com.android.keyguard; +import static kotlinx.coroutines.flow.FlowKt.emptyFlow; + import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -30,6 +32,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory; +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.power.data.repository.FakePowerRepository; import com.android.systemui.power.domain.interactor.PowerInteractorFactory; import com.android.systemui.res.R; @@ -59,6 +62,7 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { @Mock protected KeyguardStatusViewController mControllerMock; @Mock protected InteractionJankMonitor mInteractionJankMonitor; @Mock protected ViewTreeObserver mViewTreeObserver; + @Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor; @Mock protected DumpManager mDumpManager; protected FakeKeyguardRepository mFakeKeyguardRepository; protected FakePowerRepository mFakePowerRepository; @@ -89,6 +93,7 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { mKeyguardLogger, mInteractionJankMonitor, deps.getKeyguardInteractor(), + mKeyguardTransitionInteractor, mDumpManager, PowerInteractorFactory.create( mFakePowerRepository @@ -105,6 +110,7 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { when(mKeyguardStatusView.getViewTreeObserver()).thenReturn(mViewTreeObserver); when(mKeyguardClockSwitchController.getView()).thenReturn(mKeyguardClockSwitch); + when(mKeyguardTransitionInteractor.getGoneToAodTransition()).thenReturn(emptyFlow()); when(mKeyguardStatusView.findViewById(R.id.keyguard_status_area)) .thenReturn(mKeyguardStatusAreaView); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java index 24a5e8072796..2e040077c227 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java @@ -15,14 +15,11 @@ package com.android.systemui; import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; import android.os.Looper; import androidx.test.filters.SmallTest; -import com.android.systemui.statusbar.policy.FlashlightController; - import org.junit.Assert; import org.junit.Test; @@ -33,9 +30,9 @@ public class DependencyTest extends SysuiTestCase { @Test public void testClassDependency() { - FlashlightController f = mock(FlashlightController.class); - mDependency.injectTestDependency(FlashlightController.class, f); - Assert.assertEquals(f, Dependency.get(FlashlightController.class)); + FakeClass f = new FakeClass(); + mDependency.injectTestDependency(FakeClass.class, f); + Assert.assertEquals(f, Dependency.get(FakeClass.class)); } @Test @@ -53,4 +50,8 @@ public class DependencyTest extends SysuiTestCase { Dependency dependency = initializer.getSysUIComponent().createDependency(); dependency.start(); } + + private static class FakeClass { + + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java index 3da72618fb60..095c945ba77b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java @@ -22,10 +22,9 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGAT import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; -import android.app.ActivityManager; import android.content.Context; import android.content.ContextWrapper; import android.hardware.display.DisplayManager; @@ -75,13 +74,13 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { private AccessibilityManager mAccessibilityManager; private KeyguardUpdateMonitor mKeyguardUpdateMonitor; private AccessibilityFloatingMenuController mController; + @Mock private AccessibilityButtonTargetsObserver mTargetsObserver; + @Mock private AccessibilityButtonModeObserver mModeObserver; @Captor private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardCallbackCaptor; private KeyguardUpdateMonitorCallback mKeyguardCallback; - private int mLastButtonMode; - private String mLastButtonTargets; @Mock private SecureSettings mSecureSettings; @@ -97,10 +96,14 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { mWindowManager = mContext.getSystemService(WindowManager.class); mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class); - mLastButtonTargets = Settings.Secure.getStringForUser(mContextWrapper.getContentResolver(), - Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT); - mLastButtonMode = Settings.Secure.getIntForUser(mContextWrapper.getContentResolver(), - Settings.Secure.ACCESSIBILITY_BUTTON_MODE, UserHandle.USER_CURRENT); + + when(mTargetsObserver.getCurrentAccessibilityButtonTargets()) + .thenReturn(Settings.Secure.getStringForUser(mContextWrapper.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT)); + + when(mModeObserver.getCurrentAccessibilityButtonMode()) + .thenReturn(Settings.Secure.getIntForUser(mContextWrapper.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, UserHandle.USER_CURRENT)); } @After @@ -109,13 +112,6 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { mController.onAccessibilityButtonTargetsChanged(""); mController = null; } - - Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(), - Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, mLastButtonTargets, - UserHandle.USER_CURRENT); - Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(), - Settings.Secure.ACCESSIBILITY_BUTTON_MODE, mLastButtonMode, - UserHandle.USER_CURRENT); } @Test @@ -227,9 +223,8 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { @Test public void onAccessibilityButtonModeChanged_floatingModeAndHasButtonTargets_showWidget() { - Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(), - Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS, - ActivityManager.getCurrentUser()); + when(mTargetsObserver.getCurrentAccessibilityButtonTargets()) + .thenReturn(TEST_A11Y_BTN_TARGETS); mController = setUpController(); mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU); @@ -239,8 +234,8 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { @Test public void onAccessibilityButtonModeChanged_floatingModeAndNoButtonTargets_destroyWidget() { - Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(), - Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, "", ActivityManager.getCurrentUser()); + when(mTargetsObserver.getCurrentAccessibilityButtonTargets()).thenReturn(""); + mController = setUpController(); mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU); @@ -250,9 +245,8 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { @Test public void onAccessibilityButtonModeChanged_navBarModeAndHasButtonTargets_destroyWidget() { - Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(), - Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS, - ActivityManager.getCurrentUser()); + when(mTargetsObserver.getCurrentAccessibilityButtonTargets()) + .thenReturn(TEST_A11Y_BTN_TARGETS); mController = setUpController(); mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR); @@ -262,8 +256,7 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { @Test public void onAccessibilityButtonModeChanged_navBarModeAndNoButtonTargets_destroyWidget() { - Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(), - Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, "", ActivityManager.getCurrentUser()); + when(mTargetsObserver.getCurrentAccessibilityButtonTargets()).thenReturn(""); mController = setUpController(); mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR); @@ -273,9 +266,8 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { @Test public void onAccessibilityButtonTargetsChanged_floatingModeAndHasButtonTargets_showWidget() { - Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(), - Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU, - ActivityManager.getCurrentUser()); + when(mModeObserver.getCurrentAccessibilityButtonMode()) + .thenReturn(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU); mController = setUpController(); mController.onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS); @@ -285,9 +277,8 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { @Test public void onAccessibilityButtonTargetsChanged_floatingModeAndNoButtonTargets_destroyWidget() { - Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(), - Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU, - ActivityManager.getCurrentUser()); + when(mModeObserver.getCurrentAccessibilityButtonMode()) + .thenReturn(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU); mController = setUpController(); mController.onAccessibilityButtonTargetsChanged(""); @@ -297,9 +288,8 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { @Test public void onAccessibilityButtonTargetsChanged_navBarModeAndHasButtonTargets_destroyWidget() { - Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(), - Settings.Secure.ACCESSIBILITY_BUTTON_MODE, - ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, ActivityManager.getCurrentUser()); + when(mModeObserver.getCurrentAccessibilityButtonMode()) + .thenReturn(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR); mController = setUpController(); mController.onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS); @@ -309,9 +299,8 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { @Test public void onAccessibilityButtonTargetsChanged_navBarModeAndNoButtonTargets_destroyWidget() { - Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(), - Settings.Secure.ACCESSIBILITY_BUTTON_MODE, - ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, ActivityManager.getCurrentUser()); + when(mModeObserver.getCurrentAccessibilityButtonMode()) + .thenReturn(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR); mController = setUpController(); mController.onAccessibilityButtonTargetsChanged(""); @@ -321,9 +310,8 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { @Test public void onTargetsChanged_isFloatingViewLayerControllerCreated() { - Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(), - Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU, - UserHandle.USER_CURRENT); + when(mModeObserver.getCurrentAccessibilityButtonMode()) + .thenReturn(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU); mController = setUpController(); mController.onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS); @@ -335,8 +323,6 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { final WindowManager windowManager = mContext.getSystemService(WindowManager.class); final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); final FakeDisplayTracker displayTracker = new FakeDisplayTracker(mContext); - mTargetsObserver = spy(Dependency.get(AccessibilityButtonTargetsObserver.class)); - mModeObserver = spy(Dependency.get(AccessibilityButtonModeObserver.class)); mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class); final AccessibilityFloatingMenuController controller = new AccessibilityFloatingMenuController(mContextWrapper, windowManager, @@ -348,12 +334,11 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { } private void enableAccessibilityFloatingMenuConfig() { - Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(), - Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU, - ActivityManager.getCurrentUser()); - Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(), - Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS, - ActivityManager.getCurrentUser()); + when(mTargetsObserver.getCurrentAccessibilityButtonTargets()) + .thenReturn(TEST_A11Y_BTN_TARGETS); + + when(mModeObserver.getCurrentAccessibilityButtonMode()) + .thenReturn(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU); } private void captureKeyguardUpdateMonitorCallback() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/compose/ComposeInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/compose/ComposeInitializerTest.kt index 3e6cc3bb4f6b..03e4f9ae1685 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/compose/ComposeInitializerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/compose/ComposeInitializerTest.kt @@ -32,10 +32,6 @@ import org.junit.runner.RunWith class ComposeInitializerTest : SysuiTestCase() { @Test fun testCanAddComposeViewInInitializedWindow() { - if (!ComposeFacade.isComposeAvailable()) { - return - } - val root = TestWindowRoot(context) try { runOnMainThreadAndWaitForIdleSync { ViewUtils.attachView(root) } @@ -55,12 +51,12 @@ class ComposeInitializerTest : SysuiTestCase() { class TestWindowRoot(context: Context) : FrameLayout(context) { override fun onAttachedToWindow() { super.onAttachedToWindow() - ComposeFacade.composeInitializer().onAttachedToWindow(this) + ComposeInitializer.onAttachedToWindow(this) } override fun onDetachedFromWindow() { super.onDetachedFromWindow() - ComposeFacade.composeInitializer().onDetachedFromWindow(this) + ComposeInitializer.onDetachedFromWindow(this) } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt index a992956f5121..59d8fc3d3fd0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt @@ -19,7 +19,6 @@ package com.android.systemui.keyboard.stickykeys.ui import android.app.Dialog import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.compose.ComposeFacade import com.android.systemui.keyboard.data.repository.FakeStickyKeysRepository import com.android.systemui.keyboard.data.repository.keyboardRepository import com.android.systemui.keyboard.stickykeys.StickyKeysLogger @@ -34,7 +33,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent -import org.junit.Assume import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -54,19 +52,22 @@ class StickyKeysIndicatorCoordinatorTest : SysuiTestCase() { @Before fun setup() { - Assume.assumeTrue(ComposeFacade.isComposeAvailable()) val dialogFactory = mock<StickyKeyDialogFactory>() whenever(dialogFactory.create(any())).thenReturn(dialog) val keyboardRepository = Kosmos().keyboardRepository - val viewModel = StickyKeysIndicatorViewModel( + val viewModel = + StickyKeysIndicatorViewModel( stickyKeysRepository, keyboardRepository, - testScope.backgroundScope) - coordinator = StickyKeysIndicatorCoordinator( + testScope.backgroundScope + ) + coordinator = + StickyKeysIndicatorCoordinator( testScope.backgroundScope, dialogFactory, viewModel, - mock<StickyKeysLogger>()) + mock<StickyKeysLogger>() + ) coordinator.startListening() keyboardRepository.setIsAnyKeyboardConnected(true) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt index d0b1dd52c7e7..df52265384fa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt @@ -122,13 +122,7 @@ class BurnInInteractorTest : SysuiTestCase() { testScope.runTest { whenever(burnInHelperWrapper.burnInScale()).thenReturn(0.5f) - val burnInModel by - collectLastValue( - underTest.burnIn( - xDimenResourceId = R.dimen.burn_in_prevention_offset_x, - yDimenResourceId = R.dimen.burn_in_prevention_offset_y - ) - ) + val burnInModel by collectLastValue(underTest.keyguardBurnIn) // After time tick, returns the configured values fakeKeyguardRepository.dozeTimeTick(10) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index 69cd173f4253..92aad692ac52 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.domain.interactor import android.app.StatusBarManager import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN import com.android.systemui.Flags.FLAG_COMMUNAL_HUB @@ -25,8 +26,7 @@ import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository import com.android.systemui.communal.domain.interactor.communalInteractor -import com.android.systemui.communal.shared.model.CommunalSceneKey -import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState +import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.fakeCommandQueue @@ -169,6 +169,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { keyguardInteractor = keyguardInteractor, transitionRepository = transitionRepository, transitionInteractor = transitionInteractor, + powerInteractor = powerInteractor, ) .apply { start() } @@ -574,8 +575,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { runCurrent() // WHEN the device begins to wake + keyguardRepository.setKeyguardShowing(true) powerInteractor.setAwakeForTest() - runCurrent() + advanceTimeBy(60L) assertThat(transitionRepository) .startedTransition( @@ -630,15 +632,66 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test - fun dozingToGone() = + fun dozingToGoneWithUnlock() = testScope.runTest { // GIVEN a prior transition has run to DOZING runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DOZING) + runCurrent() // WHEN biometrics succeeds with wake and unlock mode + powerInteractor.setAwakeForTest() keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK) + advanceTimeBy(60L) + + assertThat(transitionRepository) + .startedTransition( + to = KeyguardState.GONE, + from = KeyguardState.DOZING, + ownerName = "FromDozingTransitionInteractor", + animatorAssertion = { it.isNotNull() } + ) + + coroutineContext.cancelChildren() + } + + @Test + fun dozingToPrimaryBouncer() = + testScope.runTest { + // GIVEN a prior transition has run to DOZING + runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DOZING) runCurrent() + // WHEN awaked by a request to show the primary bouncer, as can happen if SPFS is + // touched after boot + powerInteractor.setAwakeForTest() + bouncerRepository.setPrimaryShow(true) + advanceTimeBy(60L) + + assertThat(transitionRepository) + .startedTransition( + to = KeyguardState.PRIMARY_BOUNCER, + from = KeyguardState.DOZING, + ownerName = "FromDozingTransitionInteractor", + animatorAssertion = { it.isNotNull() } + ) + + coroutineContext.cancelChildren() + } + + /** This handles security method NONE and screen off with lock timeout */ + @Test + fun dozingToGoneWithKeyguardNotShowing() = + testScope.runTest { + // GIVEN a prior transition has run to DOZING + runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DOZING) + runCurrent() + + // WHEN the device wakes up without a keyguard + keyguardRepository.setKeyguardShowing(false) + keyguardRepository.setKeyguardDismissible(true) + powerInteractor.setAwakeForTest() + advanceTimeBy(60L) + assertThat(transitionRepository) .startedTransition( to = KeyguardState.GONE, @@ -650,6 +703,32 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { coroutineContext.cancelChildren() } + /** This handles security method NONE and screen off with lock timeout */ + @Test + fun dreamingToGoneWithKeyguardNotShowing() = + testScope.runTest { + // GIVEN a prior transition has run to DREAMING + keyguardRepository.setDreamingWithOverlay(true) + runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING) + runCurrent() + + // WHEN the device wakes up without a keyguard + keyguardRepository.setKeyguardShowing(false) + keyguardRepository.setKeyguardDismissible(true) + keyguardRepository.setDreamingWithOverlay(false) + advanceTimeBy(60L) + + assertThat(transitionRepository) + .startedTransition( + to = KeyguardState.GONE, + from = KeyguardState.DREAMING, + ownerName = "FromDreamingTransitionInteractor", + animatorAssertion = { it.isNotNull() } + ) + + coroutineContext.cancelChildren() + } + @Test fun dozingToGlanceableHub() = testScope.runTest { @@ -659,15 +738,16 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN the device is idle on the glanceable hub val idleTransitionState = - MutableStateFlow<ObservableCommunalTransitionState>( - ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal) + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(CommunalScenes.Communal) ) communalInteractor.setTransitionState(idleTransitionState) runCurrent() // WHEN the device begins to wake + keyguardRepository.setKeyguardShowing(true) powerInteractor.setAwakeForTest() - runCurrent() + advanceTimeBy(60L) assertThat(transitionRepository) .startedTransition( @@ -816,8 +896,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN the device is idle on the glanceable hub val idleTransitionState = - MutableStateFlow<ObservableCommunalTransitionState>( - ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal) + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(CommunalScenes.Communal) ) communalInteractor.setTransitionState(idleTransitionState) runCurrent() @@ -965,8 +1045,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN the device is idle on the glanceable hub val idleTransitionState = - MutableStateFlow<ObservableCommunalTransitionState>( - ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal) + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(CommunalScenes.Communal) ) communalInteractor.setTransitionState(idleTransitionState) runCurrent() @@ -1073,8 +1153,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN the device is idle on the glanceable hub val idleTransitionState = - MutableStateFlow<ObservableCommunalTransitionState>( - ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal) + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(CommunalScenes.Communal) ) communalInteractor.setTransitionState(idleTransitionState) runCurrent() @@ -1108,8 +1188,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN the device is idle on the glanceable hub val idleTransitionState = - MutableStateFlow<ObservableCommunalTransitionState>( - ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal) + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(CommunalScenes.Communal) ) communalInteractor.setTransitionState(idleTransitionState) runCurrent() @@ -1226,8 +1306,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN the device is idle on the glanceable hub val idleTransitionState = - MutableStateFlow<ObservableCommunalTransitionState>( - ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal) + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(CommunalScenes.Communal) ) communalInteractor.setTransitionState(idleTransitionState) runCurrent() @@ -1335,8 +1415,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // WHEN the keyguard is occluded and device wakes up keyguardRepository.setKeyguardOccluded(true) + keyguardRepository.setKeyguardShowing(true) powerInteractor.setAwakeForTest() - runCurrent() + advanceTimeBy(60L) // THEN a transition to OCCLUDED should occur assertThat(transitionRepository) @@ -1414,13 +1495,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { runCurrent() // WHEN a transition to the glanceable hub starts - val currentScene = CommunalSceneKey.Blank - val targetScene = CommunalSceneKey.Communal + val currentScene = CommunalScenes.Blank + val targetScene = CommunalScenes.Communal val progress = MutableStateFlow(0f) val transitionState = - MutableStateFlow<ObservableCommunalTransitionState>( - ObservableCommunalTransitionState.Transition( + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( fromScene = currentScene, toScene = targetScene, progress = progress, @@ -1593,13 +1674,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { runCurrent() // WHEN a glanceable hub transition starts - val currentScene = CommunalSceneKey.Blank - val targetScene = CommunalSceneKey.Communal + val currentScene = CommunalScenes.Blank + val targetScene = CommunalScenes.Communal val progress = MutableStateFlow(0f) val transitionState = - MutableStateFlow<ObservableCommunalTransitionState>( - ObservableCommunalTransitionState.Transition( + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( fromScene = currentScene, toScene = targetScene, progress = progress, @@ -1624,8 +1705,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { clearInvocations(transitionRepository) runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB) val idleTransitionState = - MutableStateFlow<ObservableCommunalTransitionState>( - ObservableCommunalTransitionState.Idle(currentScene) + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(currentScene) ) communalInteractor.setTransitionState(idleTransitionState) runCurrent() @@ -1649,13 +1730,13 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { runCurrent() // WHEN a transition away from glanceable hub starts - val currentScene = CommunalSceneKey.Communal - val targetScene = CommunalSceneKey.Blank + val currentScene = CommunalScenes.Communal + val targetScene = CommunalScenes.Blank val progress = MutableStateFlow(0f) val transitionState = - MutableStateFlow<ObservableCommunalTransitionState>( - ObservableCommunalTransitionState.Transition( + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( fromScene = currentScene, toScene = targetScene, progress = progress, @@ -1679,8 +1760,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { clearInvocations(transitionRepository) runTransitionAndSetWakefulness(KeyguardState.GLANCEABLE_HUB, KeyguardState.LOCKSCREEN) val idleTransitionState = - MutableStateFlow<ObservableCommunalTransitionState>( - ObservableCommunalTransitionState.Idle(currentScene) + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(currentScene) ) communalInteractor.setTransitionState(idleTransitionState) runCurrent() @@ -1766,8 +1847,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN the device is idle on the glanceable hub val idleTransitionState = - MutableStateFlow<ObservableCommunalTransitionState>( - ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal) + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(CommunalScenes.Communal) ) communalInteractor.setTransitionState(idleTransitionState) runCurrent() @@ -1823,12 +1904,12 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { runCurrent() // WHEN a transition away from glanceable hub starts - val currentScene = CommunalSceneKey.Communal - val targetScene = CommunalSceneKey.Blank + val currentScene = CommunalScenes.Communal + val targetScene = CommunalScenes.Blank val transitionState = - MutableStateFlow<ObservableCommunalTransitionState>( - ObservableCommunalTransitionState.Transition( + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( fromScene = currentScene, toScene = targetScene, progress = flowOf(0f, 0.1f), diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt index 02bd810a43d5..4bb0d4781376 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt @@ -27,11 +27,14 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.keyguardRepository +import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel.Companion.UNLOCKED_DELAY_MS import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.test.Test import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.runner.RunWith @@ -71,7 +74,10 @@ class DeviceEntryIconViewModelTest : SysuiTestCase() { fun isLongPressEnabled_unlocked() = testScope.runTest { val isLongPressEnabled by collectLastValue(underTest.isLongPressEnabled) + fingerprintPropertyRepository.supportsUdfps() keyguardRepository.setKeyguardDismissible(true) + advanceTimeBy(UNLOCKED_DELAY_MS * 2) // wait for unlocked delay + runCurrent() assertThat(isLongPressEnabled).isTrue() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt index ce089b1b723a..864acfb1ddb9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt @@ -16,7 +16,6 @@ package com.android.systemui.keyguard.ui.viewmodel -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags as AConfigFlags import com.android.systemui.SysuiTestCase @@ -25,13 +24,9 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.doze.util.BurnInHelperWrapper import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.domain.interactor.BurnInInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory -import com.android.systemui.keyguard.shared.model.BurnInModel import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition -import com.android.systemui.kosmos.testScope -import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -41,23 +36,18 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.junit.runners.JUnit4 import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidJUnit4::class) +@RunWith(JUnit4::class) class KeyguardIndicationAreaViewModelTest : SysuiTestCase() { - private val kosmos = testKosmos() - private val testScope = kosmos.testScope @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper @Mock private lateinit var shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel - @Mock private lateinit var burnInInteractor: BurnInInteractor - private val burnInFlow = MutableStateFlow(BurnInModel()) - - private lateinit var bottomAreaInteractor: KeyguardBottomAreaInteractor private lateinit var underTest: KeyguardIndicationAreaViewModel private lateinit var repository: FakeKeyguardRepository @@ -80,11 +70,9 @@ class KeyguardIndicationAreaViewModelTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR) - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) whenever(burnInHelperWrapper.burnInOffset(anyInt(), any())) .thenReturn(RETURNED_BURN_IN_OFFSET) - whenever(burnInInteractor.burnIn(anyInt(), anyInt())).thenReturn(burnInFlow) val withDeps = KeyguardInteractorFactory.create() val keyguardInteractor = withDeps.keyguardInteractor @@ -94,85 +82,78 @@ class KeyguardIndicationAreaViewModelTest : SysuiTestCase() { whenever(bottomAreaViewModel.startButton).thenReturn(startButtonFlow) whenever(bottomAreaViewModel.endButton).thenReturn(endButtonFlow) whenever(bottomAreaViewModel.alpha).thenReturn(alphaFlow) - bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository) underTest = KeyguardIndicationAreaViewModel( keyguardInteractor = keyguardInteractor, - bottomAreaInteractor = bottomAreaInteractor, + bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository), keyguardBottomAreaViewModel = bottomAreaViewModel, burnInHelperWrapper = burnInHelperWrapper, - burnInInteractor = burnInInteractor, shortcutsCombinedViewModel = shortcutsCombinedViewModel, configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()), ) } @Test - fun alpha() = - testScope.runTest { - val value = collectLastValue(underTest.alpha) - - assertThat(value()).isEqualTo(1f) - alphaFlow.value = 0.1f - assertThat(value()).isEqualTo(0.1f) - alphaFlow.value = 0.5f - assertThat(value()).isEqualTo(0.5f) - alphaFlow.value = 0.2f - assertThat(value()).isEqualTo(0.2f) - alphaFlow.value = 0f - assertThat(value()).isEqualTo(0f) - } + fun alpha() = runTest { + val value = collectLastValue(underTest.alpha) + + assertThat(value()).isEqualTo(1f) + alphaFlow.value = 0.1f + assertThat(value()).isEqualTo(0.1f) + alphaFlow.value = 0.5f + assertThat(value()).isEqualTo(0.5f) + alphaFlow.value = 0.2f + assertThat(value()).isEqualTo(0.2f) + alphaFlow.value = 0f + assertThat(value()).isEqualTo(0f) + } @Test - fun isIndicationAreaPadded() = - testScope.runTest { - repository.setKeyguardShowing(true) - val value = collectLastValue(underTest.isIndicationAreaPadded) - - assertThat(value()).isFalse() - startButtonFlow.value = startButtonFlow.value.copy(isVisible = true) - assertThat(value()).isTrue() - endButtonFlow.value = endButtonFlow.value.copy(isVisible = true) - assertThat(value()).isTrue() - startButtonFlow.value = startButtonFlow.value.copy(isVisible = false) - assertThat(value()).isTrue() - endButtonFlow.value = endButtonFlow.value.copy(isVisible = false) - assertThat(value()).isFalse() - } + fun isIndicationAreaPadded() = runTest { + repository.setKeyguardShowing(true) + val value = collectLastValue(underTest.isIndicationAreaPadded) + + assertThat(value()).isFalse() + startButtonFlow.value = startButtonFlow.value.copy(isVisible = true) + assertThat(value()).isTrue() + endButtonFlow.value = endButtonFlow.value.copy(isVisible = true) + assertThat(value()).isTrue() + startButtonFlow.value = startButtonFlow.value.copy(isVisible = false) + assertThat(value()).isTrue() + endButtonFlow.value = endButtonFlow.value.copy(isVisible = false) + assertThat(value()).isFalse() + } @Test - fun indicationAreaTranslationX() = - testScope.runTest { - val value = collectLastValue(underTest.indicationAreaTranslationX) - - assertThat(value()).isEqualTo(0f) - bottomAreaInteractor.setClockPosition(100, 100) - assertThat(value()).isEqualTo(100f) - bottomAreaInteractor.setClockPosition(200, 100) - assertThat(value()).isEqualTo(200f) - bottomAreaInteractor.setClockPosition(200, 200) - assertThat(value()).isEqualTo(200f) - bottomAreaInteractor.setClockPosition(300, 100) - assertThat(value()).isEqualTo(300f) - } + fun indicationAreaTranslationX() = runTest { + val value = collectLastValue(underTest.indicationAreaTranslationX) + + assertThat(value()).isEqualTo(0f) + repository.setClockPosition(100, 100) + assertThat(value()).isEqualTo(100f) + repository.setClockPosition(200, 100) + assertThat(value()).isEqualTo(200f) + repository.setClockPosition(200, 200) + assertThat(value()).isEqualTo(200f) + repository.setClockPosition(300, 100) + assertThat(value()).isEqualTo(300f) + } @Test - fun indicationAreaTranslationY() = - testScope.runTest { - val value = - collectLastValue(underTest.indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET)) - - // Negative 0 - apparently there's a difference in floating point arithmetic - FML - assertThat(value()).isEqualTo(-0f) - val expected1 = setDozeAmountAndCalculateExpectedTranslationY(0.1f) - assertThat(value()).isEqualTo(expected1) - val expected2 = setDozeAmountAndCalculateExpectedTranslationY(0.2f) - assertThat(value()).isEqualTo(expected2) - val expected3 = setDozeAmountAndCalculateExpectedTranslationY(0.5f) - assertThat(value()).isEqualTo(expected3) - val expected4 = setDozeAmountAndCalculateExpectedTranslationY(1f) - assertThat(value()).isEqualTo(expected4) - } + fun indicationAreaTranslationY() = runTest { + val value = collectLastValue(underTest.indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET)) + + // Negative 0 - apparently there's a difference in floating point arithmetic - FML + assertThat(value()).isEqualTo(-0f) + val expected1 = setDozeAmountAndCalculateExpectedTranslationY(0.1f) + assertThat(value()).isEqualTo(expected1) + val expected2 = setDozeAmountAndCalculateExpectedTranslationY(0.2f) + assertThat(value()).isEqualTo(expected2) + val expected3 = setDozeAmountAndCalculateExpectedTranslationY(0.5f) + assertThat(value()).isEqualTo(expected3) + val expected4 = setDozeAmountAndCalculateExpectedTranslationY(1f) + assertThat(value()).isEqualTo(expected4) + } private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float { repository.setDozeAmount(dozeAmount) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt index 45f49f01a43e..29820f7a7249 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt @@ -28,7 +28,7 @@ import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.interactor.setCommunalAvailable -import com.android.systemui.communal.shared.model.CommunalSceneKey +import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq import com.android.systemui.dreams.DreamOverlayStateController import com.android.systemui.keyguard.WakefulnessLifecycle @@ -514,7 +514,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { kosmos.setCommunalAvailable(true) runCurrent() - communalInteractor.onSceneChanged(CommunalSceneKey.Communal) + communalInteractor.onSceneChanged(CommunalScenes.Communal) runCurrent() verify(mediaCarouselController) .onDesiredLocationChanged( @@ -526,7 +526,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { ) clearInvocations(mediaCarouselController) - communalInteractor.onSceneChanged(CommunalSceneKey.Blank) + communalInteractor.onSceneChanged(CommunalScenes.Blank) runCurrent() verify(mediaCarouselController) .onDesiredLocationChanged( @@ -549,7 +549,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) // UMO goes to communal even over the lock screen. - communalInteractor.onSceneChanged(CommunalSceneKey.Communal) + communalInteractor.onSceneChanged(CommunalScenes.Communal) runCurrent() verify(mediaCarouselController) .onDesiredLocationChanged( @@ -571,7 +571,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { // Device is on lock screen. whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) - communalInteractor.onSceneChanged(CommunalSceneKey.Communal) + communalInteractor.onSceneChanged(CommunalScenes.Communal) runCurrent() verify(mediaCarouselController) .onDesiredLocationChanged( diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt index 83932b0a6133..dbfab64b004a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt @@ -49,6 +49,19 @@ class ActivityTaskManagerTasksRepositoryTest : SysuiTestCase() { ) @Test + fun launchRecentTask_taskIsMovedToForeground() = + testScope.runTest { + val currentForegroundTask by collectLastValue(repo.foregroundTask) + val newForegroundTask = createTask(taskId = 1) + val backgroundTask = createTask(taskId = 2) + fakeActivityTaskManager.addRunningTasks(backgroundTask, newForegroundTask) + + repo.launchRecentTask(newForegroundTask) + + assertThat(currentForegroundTask).isEqualTo(newForegroundTask) + } + + @Test fun findRunningTaskFromWindowContainerToken_noMatch_returnsNull() { fakeActivityTaskManager.addRunningTasks(createTask(taskId = 1), createTask(taskId = 2)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt index 1c4870bc32b1..920e5ee94cca 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt @@ -17,7 +17,7 @@ package com.android.systemui.mediaprojection.taskswitcher.data.repository import android.app.ActivityManager.RunningTaskInfo -import android.app.ActivityTaskManager +import android.app.IActivityTaskManager import android.app.TaskStackListener import android.content.Intent import android.window.IWindowContainerToken @@ -31,7 +31,7 @@ class FakeActivityTaskManager { private val runningTasks = mutableListOf<RunningTaskInfo>() private val taskTaskListeners = mutableListOf<TaskStackListener>() - val activityTaskManager = mock<ActivityTaskManager>() + val activityTaskManager = mock<IActivityTaskManager>() init { whenever(activityTaskManager.registerTaskStackListener(any())).thenAnswer { @@ -42,10 +42,20 @@ class FakeActivityTaskManager { taskTaskListeners -= it.arguments[0] as TaskStackListener return@thenAnswer Unit } - whenever(activityTaskManager.getTasks(any())).thenAnswer { + whenever(activityTaskManager.getTasks(any(), any(), any(), any())).thenAnswer { val maxNumTasks = it.arguments[0] as Int return@thenAnswer runningTasks.take(maxNumTasks) } + whenever(activityTaskManager.startActivityFromRecents(any(), any())).thenAnswer { + val taskId = it.arguments[0] as Int + val runningTask = runningTasks.find { runningTask -> runningTask.taskId == taskId } + if (runningTask != null) { + moveTaskToForeground(runningTask) + return@thenAnswer 0 + } else { + return@thenAnswer -1 + } + } } fun moveTaskToForeground(task: RunningTaskInfo) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt index 44c411fdb1d1..28393e837b93 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt @@ -22,6 +22,8 @@ import android.os.Binder import android.os.IBinder import android.os.UserHandle import android.view.ContentRecordingSession +import android.window.WindowContainerToken +import com.android.systemui.mediaprojection.MediaProjectionServiceHelper import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -29,6 +31,7 @@ import com.android.systemui.util.mockito.whenever class FakeMediaProjectionManager { val mediaProjectionManager = mock<MediaProjectionManager>() + val helper = mock<MediaProjectionServiceHelper>() private val callbacks = mutableListOf<MediaProjectionManager.Callback>() @@ -41,6 +44,11 @@ class FakeMediaProjectionManager { callbacks -= it.arguments[0] as MediaProjectionManager.Callback return@thenAnswer Unit } + whenever(helper.updateTaskRecordingSession(any())).thenAnswer { + val token = it.arguments[0] as WindowContainerToken + dispatchOnSessionSet(session = createSingleTaskSession(token.asBinder())) + return@thenAnswer true + } } fun dispatchOnStart(info: MediaProjectionInfo = DEFAULT_INFO) { @@ -61,6 +69,7 @@ class FakeMediaProjectionManager { companion object { fun createDisplaySession(): ContentRecordingSession = ContentRecordingSession.createDisplaySession(/* displayToMirror = */ 123) + fun createSingleTaskSession(token: IBinder = Binder()): ContentRecordingSession = ContentRecordingSession.createTaskSession(token) diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt index 7bd97ce2670c..fdd434acdc9f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt @@ -28,9 +28,8 @@ import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeAct import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createToken import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @@ -40,7 +39,7 @@ import org.junit.runner.RunWith @SmallTest class MediaProjectionManagerRepositoryTest : SysuiTestCase() { - private val dispatcher = StandardTestDispatcher() + private val dispatcher = UnconfinedTestDispatcher() private val testScope = TestScope(dispatcher) private val fakeMediaProjectionManager = FakeMediaProjectionManager() @@ -58,14 +57,27 @@ class MediaProjectionManagerRepositoryTest : SysuiTestCase() { mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager, handler = Handler.getMain(), applicationScope = testScope.backgroundScope, - tasksRepository = tasksRepo + tasksRepository = tasksRepo, + backgroundDispatcher = dispatcher, + mediaProjectionServiceHelper = fakeMediaProjectionManager.helper ) @Test + fun switchProjectedTask_stateIsUpdatedWithNewTask() = + testScope.runTest { + val task = createTask(taskId = 1) + val state by collectLastValue(repo.mediaProjectionState) + + fakeActivityTaskManager.addRunningTasks(task) + repo.switchProjectedTask(task) + + assertThat(state).isEqualTo(MediaProjectionState.SingleTask(task)) + } + + @Test fun mediaProjectionState_onStart_emitsNotProjecting() = testScope.runTest { val state by collectLastValue(repo.mediaProjectionState) - runCurrent() fakeMediaProjectionManager.dispatchOnStart() @@ -76,7 +88,6 @@ class MediaProjectionManagerRepositoryTest : SysuiTestCase() { fun mediaProjectionState_onStop_emitsNotProjecting() = testScope.runTest { val state by collectLastValue(repo.mediaProjectionState) - runCurrent() fakeMediaProjectionManager.dispatchOnStop() @@ -87,7 +98,6 @@ class MediaProjectionManagerRepositoryTest : SysuiTestCase() { fun mediaProjectionState_onSessionSet_sessionNull_emitsNotProjecting() = testScope.runTest { val state by collectLastValue(repo.mediaProjectionState) - runCurrent() fakeMediaProjectionManager.dispatchOnSessionSet(session = null) @@ -98,7 +108,6 @@ class MediaProjectionManagerRepositoryTest : SysuiTestCase() { fun mediaProjectionState_onSessionSet_contentToRecordDisplay_emitsEntireScreen() = testScope.runTest { val state by collectLastValue(repo.mediaProjectionState) - runCurrent() fakeMediaProjectionManager.dispatchOnSessionSet( session = ContentRecordingSession.createDisplaySession(/* displayToMirror= */ 123) @@ -111,7 +120,6 @@ class MediaProjectionManagerRepositoryTest : SysuiTestCase() { fun mediaProjectionState_sessionSet_taskWithToken_noMatchingRunningTask_emitsEntireScreen() = testScope.runTest { val state by collectLastValue(repo.mediaProjectionState) - runCurrent() val taskWindowContainerToken = Binder() fakeMediaProjectionManager.dispatchOnSessionSet( @@ -128,7 +136,6 @@ class MediaProjectionManagerRepositoryTest : SysuiTestCase() { val task = createTask(taskId = 1, token = token) fakeActivityTaskManager.addRunningTasks(task) val state by collectLastValue(repo.mediaProjectionState) - runCurrent() fakeMediaProjectionManager.dispatchOnSessionSet( session = ContentRecordingSession.createTaskSession(token.asBinder()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt index b2ebe1bcbc8b..dfb688bbde4b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt @@ -61,6 +61,8 @@ class TaskSwitchInteractorTest : SysuiTestCase() { handler = Handler.getMain(), applicationScope = testScope.backgroundScope, tasksRepository = tasksRepo, + backgroundDispatcher = dispatcher, + mediaProjectionServiceHelper = fakeMediaProjectionManager.helper, ) private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo) @@ -118,6 +120,40 @@ class TaskSwitchInteractorTest : SysuiTestCase() { } @Test + fun taskSwitchChanges_projectingTask_foregroundTaskDifferent_thenSwitched_emitsUnchanged() = + testScope.runTest { + val projectedTask = createTask(taskId = 0) + val foregroundTask = createTask(taskId = 1) + val taskSwitchState by collectLastValue(interactor.taskSwitchChanges) + + fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask) + fakeMediaProjectionManager.dispatchOnSessionSet( + session = createSingleTaskSession(token = projectedTask.token.asBinder()) + ) + fakeActivityTaskManager.moveTaskToForeground(foregroundTask) + interactor.switchProjectedTask(foregroundTask) + + assertThat(taskSwitchState).isEqualTo(TaskSwitchState.TaskUnchanged) + } + + @Test + fun taskSwitchChanges_projectingTask_foregroundTaskDifferent_thenWentBack_emitsUnchanged() = + testScope.runTest { + val projectedTask = createTask(taskId = 0) + val foregroundTask = createTask(taskId = 1) + val taskSwitchState by collectLastValue(interactor.taskSwitchChanges) + + fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask) + fakeMediaProjectionManager.dispatchOnSessionSet( + session = createSingleTaskSession(token = projectedTask.token.asBinder()) + ) + fakeActivityTaskManager.moveTaskToForeground(foregroundTask) + interactor.goBackToTask(projectedTask) + + assertThat(taskSwitchState).isEqualTo(TaskSwitchState.TaskUnchanged) + } + + @Test fun taskSwitchChanges_projectingTask_foregroundTaskLauncher_emitsTaskUnchanged() = testScope.runTest { val projectedTask = createTask(taskId = 0) diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt index d0c6d7cddc46..c4e939339fa1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt @@ -21,14 +21,15 @@ import android.app.NotificationManager import android.os.Handler import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager +import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel +import com.android.systemui.res.R import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.mock @@ -42,6 +43,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor +import org.mockito.Mockito.never import org.mockito.Mockito.verify @OptIn(ExperimentalCoroutinesApi::class) @@ -49,7 +51,7 @@ import org.mockito.Mockito.verify @SmallTest class TaskSwitcherNotificationCoordinatorTest : SysuiTestCase() { - private val notificationManager: NotificationManager = mock() + private val notificationManager = mock<NotificationManager>() private val dispatcher = UnconfinedTestDispatcher() private val testScope = TestScope(dispatcher) @@ -70,22 +72,26 @@ class TaskSwitcherNotificationCoordinatorTest : SysuiTestCase() { handler = Handler.getMain(), applicationScope = testScope.backgroundScope, tasksRepository = tasksRepo, + backgroundDispatcher = dispatcher, + mediaProjectionServiceHelper = fakeMediaProjectionManager.helper, ) private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo) - private val viewModel = TaskSwitcherNotificationViewModel(interactor) - - private val coordinator = - TaskSwitcherNotificationCoordinator( - context, - notificationManager, - testScope.backgroundScope, - dispatcher, - viewModel - ) + private val viewModel = + TaskSwitcherNotificationViewModel(interactor, backgroundDispatcher = dispatcher) + + private lateinit var coordinator: TaskSwitcherNotificationCoordinator @Before fun setup() { + coordinator = + TaskSwitcherNotificationCoordinator( + context, + notificationManager, + testScope.backgroundScope, + viewModel, + fakeBroadcastDispatcher, + ) coordinator.start() } @@ -105,7 +111,7 @@ class TaskSwitcherNotificationCoordinatorTest : SysuiTestCase() { testScope.runTest { fakeMediaProjectionManager.dispatchOnStop() - verify(notificationManager).cancel(any()) + verify(notificationManager).cancel(any(), any()) } } @@ -114,7 +120,7 @@ class TaskSwitcherNotificationCoordinatorTest : SysuiTestCase() { testScope.runTest { fakeMediaProjectionManager.dispatchOnStop() val idCancel = argumentCaptor<Int>() - verify(notificationManager).cancel(idCancel.capture()) + verify(notificationManager).cancel(any(), idCancel.capture()) switchTask() val idNotify = argumentCaptor<Int>() @@ -124,9 +130,55 @@ class TaskSwitcherNotificationCoordinatorTest : SysuiTestCase() { } } + @Test + fun switchTaskAction_hidesNotification() = + testScope.runTest { + switchTask() + val notification = argumentCaptor<Notification>() + verify(notificationManager).notify(any(), any(), notification.capture()) + verify(notificationManager, never()).cancel(any(), any()) + + val action = findSwitchAction(notification.value) + fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + action.actionIntent.intent + ) + + verify(notificationManager).cancel(any(), any()) + } + + @Test + fun goBackAction_hidesNotification() = + testScope.runTest { + switchTask() + val notification = argumentCaptor<Notification>() + verify(notificationManager).notify(any(), any(), notification.capture()) + verify(notificationManager, never()).cancel(any(), any()) + + val action = findGoBackAction(notification.value) + fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + action.actionIntent.intent + ) + + verify(notificationManager).cancel(any(), any()) + } + + private fun findSwitchAction(notification: Notification): Notification.Action { + return notification.actions.first { + it.title == context.getString(R.string.media_projection_task_switcher_action_switch) + } + } + + private fun findGoBackAction(notification: Notification): Notification.Action { + return notification.actions.first { + it.title == context.getString(R.string.media_projection_task_switcher_action_back) + } + } + private fun switchTask() { - val projectedTask = FakeActivityTaskManager.createTask(taskId = 1) - val foregroundTask = FakeActivityTaskManager.createTask(taskId = 2) + val projectedTask = createTask(taskId = 1) + val foregroundTask = createTask(taskId = 2) fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask) fakeMediaProjectionManager.dispatchOnSessionSet( session = diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt index 7d38de4bc2d3..687f97082ce4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt @@ -31,8 +31,11 @@ import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMed import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState +import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel.Companion.NOTIFICATION_MAX_SHOW_DURATION import com.google.common.truth.Truth.assertThat +import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestCoroutineScheduler import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest @@ -44,7 +47,8 @@ import org.junit.runner.RunWith @SmallTest class TaskSwitcherNotificationViewModelTest : SysuiTestCase() { - private val dispatcher = UnconfinedTestDispatcher() + private val scheduler = TestCoroutineScheduler() + private val dispatcher = UnconfinedTestDispatcher(scheduler) private val testScope = TestScope(dispatcher) private val fakeActivityTaskManager = FakeActivityTaskManager() @@ -63,11 +67,14 @@ class TaskSwitcherNotificationViewModelTest : SysuiTestCase() { handler = Handler.getMain(), applicationScope = testScope.backgroundScope, tasksRepository = tasksRepo, + backgroundDispatcher = dispatcher, + mediaProjectionServiceHelper = fakeMediaProjectionManager.helper, ) private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo) - private val viewModel = TaskSwitcherNotificationViewModel(interactor) + private val viewModel = + TaskSwitcherNotificationViewModel(interactor, backgroundDispatcher = dispatcher) @Test fun uiState_notProjecting_emitsNotShowing() = @@ -135,6 +142,75 @@ class TaskSwitcherNotificationViewModelTest : SysuiTestCase() { } @Test + fun uiState_taskChanged_beforeDelayLimit_stillEmitsShowing() = + testScope.runTest { + val projectedTask = createTask(taskId = 1) + val foregroundTask = createTask(taskId = 2) + val uiState by collectLastValue(viewModel.uiState) + + fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask) + fakeMediaProjectionManager.dispatchOnSessionSet( + session = createSingleTaskSession(projectedTask.token.asBinder()) + ) + fakeActivityTaskManager.moveTaskToForeground(foregroundTask) + + testScheduler.advanceTimeBy(NOTIFICATION_MAX_SHOW_DURATION - 1.milliseconds) + assertThat(uiState) + .isEqualTo(TaskSwitcherNotificationUiState.Showing(projectedTask, foregroundTask)) + } + + @Test + fun uiState_taskChanged_afterDelayLimit_emitsNotShowing() = + testScope.runTest { + val projectedTask = createTask(taskId = 1) + val foregroundTask = createTask(taskId = 2) + val uiState by collectLastValue(viewModel.uiState) + + fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask) + fakeMediaProjectionManager.dispatchOnSessionSet( + session = createSingleTaskSession(projectedTask.token.asBinder()) + ) + fakeActivityTaskManager.moveTaskToForeground(foregroundTask) + + testScheduler.advanceTimeBy(NOTIFICATION_MAX_SHOW_DURATION) + assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing) + } + + @Test + fun uiState_projectingTask_foregroundTaskChanged_thenTaskSwitched_emitsNotShowing() = + testScope.runTest { + val projectedTask = createTask(taskId = 1) + val foregroundTask = createTask(taskId = 2) + val uiState by collectLastValue(viewModel.uiState) + + fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask) + fakeMediaProjectionManager.dispatchOnSessionSet( + session = createSingleTaskSession(projectedTask.token.asBinder()) + ) + fakeActivityTaskManager.moveTaskToForeground(foregroundTask) + viewModel.onSwitchTaskClicked(foregroundTask) + + assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing) + } + + @Test + fun uiState_projectingTask_foregroundTaskChanged_thenGoBack_emitsNotShowing() = + testScope.runTest { + val projectedTask = createTask(taskId = 1) + val foregroundTask = createTask(taskId = 2) + val uiState by collectLastValue(viewModel.uiState) + + fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask) + fakeMediaProjectionManager.dispatchOnSessionSet( + session = createSingleTaskSession(projectedTask.token.asBinder()) + ) + fakeActivityTaskManager.moveTaskToForeground(foregroundTask) + viewModel.onGoBackToTaskClicked(projectedTask) + + assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing) + } + + @Test fun uiState_projectingTask_foregroundTaskChanged_same_emitsNotShowing() = testScope.runTest { val projectedTask = createTask(taskId = 1) diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java index 52859cdeb406..d405df7c2cba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java @@ -43,13 +43,11 @@ import static org.mockito.Mockito.when; import android.content.res.Configuration; import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper.RunWithLooper; import android.util.SparseArray; import androidx.test.filters.SmallTest; import com.android.dx.mockito.inline.extended.StaticMockitoSession; -import com.android.systemui.Dependency; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; import com.android.systemui.model.SysUiState; @@ -61,7 +59,9 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.settings.SecureSettings; +import com.android.systemui.util.time.FakeSystemClock; import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.pip.Pip; @@ -76,7 +76,6 @@ import java.util.Optional; /** atest NavigationBarControllerTest */ @RunWith(AndroidTestingRunner.class) -@RunWithLooper @SmallTest public class NavigationBarControllerImplTest extends SysuiTestCase { @@ -88,6 +87,8 @@ public class NavigationBarControllerImplTest extends SysuiTestCase { private StaticMockitoSession mMockitoSession; private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); + private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); + @Mock private CommandQueue mCommandQueue; @Mock @@ -104,7 +105,7 @@ public class NavigationBarControllerImplTest extends SysuiTestCase { mock(NavigationModeController.class), mock(SysUiState.class), mCommandQueue, - Dependency.get(Dependency.MAIN_HANDLER), + mExecutor, mock(ConfigurationController.class), mock(NavBarHelper.class), mTaskbarDelegate, 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 7b285abe83ce..ada93db537e5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt @@ -33,6 +33,7 @@ import com.android.systemui.flags.Flags import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger import com.android.systemui.mediaprojection.SessionCreationSource import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver +import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate import com.android.systemui.model.SysUiState import com.android.systemui.qs.tiles.RecordIssueTile import com.android.systemui.res.R @@ -41,18 +42,18 @@ import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.phone.SystemUIDialogManager +import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import java.util.concurrent.CountDownLatch -import java.util.concurrent.Executor import java.util.concurrent.TimeUnit import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock @@ -74,12 +75,16 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() { @Mock private lateinit var userTracker: UserTracker @Mock private lateinit var userFileManager: UserFileManager @Mock private lateinit var sharedPreferences: SharedPreferences + @Mock private lateinit var screenCaptureDisabledDialogDelegate: + ScreenCaptureDisabledDialogDelegate + @Mock private lateinit var screenCaptureDisabledDialog: SystemUIDialog @Mock private lateinit var sysuiState: SysUiState @Mock private lateinit var systemUIDialogManager: SystemUIDialogManager @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher - @Mock private lateinit var bgExecutor: Executor - @Mock private lateinit var mainExecutor: Executor + private val systemClock = FakeSystemClock() + private val bgExecutor = FakeExecutor(systemClock) + private val mainExecutor = FakeExecutor(systemClock) @Mock private lateinit var mDialogTransitionAnimator: DialogTransitionAnimator private lateinit var dialog: SystemUIDialog @@ -92,6 +97,8 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() { whenever(dprLazy.get()).thenReturn(devicePolicyResolver) whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState) whenever(userContextProvider.userContext).thenReturn(mContext) + whenever(screenCaptureDisabledDialogDelegate.createDialog()) + .thenReturn(screenCaptureDisabledDialog) whenever( userFileManager.getSharedPreferences( eq(RecordIssueTile.TILE_SPEC), @@ -124,6 +131,7 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() { dprLazy, mediaProjectionMetricsLogger, userFileManager, + screenCaptureDisabledDialogDelegate, ) { latch.countDown() } @@ -163,13 +171,8 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() { val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch) screenRecordSwitch.isChecked = true - val bgCaptor = ArgumentCaptor.forClass(Runnable::class.java) - verify(bgExecutor).execute(bgCaptor.capture()) - bgCaptor.value.run() - - val mainCaptor = ArgumentCaptor.forClass(Runnable::class.java) - verify(mainExecutor).execute(mainCaptor.capture()) - mainCaptor.value.run() + bgExecutor.runAllReady() + mainExecutor.runAllReady() verify(mediaProjectionMetricsLogger, never()) .notifyProjectionInitiated( @@ -192,13 +195,8 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() { val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch) screenRecordSwitch.isChecked = true - val bgCaptor = ArgumentCaptor.forClass(Runnable::class.java) - verify(bgExecutor).execute(bgCaptor.capture()) - bgCaptor.value.run() - - val mainCaptor = ArgumentCaptor.forClass(Runnable::class.java) - verify(mainExecutor).execute(mainCaptor.capture()) - mainCaptor.value.run() + bgExecutor.runAllReady() + mainExecutor.runAllReady() verify(mediaProjectionMetricsLogger) .notifyProjectionInitiated( @@ -219,9 +217,7 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() { val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch) screenRecordSwitch.isChecked = true - val bgCaptor = ArgumentCaptor.forClass(Runnable::class.java) - verify(bgExecutor).execute(bgCaptor.capture()) - bgCaptor.value.run() + bgExecutor.runAllReady() verify(mediaProjectionMetricsLogger) .notifyProjectionInitiated( 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 6cbe8c9a939b..b3df12ee2c10 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java @@ -17,13 +17,11 @@ package com.android.systemui.screenrecord; import static android.os.Process.myUid; - import static com.google.common.truth.Truth.assertThat; - import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; - import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -48,10 +46,9 @@ import com.android.systemui.flags.Flags; import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger; import com.android.systemui.mediaprojection.SessionCreationSource; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver; -import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog; +import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate; import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.settings.UserContextProvider; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.phone.DialogDelegate; import com.android.systemui.statusbar.phone.SystemUIDialog; @@ -84,8 +81,6 @@ public class RecordingControllerTest extends SysuiTestCase { @Mock private BroadcastDispatcher mBroadcastDispatcher; @Mock - private UserContextProvider mUserContextProvider; - @Mock private ScreenCaptureDevicePolicyResolver mDevicePolicyResolver; @Mock private DialogTransitionAnimator mDialogTransitionAnimator; @@ -96,6 +91,22 @@ public class RecordingControllerTest extends SysuiTestCase { @Mock private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger; + @Mock + private ScreenCaptureDisabledDialogDelegate mScreenCaptureDisabledDialogDelegate; + @Mock + private SystemUIDialog mScreenCaptureDisabledDialog; + @Mock + private ScreenRecordDialogDelegate.Factory mScreenRecordDialogFactory; + @Mock + private ScreenRecordDialogDelegate mScreenRecordDialogDelegate; + @Mock + private ScreenRecordPermissionDialogDelegate.Factory + mScreenRecordPermissionDialogDelegateFactory; + @Mock + private ScreenRecordPermissionDialogDelegate mScreenRecordPermissionDialogDelegate; + @Mock + private SystemUIDialog mScreenRecordSystemUIDialog; + private FakeFeatureFlags mFeatureFlags; private RecordingController mController; private TestSystemUIDialogFactory mDialogFactory; @@ -108,8 +119,6 @@ public class RecordingControllerTest extends SysuiTestCase { Context spiedContext = spy(mContext); when(spiedContext.getUserId()).thenReturn(TEST_USER_ID); - when(mUserContextProvider.getUserContext()).thenReturn(spiedContext); - mDialogFactory = new TestSystemUIDialogFactory( mContext, Dependency.get(SystemUIDialogManager.class), @@ -119,16 +128,26 @@ public class RecordingControllerTest extends SysuiTestCase { ); mFeatureFlags = new FakeFeatureFlags(); + when(mScreenCaptureDisabledDialogDelegate.createDialog()) + .thenReturn(mScreenCaptureDisabledDialog); + when(mScreenRecordDialogFactory.create(any(), any())) + .thenReturn(mScreenRecordDialogDelegate); + when(mScreenRecordDialogDelegate.createDialog()).thenReturn(mScreenRecordSystemUIDialog); + when(mScreenRecordPermissionDialogDelegateFactory.create(any(), any(), anyInt(), any())) + .thenReturn(mScreenRecordPermissionDialogDelegate); + when(mScreenRecordPermissionDialogDelegate.createDialog()) + .thenReturn(mScreenRecordSystemUIDialog); mController = new RecordingController( mMainExecutor, mBroadcastDispatcher, - mContext, mFeatureFlags, - mUserContextProvider, () -> mDevicePolicyResolver, mUserTracker, mMediaProjectionMetricsLogger, - mDialogFactory); + mScreenCaptureDisabledDialogDelegate, + mScreenRecordDialogFactory, + mScreenRecordPermissionDialogDelegateFactory + ); mController.addCallback(mCallback); } @@ -242,8 +261,8 @@ public class RecordingControllerTest extends SysuiTestCase { mActivityStarter, /* onStartRecordingClicked= */ null); - assertThat(dialog).isSameInstanceAs(mDialogFactory.mLastCreatedDialog); - assertThat(mDialogFactory.mLastDelegate) + assertThat(dialog).isSameInstanceAs(mScreenRecordSystemUIDialog); + assertThat(mScreenRecordPermissionDialogDelegate) .isInstanceOf(ScreenRecordPermissionDialogDelegate.class); } @@ -255,7 +274,7 @@ public class RecordingControllerTest extends SysuiTestCase { Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags, mDialogTransitionAnimator, mActivityStarter, /* onStartRecordingClicked= */ null); - assertThat(dialog).isInstanceOf(ScreenRecordDialog.class); + assertThat(dialog).isEqualTo(mScreenRecordSystemUIDialog); } @Test @@ -267,7 +286,7 @@ public class RecordingControllerTest extends SysuiTestCase { Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags, mDialogTransitionAnimator, mActivityStarter, /* onStartRecordingClicked= */ null); - assertThat(dialog).isInstanceOf(ScreenCaptureDisabledDialog.class); + assertThat(dialog).isEqualTo(mScreenCaptureDisabledDialog); } @Test @@ -284,8 +303,8 @@ public class RecordingControllerTest extends SysuiTestCase { mActivityStarter, /* onStartRecordingClicked= */ null); - assertThat(dialog).isSameInstanceAs(mDialogFactory.mLastCreatedDialog); - assertThat(mDialogFactory.mLastDelegate) + assertThat(dialog).isSameInstanceAs(mScreenRecordSystemUIDialog); + assertThat(mScreenRecordPermissionDialogDelegate) .isInstanceOf(ScreenRecordPermissionDialogDelegate.class); } 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 90ced92c7f30..6e480746a076 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt @@ -39,6 +39,7 @@ import com.android.systemui.res.R import com.android.systemui.settings.UserContextProvider import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.phone.SystemUIDialogManager +import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat @@ -58,6 +59,7 @@ import org.mockito.MockitoAnnotations @TestableLooper.RunWithLooper(setAsMainLooper = true) class ScreenRecordPermissionDialogDelegateTest : SysuiTestCase() { + //@Mock private lateinit var dialogFactory: SystemUIDialog.Factory @Mock private lateinit var starter: ActivityStarter @Mock private lateinit var controller: RecordingController @Mock private lateinit var userContextProvider: UserContextProvider @@ -71,14 +73,17 @@ class ScreenRecordPermissionDialogDelegateTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) + whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true) + val systemUIDialogFactory = - SystemUIDialog.Factory( - context, - Dependency.get(SystemUIDialogManager::class.java), - Dependency.get(SysUiState::class.java), - Dependency.get(BroadcastDispatcher::class.java), - Dependency.get(DialogTransitionAnimator::class.java), - ) + SystemUIDialog.Factory( + context, + Dependency.get(SystemUIDialogManager::class.java), + Dependency.get(SysUiState::class.java), + Dependency.get(BroadcastDispatcher::class.java), + Dependency.get(DialogTransitionAnimator::class.java), + ) + val delegate = ScreenRecordPermissionDialogDelegate( UserHandle.of(0), @@ -88,11 +93,9 @@ class ScreenRecordPermissionDialogDelegateTest : SysuiTestCase() { userContextProvider, onStartRecordingClicked, mediaProjectionMetricsLogger, - systemUIDialogFactory + systemUIDialogFactory, ) dialog = delegate.createDialog() - delegate.onCreate(dialog, savedInstanceState = null) - whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true) } @After diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt index 2f911fffe335..92c240404b24 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt @@ -22,8 +22,10 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import java.lang.IllegalStateException +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -31,12 +33,14 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify @SmallTest +@OptIn(ExperimentalCoroutinesApi::class) class ScreenshotSoundControllerTest : SysuiTestCase() { private val soundProvider = mock<ScreenshotSoundProvider>() private val mediaPlayer = mock<MediaPlayer>() private val bgDispatcher = UnconfinedTestDispatcher() private val scope = TestScope(bgDispatcher) + @Before fun setup() { whenever(soundProvider.getScreenshotSound()).thenReturn(mediaPlayer) @@ -45,52 +49,59 @@ class ScreenshotSoundControllerTest : SysuiTestCase() { @Test fun init_soundLoading() { createController() - bgDispatcher.scheduler.runCurrent() + scope.advanceUntilIdle() verify(soundProvider).getScreenshotSound() } @Test - fun init_soundLoadingException_playAndReleaseDoNotThrow() = runTest { - whenever(soundProvider.getScreenshotSound()).thenThrow(IllegalStateException()) + fun init_soundLoadingException_playAndReleaseDoNotThrow() = + scope.runTest { + whenever(soundProvider.getScreenshotSound()).thenThrow(IllegalStateException()) - val controller = createController() + val controller = createController() - controller.playCameraSound().await() - controller.releaseScreenshotSound().await() + controller.playScreenshotSound() + advanceUntilIdle() - verify(mediaPlayer, never()).start() - verify(mediaPlayer, never()).release() - } + verify(mediaPlayer, never()).start() + verify(mediaPlayer, never()).release() + } @Test - fun playCameraSound_soundLoadingSuccessful_mediaPlayerPlays() = runTest { - val controller = createController() + fun playCameraSound_soundLoadingSuccessful_mediaPlayerPlays() = + scope.runTest { + val controller = createController() - controller.playCameraSound().await() + controller.playScreenshotSound() + advanceUntilIdle() - verify(mediaPlayer).start() - } + verify(mediaPlayer).start() + } @Test - fun playCameraSound_illegalStateException_doesNotThrow() = runTest { - whenever(mediaPlayer.start()).thenThrow(IllegalStateException()) + fun playCameraSound_illegalStateException_doesNotThrow() = + scope.runTest { + whenever(mediaPlayer.start()).thenThrow(IllegalStateException()) - val controller = createController() - controller.playCameraSound().await() + val controller = createController() + controller.playScreenshotSound() + advanceUntilIdle() - verify(mediaPlayer).start() - verify(mediaPlayer).release() - } + verify(mediaPlayer).start() + verify(mediaPlayer).release() + } @Test - fun playCameraSound_soundLoadingSuccessful_mediaPlayerReleases() = runTest { - val controller = createController() + fun playCameraSound_soundLoadingSuccessful_mediaPlayerReleases() = + scope.runTest { + val controller = createController() - controller.releaseScreenshotSound().await() + controller.releaseScreenshotSound() + advanceUntilIdle() - verify(mediaPlayer).release() - } + verify(mediaPlayer).release() + } private fun createController() = ScreenshotSoundControllerImpl(soundProvider, scope, bgDispatcher) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt index 665fc750c742..62d2d0efe24c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt @@ -26,6 +26,7 @@ import android.view.View import android.view.WindowManager import android.widget.FrameLayout import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.SceneKey import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.FakeCommunalRepository @@ -33,9 +34,8 @@ import com.android.systemui.communal.data.repository.fakeCommunalRepository import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.interactor.setCommunalAvailable -import com.android.systemui.communal.shared.model.CommunalSceneKey +import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.ui.viewmodel.CommunalViewModel -import com.android.systemui.compose.ComposeFacade import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testDispatcher @@ -52,9 +52,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.test.UnconfinedTestDispatcher import org.junit.After import org.junit.Assert.assertThrows -import org.junit.Assume.assumeTrue import org.junit.Before -import org.junit.BeforeClass import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock @@ -155,7 +153,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { @Test fun onTouchEvent_communalClosed_doesNotIntercept() { // Communal is closed. - goToScene(CommunalSceneKey.Blank) + goToScene(CommunalScenes.Blank) assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse() } @@ -163,7 +161,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { @Test fun onTouchEvent_openGesture_interceptsTouches() { // Communal is closed. - goToScene(CommunalSceneKey.Blank) + goToScene(CommunalScenes.Blank) // Initial touch down is intercepted, and so are touches outside of the region, until an // up event is received. @@ -176,7 +174,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { @Test fun onTouchEvent_communalOpen_interceptsTouches() { // Communal is open. - goToScene(CommunalSceneKey.Communal) + goToScene(CommunalScenes.Communal) // Touch events are intercepted outside of any gesture areas. assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue() @@ -187,7 +185,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { @Test fun onTouchEvent_topSwipeWhenCommunalOpen_doesNotIntercept() { // Communal is open. - goToScene(CommunalSceneKey.Communal) + goToScene(CommunalScenes.Communal) // Touch event in the top swipe reqgion is not intercepted. assertThat(underTest.onTouchEvent(DOWN_IN_TOP_SWIPE_REGION_EVENT)).isFalse() @@ -196,7 +194,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { @Test fun onTouchEvent_bottomSwipeWhenCommunalOpen_doesNotIntercept() { // Communal is open. - goToScene(CommunalSceneKey.Communal) + goToScene(CommunalScenes.Communal) // Touch event in the bottom swipe reqgion is not intercepted. assertThat(underTest.onTouchEvent(DOWN_IN_BOTTOM_SWIPE_REGION_EVENT)).isFalse() @@ -205,7 +203,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { @Test fun onTouchEvent_communalAndBouncerShowing_doesNotIntercept() { // Communal is open. - goToScene(CommunalSceneKey.Communal) + goToScene(CommunalScenes.Communal) // Bouncer is visible. bouncerShowingFlow.value = true @@ -220,7 +218,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { @Test fun onTouchEvent_communalAndShadeShowing_doesNotIntercept() { // Communal is open. - goToScene(CommunalSceneKey.Communal) + goToScene(CommunalScenes.Communal) shadeShowingFlow.value = true testableLooper.processAllMessages() @@ -232,7 +230,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { @Test fun onTouchEvent_containerViewDisposed_doesNotIntercept() { // Communal is open. - goToScene(CommunalSceneKey.Communal) + goToScene(CommunalScenes.Communal) // Touch events are intercepted. assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue() @@ -265,7 +263,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { wm.updateViewLayout(parentView, lp) } - private fun goToScene(scene: CommunalSceneKey) { + private fun goToScene(scene: SceneKey) { communalRepository.setDesiredScene(scene) testableLooper.processAllMessages() } @@ -305,13 +303,5 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, CONTAINER_HEIGHT.toFloat(), 0) private val MOVE_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) private val UP_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0) - - @BeforeClass - @JvmStatic - fun beforeClass() { - // Glanceable hub requires Compose, no point running any of these tests if compose isn't - // enabled. - assumeTrue(ComposeFacade.isComposeAvailable()) - } } } 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 f8771b26476c..acbf9976873a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -287,7 +287,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @Mock protected KeyguardMediaController mKeyguardMediaController; @Mock protected NavigationModeController mNavigationModeController; @Mock protected NavigationBarController mNavigationBarController; - @Mock protected QuickSettingsController mQsController; + @Mock protected QuickSettingsControllerImpl mQsController; @Mock protected ShadeHeaderController mShadeHeaderController; @Mock protected ContentResolver mContentResolver; @Mock protected TapAgainViewController mTapAgainViewController; @@ -380,7 +380,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { protected final ShadeExpansionStateManager mShadeExpansionStateManager = new ShadeExpansionStateManager(); - protected QuickSettingsController mQuickSettingsController; + protected QuickSettingsControllerImpl mQuickSettingsController; @Mock protected Lazy<NotificationPanelViewController> mNotificationPanelViewControllerLazy; protected FragmentHostManager.FragmentListener mFragmentListener; @@ -461,6 +461,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mKeyguardLogger, mInteractionJankMonitor, mKeyguardInteractor, + mKeyguardTransitionInteractor, mDumpManager, mPowerInteractor)); @@ -793,7 +794,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { when(mNotificationPanelViewControllerLazy.get()) .thenReturn(mNotificationPanelViewController); - mQuickSettingsController = new QuickSettingsController( + mQuickSettingsController = new QuickSettingsControllerImpl( mNotificationPanelViewControllerLazy, mView, mQsFrameTranslateController, 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 b426d1de0b00..617b25d97eee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.shade +import org.mockito.Mockito.`when` as whenever import android.content.Context import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper @@ -33,7 +34,6 @@ import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.bouncer.ui.binder.BouncerViewBinder import com.android.systemui.classifier.FalsingCollectorFake -import com.android.systemui.compose.ComposeFacade.isComposeAvailable import com.android.systemui.dock.DockManager import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FakeFeatureFlagsClassic @@ -88,7 +88,6 @@ import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @@ -113,7 +112,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager @Mock private lateinit var statusBarWindowStateController: StatusBarWindowStateController - @Mock private lateinit var quickSettingsController: QuickSettingsController + @Mock private lateinit var quickSettingsController: QuickSettingsControllerImpl @Mock private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController @Mock private lateinit var lockIconViewController: LockIconViewController @@ -511,10 +510,6 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @Test @Ignore("b/321332798") fun setsUpCommunalHubLayout_whenFlagEnabled() { - if (!isComposeAvailable()) { - return - } - whenever(mGlanceableHubContainerController.communalAvailable()) .thenReturn(MutableStateFlow(true)) @@ -537,10 +532,6 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @Test fun doesNotSetupCommunalHubLayout_whenFlagDisabled() { - if (!isComposeAvailable()) { - return - } - whenever(mGlanceableHubContainerController.communalAvailable()) .thenReturn(MutableStateFlow(false)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java index 0b49a954e848..4809a47709c8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java @@ -109,7 +109,7 @@ import org.mockito.MockitoAnnotations; import kotlinx.coroutines.test.TestScope; -public class QuickSettingsControllerBaseTest extends SysuiTestCase { +public class QuickSettingsControllerImplBaseTest extends SysuiTestCase { protected static final float QS_FRAME_START_X = 0f; protected static final int QS_FRAME_WIDTH = 1000; protected static final int QS_FRAME_TOP = 0; @@ -119,7 +119,7 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { protected static final int DEFAULT_MIN_HEIGHT_SPLIT_SHADE = DEFAULT_HEIGHT; protected static final int DEFAULT_MIN_HEIGHT = 300; - protected QuickSettingsController mQsController; + protected QuickSettingsControllerImpl mQsController; protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); protected TestScope mTestScope = mKosmos.getTestScope(); @@ -304,7 +304,7 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { mMainHandler = new Handler(Looper.getMainLooper()); - mQsController = new QuickSettingsController( + mQsController = new QuickSettingsControllerImpl( mPanelViewControllerLazy, mPanelView, mQsFrameTranslateController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java index 997e0e27ef9c..b16f41234656 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java @@ -41,8 +41,8 @@ import android.view.MotionEvent; import androidx.test.filters.SmallTest; -import com.android.systemui.res.R; import com.android.systemui.plugins.qs.QS; +import com.android.systemui.res.R; import org.junit.Test; import org.junit.runner.RunWith; @@ -53,7 +53,7 @@ import java.util.List; @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) -public class QuickSettingsControllerTest extends QuickSettingsControllerBaseTest { +public class QuickSettingsControllerImplTest extends QuickSettingsControllerImplBaseTest { @Test public void testCloseQsSideEffects() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt index cc4a06341cc6..2c453a711c87 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt @@ -27,7 +27,7 @@ import org.junit.Test @SmallTest @OptIn(ExperimentalCoroutinesApi::class) -class QuickSettingsControllerWithCoroutinesTest : QuickSettingsControllerBaseTest() { +class QuickSettingsControllerImplWithCoroutinesTest : QuickSettingsControllerImplBaseTest() { @Test fun isExpansionEnabled_dozing_false() = diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt index 651006dfc953..2f957b09467b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt @@ -5,6 +5,8 @@ package com.android.systemui.shade.transition import android.platform.test.annotations.DisableFlags import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey import com.android.systemui.Flags.FLAG_SCENE_CONTAINER import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -20,8 +22,7 @@ import com.android.systemui.scene.domain.interactor.PanelExpansionInteractor import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.FakeSceneDataSource -import com.android.systemui.scene.shared.model.ObservableTransitionState -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource import com.android.systemui.shade.STATE_OPENING import com.android.systemui.shade.ShadeExpansionChangeEvent @@ -117,13 +118,13 @@ class ShadeTransitionControllerTest : SysuiTestCase() { setUnlocked(true) val transitionState = MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(SceneKey.Gone) + ObservableTransitionState.Idle(Scenes.Gone) ) sceneInteractor.setTransitionState(transitionState) - changeScene(SceneKey.Gone, transitionState) + changeScene(Scenes.Gone, transitionState) val currentScene by collectLastValue(sceneInteractor.currentScene) - assertThat(currentScene).isEqualTo(SceneKey.Gone) + assertThat(currentScene).isEqualTo(Scenes.Gone) assertThat(latestChangeEvent) .isEqualTo( @@ -135,7 +136,7 @@ class ShadeTransitionControllerTest : SysuiTestCase() { ) ) - changeScene(SceneKey.Shade, transitionState) { progress -> + changeScene(Scenes.Shade, transitionState) { progress -> assertThat(latestChangeEvent) .isEqualTo( ShadeExpansionChangeEvent( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt index fe16347fa298..dfbb6ea08f82 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt @@ -54,7 +54,6 @@ import com.android.systemui.power.data.repository.FakePowerRepository import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags -import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.shade.LargeScreenHeaderHelper import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.shade.domain.interactor.ShadeInteractor @@ -87,6 +86,7 @@ import org.mockito.Mockito import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever +import com.android.systemui.scene.shared.model.Scenes import org.mockito.MockitoAnnotations @SmallTest @@ -313,48 +313,48 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { kosmos.fakeDeviceEntryRepository.setUnlocked(false) runCurrent() kosmos.sceneInteractor.changeScene( - toScene = SceneKey.Lockscreen, + toScene = Scenes.Lockscreen, loggingReason = "reason" ) runCurrent() assertThat(kosmos.deviceUnlockedInteractor.isDeviceUnlocked.value).isFalse() - assertThat(currentScene).isEqualTo(SceneKey.Lockscreen) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) // Call start to begin hydrating based on the scene framework: underTest.start() - kosmos.sceneInteractor.changeScene(toScene = SceneKey.Bouncer, loggingReason = "reason") + kosmos.sceneInteractor.changeScene(toScene = Scenes.Bouncer, loggingReason = "reason") runCurrent() - assertThat(currentScene).isEqualTo(SceneKey.Bouncer) + assertThat(currentScene).isEqualTo(Scenes.Bouncer) assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD) - kosmos.sceneInteractor.changeScene(toScene = SceneKey.Shade, loggingReason = "reason") + kosmos.sceneInteractor.changeScene(toScene = Scenes.Shade, loggingReason = "reason") runCurrent() - assertThat(currentScene).isEqualTo(SceneKey.Shade) + assertThat(currentScene).isEqualTo(Scenes.Shade) assertThat(statusBarState).isEqualTo(StatusBarState.SHADE_LOCKED) kosmos.sceneInteractor.changeScene( - toScene = SceneKey.QuickSettings, + toScene = Scenes.QuickSettings, loggingReason = "reason" ) runCurrent() - assertThat(currentScene).isEqualTo(SceneKey.QuickSettings) + assertThat(currentScene).isEqualTo(Scenes.QuickSettings) assertThat(statusBarState).isEqualTo(StatusBarState.SHADE_LOCKED) kosmos.sceneInteractor.changeScene( - toScene = SceneKey.Communal, + toScene = Scenes.Communal, loggingReason = "reason" ) runCurrent() - assertThat(currentScene).isEqualTo(SceneKey.Communal) + assertThat(currentScene).isEqualTo(Scenes.Communal) assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD) kosmos.sceneInteractor.changeScene( - toScene = SceneKey.Lockscreen, + toScene = Scenes.Lockscreen, loggingReason = "reason" ) runCurrent() - assertThat(currentScene).isEqualTo(SceneKey.Lockscreen) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD) } @@ -377,25 +377,25 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { ) kosmos.fakeDeviceEntryRepository.setUnlocked(true) runCurrent() - kosmos.sceneInteractor.changeScene(toScene = SceneKey.Gone, loggingReason = "reason") + kosmos.sceneInteractor.changeScene(toScene = Scenes.Gone, loggingReason = "reason") runCurrent() assertThat(kosmos.deviceUnlockedInteractor.isDeviceUnlocked.value).isTrue() - assertThat(currentScene).isEqualTo(SceneKey.Gone) + assertThat(currentScene).isEqualTo(Scenes.Gone) // Call start to begin hydrating based on the scene framework: underTest.start() - kosmos.sceneInteractor.changeScene(toScene = SceneKey.Shade, loggingReason = "reason") + kosmos.sceneInteractor.changeScene(toScene = Scenes.Shade, loggingReason = "reason") runCurrent() - assertThat(currentScene).isEqualTo(SceneKey.Shade) + assertThat(currentScene).isEqualTo(Scenes.Shade) assertThat(statusBarState).isEqualTo(StatusBarState.SHADE) kosmos.sceneInteractor.changeScene( - toScene = SceneKey.QuickSettings, + toScene = Scenes.QuickSettings, loggingReason = "reason" ) runCurrent() - assertThat(currentScene).isEqualTo(SceneKey.QuickSettings) + assertThat(currentScene).isEqualTo(Scenes.QuickSettings) assertThat(statusBarState).isEqualTo(StatusBarState.SHADE) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt index dbf7b6ce145e..012ff2e31562 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt @@ -38,6 +38,7 @@ import android.util.ArraySet import android.view.View import android.view.accessibility.accessibilityManager import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState import com.android.internal.logging.MetricsLogger import com.android.internal.logging.UiEventLogger import com.android.internal.logging.metricsLogger @@ -55,8 +56,7 @@ import com.android.systemui.scene.data.repository.WindowRootViewVisibilityReposi import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags -import com.android.systemui.scene.shared.model.ObservableTransitionState -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.settings.UserContextProvider import com.android.systemui.shade.shadeControllerSceneImpl import com.android.systemui.statusbar.NotificationEntryHelper @@ -602,9 +602,9 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { private fun setIsLockscreenOrShadeVisible(isVisible: Boolean) { val key = if (isVisible) { - SceneKey.Lockscreen + Scenes.Lockscreen } else { - SceneKey.Bouncer + Scenes.Bouncer } sceneInteractor.changeScene(key, "test") sceneInteractor.setTransitionState( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index e78081fc34bd..fb49499fc29d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -89,6 +89,8 @@ import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder; import com.android.systemui.statusbar.policy.SmartReplyConstants; import com.android.systemui.statusbar.policy.SmartReplyStateInflater; import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent; +import com.android.systemui.util.time.SystemClock; +import com.android.systemui.util.time.SystemClockImpl; import com.android.systemui.wmshell.BubblesManager; import com.android.systemui.wmshell.BubblesTestActivity; @@ -136,6 +138,8 @@ public class NotificationTestHelper { public final Runnable mFutureDismissalRunnable; private @InflationFlag int mDefaultInflationFlags; private final FakeFeatureFlags mFeatureFlags; + private final SystemClock mSystemClock; + private final RowInflaterTaskLogger mRowInflaterTaskLogger; public NotificationTestHelper( Context context, @@ -199,6 +203,9 @@ public class NotificationTestHelper { mFutureDismissalRunnable = mock(Runnable.class); when(mOnUserInteractionCallback.registerFutureDismissal(any(), anyInt())) .thenReturn(mFutureDismissalRunnable); + + mSystemClock = new SystemClockImpl(); + mRowInflaterTaskLogger = mock(RowInflaterTaskLogger.class); } public void setDefaultInflationFlags(@InflationFlag int defaultInflationFlags) { @@ -572,7 +579,8 @@ public class NotificationTestHelper { LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( Context.LAYOUT_INFLATER_SERVICE); if (com.android.systemui.Flags.notificationRowUserContext()) { - inflater.setFactory2(new RowInflaterTask.RowAsyncLayoutInflater(entry)); + inflater.setFactory2(new RowInflaterTask.RowAsyncLayoutInflater(entry, mSystemClock, + mRowInflaterTaskLogger)); } mRow = (ExpandableNotificationRow) inflater.inflate( R.layout.status_bar_notification_row, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackInteractorTest.kt new file mode 100644 index 000000000000..1c6bda985a0e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackInteractorTest.kt @@ -0,0 +1,85 @@ +/* + * 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. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.statusbar.notification.stack.domain.interactor + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.kosmos.testScope +import com.android.systemui.power.data.repository.fakePowerRepository +import com.android.systemui.power.shared.model.WakefulnessState +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class NotificationStackInteractorTest : SysuiTestCase() { + private val kosmos = testKosmos() + val underTest + get() = kosmos.notificationStackInteractor + + @Test + fun testIsShowingOnLockscreen_falseWhenViewingShade() = + kosmos.testScope.runTest { + val onLockscreen by collectLastValue(underTest.isShowingOnLockscreen) + + // WHEN shade is open + kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) + runCurrent() + + // THEN notifications are not showing on lockscreen + assertThat(onLockscreen).isFalse() + } + + @Test + fun testIsShowingOnLockscreen_trueWhenViewingKeyguard() = + kosmos.testScope.runTest { + val onLockscreen by collectLastValue(underTest.isShowingOnLockscreen) + + // WHEN on keyguard + kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + runCurrent() + + // THEN notifications are showing on lockscreen + assertThat(onLockscreen).isTrue() + } + + @Test + fun testIsShowingOnLockscreen_trueWhenStartingToSleep() = + kosmos.testScope.runTest { + val onLockscreen by collectLastValue(underTest.isShowingOnLockscreen) + + // WHEN shade is open + kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) + // AND device is starting to go to sleep + kosmos.fakePowerRepository.updateWakefulness(WakefulnessState.STARTING_TO_SLEEP) + runCurrent() + + // THEN notifications are showing on lockscreen + assertThat(onLockscreen).isTrue() + } +} 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 3a94295de668..84156ee1fd53 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 @@ -73,6 +73,7 @@ import android.view.WindowManager; import androidx.test.filters.SmallTest; +import com.android.compose.animation.scene.ObservableTransitionState; import com.android.internal.colorextraction.ColorExtractor; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -95,8 +96,7 @@ import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.communal.data.repository.CommunalRepository; import com.android.systemui.communal.domain.interactor.CommunalInteractor; -import com.android.systemui.communal.shared.model.CommunalSceneKey; -import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState; +import com.android.systemui.communal.shared.model.CommunalScenes; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; @@ -842,16 +842,16 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Test public void testEnteringGlanceableHub_updatesScrim() { // Transition to the glanceable hub. - mCommunalRepository.setTransitionState(flowOf(new ObservableCommunalTransitionState.Idle( - CommunalSceneKey.Communal.INSTANCE))); + mCommunalRepository.setTransitionState(flowOf(new ObservableTransitionState.Idle( + CommunalScenes.Communal))); mTestScope.getTestScheduler().runCurrent(); // ScrimState also transitions. verify(mScrimController).transitionTo(ScrimState.GLANCEABLE_HUB); // Transition away from the glanceable hub. - mCommunalRepository.setTransitionState(flowOf(new ObservableCommunalTransitionState.Idle( - CommunalSceneKey.Blank.INSTANCE))); + mCommunalRepository.setTransitionState(flowOf(new ObservableTransitionState.Idle( + CommunalScenes.Blank))); mTestScope.getTestScheduler().runCurrent(); // ScrimState goes back to UNLOCKED. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt index 3792d5c1d6b9..443dd6a5dbc4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt @@ -234,21 +234,16 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { } @Test - fun shadeIsExpandedOnStatusIconClick() { + fun shadeIsExpandedOnStatusIconMouseClick() { val view = createViewMock() InstrumentationRegistry.getInstrumentation().runOnMainSync { controller = createAndInitController(view) } val statusContainer = view.requireViewById<View>(R.id.system_icons) statusContainer.dispatchTouchEvent( - getMotionEventFromSource( - MotionEvent.ACTION_UP, - 0, - 0, - InputDevice.SOURCE_MOUSE - ) + getActionUpEventFromSource(InputDevice.SOURCE_MOUSE) ) - verify(shadeViewController).expand(any()) + verify(shadeControllerImpl).animateExpandShade() } @Test @@ -259,18 +254,13 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { } val statusContainer = view.requireViewById<View>(R.id.system_icons) val handled = statusContainer.dispatchTouchEvent( - getMotionEventFromSource( - MotionEvent.ACTION_UP, - 0, - 0, - InputDevice.SOURCE_TOUCHSCREEN - ) + getActionUpEventFromSource(InputDevice.SOURCE_TOUCHSCREEN) ) assertThat(handled).isFalse() } - private fun getMotionEventFromSource(action: Int, x: Int, y: Int, source: Int): MotionEvent { - val ev = MotionEvent.obtain(0, 0, action, x.toFloat(), y.toFloat(), 0) + private fun getActionUpEventFromSource(source: Int): MotionEvent { + val ev = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0f, 0f, 0) ev.source = source return ev } @@ -282,7 +272,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { controller = createAndInitController(view) } view.performClick() - verify(shadeViewController, never()).expand(any()) + verify(shadeControllerImpl, never()).animateExpandShade() } private fun getCommandQueueCallback(): CommandQueue.Callbacks { 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 3666248d1783..f050857d5df2 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 @@ -86,6 +86,7 @@ import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.navigationbar.TaskbarDelegate; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; +import com.android.systemui.scene.domain.interactor.SceneInteractor; import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeExpansionChangeEvent; @@ -224,7 +225,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { () -> mock(KeyguardDismissActionInteractor.class), mSelectedUserInteractor, () -> mock(KeyguardSurfaceBehindInteractor.class), - mock(JavaAdapter.class)) { + mock(JavaAdapter.class), + () -> mock(SceneInteractor.class)) { @Override public ViewRootImpl getViewRootImpl() { return mViewRootImpl; @@ -733,7 +735,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { () -> mock(KeyguardDismissActionInteractor.class), mSelectedUserInteractor, () -> mock(KeyguardSurfaceBehindInteractor.class), - mock(JavaAdapter.class)) { + mock(JavaAdapter.class), + () -> mock(SceneInteractor.class)) { @Override public ViewRootImpl getViewRootImpl() { return mViewRootImpl; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index 41514ce3e72c..938b2f88d4b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java @@ -79,6 +79,7 @@ import com.android.systemui.shade.ShadeViewController; import com.android.systemui.shade.data.repository.FakeShadeRepository; import com.android.systemui.shade.data.repository.ShadeAnimationRepository; import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl; +import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationClickNotifier; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationPresenter; @@ -130,6 +131,8 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { @Mock private ActivityStarter mActivityStarter; @Mock + private CommandQueue mCommandQueue; + @Mock private NotificationClickNotifier mClickNotifier; @Mock private StatusBarStateController mStatusBarStateController; @@ -236,6 +239,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { mVisibilityProvider, headsUpManager, mActivityStarter, + mCommandQueue, mClickNotifier, mStatusBarKeyguardViewManager, mock(KeyguardManager.class), @@ -257,7 +261,9 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { mock(NotificationShadeWindowController.class), mActivityTransitionAnimator, new ShadeAnimationInteractorLegacyImpl( - new ShadeAnimationRepository(), new FakeShadeRepository()), + new ShadeAnimationRepository(), + new FakeShadeRepository() + ), notificationAnimationProvider, mock(LaunchFullScreenIntentProvider.class), mPowerInteractor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java index 1dafcc48f19f..b0404a055a68 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java @@ -15,13 +15,14 @@ package com.android.systemui.statusbar.phone; import static android.view.Display.DEFAULT_DISPLAY; + import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE; import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK; import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE; + import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepository.kt new file mode 100644 index 000000000000..b8284acc997b --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepository.kt @@ -0,0 +1,37 @@ +/* + * 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.camera.data.repository + +import android.os.UserHandle +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +class FakeCameraAutoRotateRepository : CameraAutoRotateRepository { + private val userMap = mutableMapOf<Int, MutableStateFlow<Boolean>>() + + /** Send a Unit signal when value changes */ + override fun isCameraAutoRotateSettingEnabled(userHandle: UserHandle): StateFlow<Boolean> = + getFlow(userHandle.identifier) + + fun setEnabled(userHandle: UserHandle, enabled: Boolean) { + getFlow(userHandle.identifier).value = enabled + } + + /** initializes the flow if already not */ + private fun getFlow(userId: Int): MutableStateFlow<Boolean> = + userMap.getOrPut(userId) { MutableStateFlow(false) } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepositoryKosmos.kt index ad8ccb00f45f..615c59661f1d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepositoryKosmos.kt @@ -14,12 +14,9 @@ * limitations under the License. */ -package com.android.systemui.volume.panel +package com.android.systemui.camera.data.repository import com.android.systemui.kosmos.Kosmos -import com.android.systemui.media.mediaOutputDialogFactory -import com.android.systemui.plugins.activityStarter -import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor -val Kosmos.mediaOutputActionsInteractor by - Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogFactory, activityStarter) } +val Kosmos.fakeCameraAutoRotateRepository: FakeCameraAutoRotateRepository by + Kosmos.Fixture { FakeCameraAutoRotateRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepository.kt new file mode 100644 index 000000000000..994e9b2f3683 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepository.kt @@ -0,0 +1,36 @@ +/* + * 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.camera.data.repository + +import android.os.UserHandle +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +class FakeCameraSensorPrivacyRepository : CameraSensorPrivacyRepository { + + private val userMap = mutableMapOf<Int, MutableStateFlow<Boolean>>() + override fun isEnabled(userHandle: UserHandle): StateFlow<Boolean> = + getFlow(userHandle.identifier) + + fun setEnabled(userHandle: UserHandle, enabled: Boolean) { + getFlow(userHandle.identifier).value = enabled + } + + /** initializes the flow if already not */ + private fun getFlow(userId: Int): MutableStateFlow<Boolean> = + userMap.getOrPut(userId) { MutableStateFlow(false) } +} diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/UserActionResult.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepositoryKosmos.kt index c6ae21505c68..c7e704cab42f 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/UserActionResult.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepositoryKosmos.kt @@ -14,18 +14,9 @@ * limitations under the License. */ -package com.android.systemui.scene.shared.model +package com.android.systemui.camera.data.repository -data class UserActionResult( +import com.android.systemui.kosmos.Kosmos - /** The scene we should be transitioning due to the [UserAction]. */ - val toScene: SceneKey, - - /** - * The key of the transition that should be used, if a specific one should be used. - * - * If `null`, the transition used will be the corresponding transition from the collection - * passed into the UI layer. - */ - val transitionKey: TransitionKey? = null, -) +val Kosmos.fakeCameraSensorPrivacyRepository: FakeCameraSensorPrivacyRepository by + Kosmos.Fixture { FakeCameraSensorPrivacyRepository() } 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 9d508d23dca7..5ff588f810bd 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 @@ -1,7 +1,8 @@ package com.android.systemui.communal.data.repository -import com.android.systemui.communal.shared.model.CommunalSceneKey -import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey +import com.android.systemui.communal.shared.model.CommunalScenes import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -16,17 +17,16 @@ import kotlinx.coroutines.flow.stateIn @OptIn(ExperimentalCoroutinesApi::class) class FakeCommunalRepository( applicationScope: CoroutineScope, - override val desiredScene: MutableStateFlow<CommunalSceneKey> = - MutableStateFlow(CommunalSceneKey.DEFAULT), + override val desiredScene: MutableStateFlow<SceneKey> = + MutableStateFlow(CommunalScenes.Default), ) : CommunalRepository { - override fun setDesiredScene(desiredScene: CommunalSceneKey) { + override fun setDesiredScene(desiredScene: SceneKey) { this.desiredScene.value = desiredScene } - private val defaultTransitionState = - ObservableCommunalTransitionState.Idle(CommunalSceneKey.DEFAULT) - private val _transitionState = MutableStateFlow<Flow<ObservableCommunalTransitionState>?>(null) - override val transitionState: StateFlow<ObservableCommunalTransitionState> = + private val defaultTransitionState = ObservableTransitionState.Idle(CommunalScenes.Default) + private val _transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null) + override val transitionState: StateFlow<ObservableTransitionState> = _transitionState .flatMapLatest { innerFlowOrNull -> innerFlowOrNull ?: flowOf(defaultTransitionState) } .stateIn( @@ -35,7 +35,7 @@ class FakeCommunalRepository( initialValue = defaultTransitionState, ) - override fun setTransitionState(transitionState: Flow<ObservableCommunalTransitionState>?) { + override fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) { _transitionState.value = transitionState } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt index 3faa6eb8f5f2..4e05de27bb33 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt @@ -17,7 +17,6 @@ package com.android.systemui.flags import android.util.Log -import com.android.systemui.compose.ComposeFacade import com.android.systemui.scene.shared.flag.SceneContainerFlag import org.junit.Assert import org.junit.Assume @@ -42,10 +41,6 @@ class SceneContainerRule : TestRule { null || description?.getAnnotation(EnableSceneContainer::class.java) != null if (hasAnnotation) { Assume.assumeTrue( - "Compose must be available for @EnableSceneContainer test", - ComposeFacade.isComposeAvailable() - ) - Assume.assumeTrue( "Couldn't set Flags.SCENE_CONTAINER_ENABLED for @EnableSceneContainer test", trySetSceneContainerEnabled(true) ) 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 1e305d67d40d..793e2d7efcda 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 @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.data.repository import android.graphics.Point +import com.android.systemui.common.shared.model.Position import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.BiometricUnlockSource @@ -57,6 +58,9 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { private val _bottomAreaAlpha = MutableStateFlow(1f) override val bottomAreaAlpha: StateFlow<Float> = _bottomAreaAlpha + private val _clockPosition = MutableStateFlow(Position(0, 0)) + override val clockPosition: StateFlow<Position> = _clockPosition + private val _isKeyguardShowing = MutableStateFlow(false) override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing @@ -145,6 +149,10 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { _bottomAreaAlpha.value = alpha } + override fun setClockPosition(x: Int, y: Int) { + _clockPosition.value = Position(x, y) + } + fun setKeyguardShowing(isShowing: Boolean) { _isKeyguardShowing.value = isShowing } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt index da5cd679351f..2477415cc06e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt @@ -20,6 +20,7 @@ import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepos import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope +import com.android.systemui.power.domain.interactor.powerInteractor val Kosmos.fromAodTransitionInteractor by Kosmos.Fixture { @@ -30,5 +31,6 @@ val Kosmos.fromAodTransitionInteractor by bgDispatcher = testDispatcher, mainDispatcher = testDispatcher, keyguardInteractor = keyguardInteractor, + powerInteractor = powerInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt index 8ca53e6591c0..c2300a1ed1ad 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt @@ -39,8 +39,11 @@ val Kosmos.keyguardRootViewModel by Fixture { keyguardTransitionInteractor = keyguardTransitionInteractor, notificationsKeyguardInteractor = notificationsKeyguardInteractor, alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel, + aodToGoneTransitionViewModel = aodToGoneTransitionViewModel, aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel, + dozingToGoneTransitionViewModel = dozingToGoneTransitionViewModel, dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel, + dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel, glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel, goneToAodTransitionViewModel = goneToAodTransitionViewModel, goneToDozingTransitionViewModel = goneToDozingTransitionViewModel, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/rotation/RotationLockTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/rotation/RotationLockTileKosmos.kt new file mode 100644 index 000000000000..ecf8ce5bb8b8 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/rotation/RotationLockTileKosmos.kt @@ -0,0 +1,24 @@ +/* + * 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.qs.tiles.impl.rotation + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.qsEventLogger +import com.android.systemui.rotationlock.RotationLockNewModule + +val Kosmos.qsRotationLockTileConfig by + Kosmos.Fixture { RotationLockNewModule.provideRotationTileConfig(qsEventLogger) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt index 8fc419cadb21..2cdf76d50299 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt @@ -3,18 +3,18 @@ package com.android.systemui.scene import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.scene.shared.model.SceneContainerConfig -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.Scenes var Kosmos.sceneKeys by Fixture { listOf( - SceneKey.QuickSettings, - SceneKey.Shade, - SceneKey.Lockscreen, - SceneKey.Bouncer, - SceneKey.Gone, - SceneKey.Communal, + Scenes.QuickSettings, + Scenes.Shade, + Scenes.Lockscreen, + Scenes.Bouncer, + Scenes.Gone, + Scenes.Communal, ) } -val Kosmos.initialSceneKey by Fixture { SceneKey.Lockscreen } +val Kosmos.initialSceneKey by Fixture { Scenes.Lockscreen } val Kosmos.sceneContainerConfig by Fixture { SceneContainerConfig(sceneKeys, initialSceneKey) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt index c208aad78295..59a01cbedc5c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt @@ -16,6 +16,8 @@ package com.android.systemui.scene.shared.model +import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.TransitionKey import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackInteractorKosmos.kt new file mode 100644 index 000000000000..db6ba6284599 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackInteractorKosmos.kt @@ -0,0 +1,29 @@ +/* + * 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.statusbar.notification.stack.domain.interactor + +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.power.domain.interactor.powerInteractor + +val Kosmos.notificationStackInteractor by Fixture { + NotificationStackInteractor( + keyguardInteractor = keyguardInteractor, + powerInteractor = powerInteractor, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt index 25e3eac0b4b7..f1767ebb10d1 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt @@ -16,16 +16,15 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel -import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor import com.android.systemui.statusbar.notification.footer.ui.viewmodel.footerViewModel import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.notificationShelfViewModel +import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackInteractor import com.android.systemui.statusbar.policy.domain.interactor.userSetupInteractor import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor import java.util.Optional @@ -37,8 +36,7 @@ val Kosmos.notificationListViewModel by Fixture { Optional.of(footerViewModel), Optional.of(notificationListLoggerViewModel), activeNotificationsInteractor, - keyguardInteractor, - powerInteractor, + notificationStackInteractor, remoteInputInteractor, seenNotificationsInteractor, shadeInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt index d79633ae72ba..bada2a61995d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel +import com.android.systemui.dump.dumpManager import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.scene.domain.interactor.sceneInteractor @@ -24,6 +25,7 @@ import com.android.systemui.statusbar.notification.stack.domain.interactor.notif val Kosmos.notificationStackAppearanceViewModel by Fixture { NotificationStackAppearanceViewModel( + dumpManager = dumpManager, stackAppearanceInteractor = notificationStackAppearanceInteractor, shadeInteractor = shadeInteractor, sceneInteractor = sceneInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt index 832344d7a822..106e85cc8d85 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel import com.android.systemui.communal.domain.interactor.communalInteractor +import com.android.systemui.dump.dumpManager import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.ui.viewmodel.alternateBouncerToGoneTransitionViewModel @@ -48,6 +49,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.sharedNotificationContainerViewModel by Fixture { SharedNotificationContainerViewModel( interactor = sharedNotificationContainerInteractor, + dumpManager = dumpManager, applicationScope = applicationCoroutineScope, keyguardInteractor = keyguardInteractor, keyguardTransitionInteractor = keyguardTransitionInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt index 41c11ad61c7f..7a86c4f73a3f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt @@ -33,6 +33,7 @@ import com.android.systemui.settings.userTracker import com.android.systemui.shade.domain.interactor.shadeAnimationInteractor import com.android.systemui.shade.shadeController import com.android.systemui.shade.shadeViewController +import com.android.systemui.statusbar.commandQueue import com.android.systemui.statusbar.notification.collection.provider.launchFullScreenIntentProvider import com.android.systemui.statusbar.notification.collection.render.notificationVisibilityProvider import com.android.systemui.statusbar.notification.notificationTransitionAnimatorControllerProvider @@ -59,6 +60,7 @@ val Kosmos.statusBarNotificationActivityStarter by notificationVisibilityProvider, headsUpManager, activityStarter, + commandQueue, notificationClickNotifier, statusBarKeyguardViewManager, keyguardManager, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/DevicePostureControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/DevicePostureControllerKosmos.kt new file mode 100644 index 000000000000..89eaf15a52d9 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/DevicePostureControllerKosmos.kt @@ -0,0 +1,22 @@ +/* + * 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.statusbar.policy + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +val Kosmos.devicePostureController by Kosmos.Fixture { mock<DevicePostureController>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeRotationLockController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeRotationLockController.java index be57658a4266..4aa85a79c934 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeRotationLockController.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeRotationLockController.java @@ -19,13 +19,29 @@ import android.testing.LeakCheck; import com.android.systemui.statusbar.policy.RotationLockController; import com.android.systemui.statusbar.policy.RotationLockController.RotationLockControllerCallback; +import java.util.ArrayList; +import java.util.List; + public class FakeRotationLockController extends BaseLeakChecker<RotationLockControllerCallback> implements RotationLockController { + private boolean mIsLocked = false; + private final List<RotationLockControllerCallback> mCallbacks = new ArrayList<>(); public FakeRotationLockController(LeakCheck test) { super(test, "rotation"); } @Override + public void addCallback(RotationLockControllerCallback listener) { + mCallbacks.add(listener); + listener.onRotationLockStateChanged(mIsLocked, isRotationLockAffordanceVisible()); + } + + @Override + public void removeCallback(RotationLockControllerCallback listener) { + mCallbacks.remove(listener); + } + + @Override public void setListening(boolean listening) { } @@ -42,12 +58,15 @@ public class FakeRotationLockController extends BaseLeakChecker<RotationLockCont @Override public boolean isRotationLocked() { - return false; + return mIsLocked; } @Override public void setRotationLocked(boolean locked, String caller) { - + mIsLocked = locked; + for (RotationLockControllerCallback callback : mCallbacks) { + callback.onRotationLockStateChanged(locked, isRotationLockAffordanceVisible()); + } } @Override diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt index 3f20df3376d9..a3b1a0eb260d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt @@ -25,7 +25,6 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testScope import com.android.systemui.media.mediaOutputDialogFactory -import com.android.systemui.plugins.activityStarter import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -43,7 +42,7 @@ val Kosmos.localMediaRepositoryFactory: LocalMediaRepositoryFactory by Kosmos.Fixture { FakeLocalMediaRepositoryFactory { localMediaRepository } } val Kosmos.mediaOutputActionsInteractor by - Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogFactory, activityStarter) } + Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogFactory) } val Kosmos.mediaControllerRepository by Kosmos.Fixture { FakeMediaControllerRepository() } val Kosmos.mediaOutputInteractor by Kosmos.Fixture { diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java index 6ca60be25023..f31eb44f23f5 100644 --- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java +++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java @@ -39,9 +39,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; -import android.graphics.Point; import android.graphics.Rect; -import android.hardware.display.DisplayManager; import android.os.FileUtils; import android.os.ParcelFileDescriptor; import android.os.RemoteException; @@ -51,22 +49,16 @@ import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.Xml; -import android.view.Display; -import android.view.DisplayInfo; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageMonitor; -import com.android.modules.utils.TypedXmlPullParser; -import com.android.modules.utils.TypedXmlSerializer; import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; -import java.util.ArrayList; import java.util.List; /** @@ -110,9 +102,6 @@ public class WallpaperBackupAgent extends BackupAgent { @VisibleForTesting static final String WALLPAPER_INFO_STAGE = "wallpaper-info-stage"; - @VisibleForTesting - static final String WALLPAPER_BACKUP_DEVICE_INFO_STAGE = "wallpaper-backup-device-info-stage"; - static final String EMPTY_SENTINEL = "empty"; static final String QUOTA_SENTINEL = "quota"; @@ -121,11 +110,6 @@ public class WallpaperBackupAgent extends BackupAgent { static final String SYSTEM_GENERATION = "system_gen"; static final String LOCK_GENERATION = "lock_gen"; - /** - * An approximate area threshold to compare device dimension similarity - */ - static final int AREA_THRESHOLD = 50; // TODO: determine appropriate threshold - // If this file exists, it means we exceeded our quota last time private File mQuotaFile; private boolean mQuotaExceeded; @@ -137,8 +121,6 @@ public class WallpaperBackupAgent extends BackupAgent { private boolean mSystemHasLiveComponent; private boolean mLockHasLiveComponent; - private DisplayManager mDisplayManager; - @Override public void onCreate() { if (DEBUG) { @@ -155,8 +137,6 @@ public class WallpaperBackupAgent extends BackupAgent { mBackupManager = new BackupManager(getBaseContext()); mEventLogger = new WallpaperEventLogger(mBackupManager, /* wallpaperAgent */ this); - - mDisplayManager = getSystemService(DisplayManager.class); } @Override @@ -195,7 +175,6 @@ public class WallpaperBackupAgent extends BackupAgent { mSystemHasLiveComponent = mWallpaperManager.getWallpaperInfo(FLAG_SYSTEM) != null; mLockHasLiveComponent = mWallpaperManager.getWallpaperInfo(FLAG_LOCK) != null; - backupDeviceInfoFile(data); backupWallpaperInfoFile(/* sysOrLockChanged= */ sysChanged || lockChanged, data); backupSystemWallpaperFile(sharedPrefs, sysChanged, sysGeneration, data); backupLockWallpaperFileIfItExists(sharedPrefs, lockChanged, lockGeneration, data); @@ -212,50 +191,6 @@ public class WallpaperBackupAgent extends BackupAgent { } } - /** - * This method backs up the device dimension information. The device data will always get - * overwritten when triggering a backup - */ - private void backupDeviceInfoFile(FullBackupDataOutput data) - throws IOException { - final File deviceInfoStage = new File(getFilesDir(), WALLPAPER_BACKUP_DEVICE_INFO_STAGE); - - // save the dimensions of the device with xml formatting - Point dimensions = getScreenDimensions(); - Point secondaryDimensions = getRealSize(getSmallerDisplay()); - - deviceInfoStage.createNewFile(); - FileOutputStream fstream = new FileOutputStream(deviceInfoStage, false); - TypedXmlSerializer out = Xml.resolveSerializer(fstream); - out.startDocument(null, true); - out.startTag(null, "dimensions"); - - out.startTag(null, "width"); - out.text(String.valueOf(dimensions.x)); - out.endTag(null, "width"); - - out.startTag(null, "height"); - out.text(String.valueOf(dimensions.y)); - out.endTag(null, "height"); - - out.startTag(null, "secondarywidth"); - out.text(String.valueOf(secondaryDimensions != null ? secondaryDimensions.x : 0)); - out.endTag(null, "secondarywidth"); - - out.startTag(null, "secondaryheight"); - out.text(String.valueOf(secondaryDimensions != null ? secondaryDimensions.y : 0)); - out.endTag(null, "secondaryheight"); - - out.endTag(null, "dimensions"); - out.endDocument(); - fstream.flush(); - FileUtils.sync(fstream); - fstream.close(); - - if (DEBUG) Slog.v(TAG, "Storing device dimension data"); - backupFile(deviceInfoStage, data); - } - private void backupWallpaperInfoFile(boolean sysOrLockChanged, FullBackupDataOutput data) throws IOException { final ParcelFileDescriptor wallpaperInfoFd = mWallpaperManager.getWallpaperInfoFile(); @@ -429,22 +364,9 @@ public class WallpaperBackupAgent extends BackupAgent { final File infoStage = new File(filesDir, WALLPAPER_INFO_STAGE); final File imageStage = new File(filesDir, SYSTEM_WALLPAPER_STAGE); final File lockImageStage = new File(filesDir, LOCK_WALLPAPER_STAGE); - final File deviceDimensionsStage = new File(filesDir, WALLPAPER_BACKUP_DEVICE_INFO_STAGE); boolean lockImageStageExists = lockImageStage.exists(); try { - // Parse the device dimensions of the source device and compare with target to - // to identify whether we need to skip the remainder of the restore process - Pair<Point, Point> sourceDeviceDimensions = parseDeviceDimensions( - deviceDimensionsStage); - - Point targetDeviceDimensions = getScreenDimensions(); - if (sourceDeviceDimensions != null - && isSourceDeviceSignificantlySmallerThanTarget(sourceDeviceDimensions.first, - targetDeviceDimensions)) { - Slog.d(TAG, "The source device is significantly smaller than target"); - } - // First parse the live component name so that we know for logging if we care about // logging errors with the image restore. ComponentName wpService = parseWallpaperComponent(infoStage, "wp"); @@ -478,7 +400,6 @@ public class WallpaperBackupAgent extends BackupAgent { infoStage.delete(); imageStage.delete(); lockImageStage.delete(); - deviceDimensionsStage.delete(); SharedPreferences prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); prefs.edit() @@ -488,66 +409,6 @@ public class WallpaperBackupAgent extends BackupAgent { } } - /** - * This method parses the given file for the backed up device dimensions - * - * @param deviceDimensions the file which holds the device dimensions - * @return the backed up device dimensions - */ - private Pair<Point, Point> parseDeviceDimensions(File deviceDimensions) { - int width = 0, height = 0, secondaryHeight = 0, secondaryWidth = 0; - try { - TypedXmlPullParser parser = Xml.resolvePullParser( - new FileInputStream(deviceDimensions)); - - while (parser.next() != XmlPullParser.END_TAG) { - if (parser.getEventType() != XmlPullParser.START_TAG) { - continue; - } - - String name = parser.getName(); - - switch (name) { - case "width": - String widthText = readText(parser); - width = Integer.valueOf(widthText); - break; - - case "height": - String textHeight = readText(parser); - height = Integer.valueOf(textHeight); - break; - - case "secondarywidth": - String secondaryWidthText = readText(parser); - secondaryWidth = Integer.valueOf(secondaryWidthText); - break; - - case "secondaryheight": - String secondaryHeightText = readText(parser); - secondaryHeight = Integer.valueOf(secondaryHeightText); - break; - default: - break; - } - } - return new Pair<>(new Point(width, height), new Point(secondaryWidth, secondaryHeight)); - - } catch (Exception e) { - return null; - } - } - - private static String readText(TypedXmlPullParser parser) - throws IOException, XmlPullParserException { - String result = ""; - if (parser.next() == XmlPullParser.TEXT) { - result = parser.getText(); - parser.nextTag(); - } - return result; - } - @VisibleForTesting void updateWallpaperComponent(ComponentName wpService, int which) throws IOException { @@ -639,7 +500,6 @@ public class WallpaperBackupAgent extends BackupAgent { mEventLogger.onLockImageWallpaperRestoreFailed(error); } } - private Rect parseCropHint(File wallpaperInfo, String sectionTag) { Rect cropHint = new Rect(); try (FileInputStream stream = new FileInputStream(wallpaperInfo)) { @@ -677,7 +537,7 @@ public class WallpaperBackupAgent extends BackupAgent { if (type != XmlPullParser.START_TAG) continue; String tag = parser.getName(); if (!sectionTag.equals(tag)) continue; - for (Pair<Integer, String> pair : List.of( + for (Pair<Integer, String> pair: List.of( new Pair<>(WallpaperManager.PORTRAIT, "Portrait"), new Pair<>(WallpaperManager.LANDSCAPE, "Landscape"), new Pair<>(WallpaperManager.SQUARE_PORTRAIT, "SquarePortrait"), @@ -831,94 +691,6 @@ public class WallpaperBackupAgent extends BackupAgent { }; } - /** - * This method retrieves the dimensions of the largest display of the device - * - * @return a @{Point} object that contains the dimensions of the largest display on the device - */ - private Point getScreenDimensions() { - Point largetDimensions = null; - int maxArea = 0; - - for (Display display : getInternalDisplays()) { - Point displaySize = getRealSize(display); - - int width = displaySize.x; - int height = displaySize.y; - int area = width * height; - - if (area > maxArea) { - maxArea = area; - largetDimensions = displaySize; - } - } - - return largetDimensions; - } - - private Point getRealSize(Display display) { - DisplayInfo displayInfo = new DisplayInfo(); - display.getDisplayInfo(displayInfo); - return new Point(displayInfo.logicalWidth, displayInfo.logicalHeight); - } - - /** - * This method returns the smaller display on a multi-display device - * - * @return Display that corresponds to the smaller display on a device or null if ther is only - * one Display on a device - */ - private Display getSmallerDisplay() { - List<Display> internalDisplays = getInternalDisplays(); - Point largestDisplaySize = getScreenDimensions(); - - // Find the first non-matching internal display - for (Display display : internalDisplays) { - Point displaySize = getRealSize(display); - if (displaySize.x != largestDisplaySize.x || displaySize.y != largestDisplaySize.y) { - return display; - } - } - - // If no smaller display found, return null, as there is only a single display - return null; - } - - /** - * This method retrieves the collection of Display objects available in the device. - * i.e. non-external displays are ignored - * - * @return list of displays corresponding to each display in the device - */ - private List<Display> getInternalDisplays() { - Display[] allDisplays = mDisplayManager.getDisplays( - DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED); - - List<Display> internalDisplays = new ArrayList<>(); - for (Display display : allDisplays) { - if (display.getType() == Display.TYPE_INTERNAL) { - internalDisplays.add(display); - } - } - return internalDisplays; - } - - /** - * This method compares the source and target dimensions, and returns true if there is a - * significant difference in area between them and the source dimensions are smaller than the - * target dimensions. - * - * @param sourceDimensions is the dimensions of the source device - * @param targetDimensions is the dimensions of the target device - */ - @VisibleForTesting - boolean isSourceDeviceSignificantlySmallerThanTarget(Point sourceDimensions, - Point targetDimensions) { - int rawAreaDelta = (targetDimensions.x * targetDimensions.y) - - (sourceDimensions.x * sourceDimensions.y); - return rawAreaDelta > AREA_THRESHOLD; - } - @VisibleForTesting boolean isDeviceInRestore() { try { diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java index 79e7bf04dadf..3ecdf3f101a5 100644 --- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java +++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java @@ -59,7 +59,6 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.graphics.Point; import android.graphics.Rect; import android.os.FileUtils; import android.os.ParcelFileDescriptor; @@ -677,7 +676,7 @@ public class WallpaperBackupAgentTest { mWallpaperBackupAgent.onRestoreFinished(); - for (String wallpaper : List.of(WALLPAPER_IMG_LOCK, WALLPAPER_IMG_SYSTEM)) { + for (String wallpaper: List.of(WALLPAPER_IMG_LOCK, WALLPAPER_IMG_SYSTEM)) { DataTypeResult result = getLoggingResult(wallpaper, mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults()); assertThat(result).isNotNull(); @@ -841,26 +840,6 @@ public class WallpaperBackupAgentTest { testParseCropHints(testMap); } - @Test - public void test_sourceDimensionsAreLargerThanTarget() { - // source device is larger than target, expecting to get false - Point sourceDimensions = new Point(2208, 1840); - Point targetDimensions = new Point(1080, 2092); - boolean isSourceSmaller = mWallpaperBackupAgent - .isSourceDeviceSignificantlySmallerThanTarget(sourceDimensions, targetDimensions); - assertThat(isSourceSmaller).isEqualTo(false); - } - - @Test - public void test_sourceDimensionsMuchSmallerThanTarget() { - // source device is smaller than target, expecting to get true - Point sourceDimensions = new Point(1080, 2092); - Point targetDimensions = new Point(2208, 1840); - boolean isSourceSmaller = mWallpaperBackupAgent - .isSourceDeviceSignificantlySmallerThanTarget(sourceDimensions, targetDimensions); - assertThat(isSourceSmaller).isEqualTo(true); - } - private void testParseCropHints(Map<Integer, Rect> testMap) throws Exception { assumeTrue(multiCrop()); mockRestoredStaticWallpaperFile(testMap); @@ -955,7 +934,7 @@ public class WallpaperBackupAgentTest { TypedXmlSerializer out = Xml.resolveSerializer(fstream); out.startDocument(null, true); out.startTag(null, "wp"); - for (Map.Entry<Integer, Rect> entry : crops.entrySet()) { + for (Map.Entry<Integer, Rect> entry: crops.entrySet()) { String orientation = switch (entry.getKey()) { case WallpaperManager.PORTRAIT -> "Portrait"; case WallpaperManager.LANDSCAPE -> "Landscape"; diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index e535f0a05a02..178102e2876d 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -86,6 +86,21 @@ java_library { jarjar_rules: ":ravenwood-services-jarjar-rules", } +// Separated out from ravenwood-junit-impl since it needs to compile +// against `module_current` +java_library { + name: "ravenwood-junit-impl-flag", + srcs: [ + "junit-flag-src/**/*.java", + ], + sdk_version: "module_current", + libs: [ + "junit", + "flag-junit", + ], + visibility: ["//visibility:public"], +} + // Carefully compiles against only test_current to support tests that // want to verify they're unbundled. The "impl" library above is what // ships inside the Ravenwood environment to actually drive any API @@ -95,10 +110,12 @@ java_library { srcs: [ "junit-src/**/*.java", "junit-stub-src/**/*.java", + "junit-flag-src/**/*.java", ], sdk_version: "test_current", libs: [ "junit", + "flag-junit", ], visibility: ["//visibility:public"], } diff --git a/ravenwood/junit-flag-src/android/platform/test/flag/junit/RavenwoodFlagsValueProvider.java b/ravenwood/junit-flag-src/android/platform/test/flag/junit/RavenwoodFlagsValueProvider.java new file mode 100644 index 000000000000..9d6277473298 --- /dev/null +++ b/ravenwood/junit-flag-src/android/platform/test/flag/junit/RavenwoodFlagsValueProvider.java @@ -0,0 +1,54 @@ +/* + * 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.platform.test.flag.junit; + +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.IFlagsValueProvider; + +/** + * Offer to create {@link CheckFlagsRule} instances that are useful on the Ravenwood deviceless + * testing environment. + * + * At the moment, default flag values are not available on Ravenwood, so the only options offered + * here are "all-on" and "all-off" options. Tests that want to exercise specific flag states should + * use {@link android.platform.test.flag.junit.SetFlagsRule}. + */ +public class RavenwoodFlagsValueProvider { + /** + * Create a {@link CheckFlagsRule} instance where flags are in an "all-on" state. + */ + public static CheckFlagsRule createAllOnCheckFlagsRule() { + return new CheckFlagsRule(new IFlagsValueProvider() { + @Override + public boolean getBoolean(String flag) { + return true; + } + }); + } + + /** + * Create a {@link CheckFlagsRule} instance where flags are in an "all-off" state. + */ + public static CheckFlagsRule createAllOffCheckFlagsRule() { + return new CheckFlagsRule(new IFlagsValueProvider() { + @Override + public boolean getBoolean(String flag) { + return false; + } + }); + } +} diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java index cc94090d9ec7..9057d163957e 100644 --- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java +++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java @@ -42,7 +42,6 @@ public class ClassLoadHook { public static final String KEYBOARD_PATHS = "keyboard_paths"; public static final String GRAPHICS_NATIVE_CLASSES = "graphics_native_classes"; - public static final String VALUE_N_A = "**n/a**"; public static final String LIBANDROID_RUNTIME_NAME = "android_runtime"; private static String sInitialDir = new File("").getAbsolutePath(); @@ -130,8 +129,6 @@ public class ClassLoadHook { } setProperty(CORE_NATIVE_CLASSES, jniClasses); setProperty(GRAPHICS_NATIVE_CLASSES, ""); - setProperty(ICU_DATA_PATH, VALUE_N_A); - setProperty(KEYBOARD_PATHS, VALUE_N_A); RavenwoodUtils.loadJniLibrary(LIBANDROID_RUNTIME_NAME); } diff --git a/ravenwood/test-authors.md b/ravenwood/test-authors.md index 9179a621d0df..7c0cee812996 100644 --- a/ravenwood/test-authors.md +++ b/ravenwood/test-authors.md @@ -112,6 +112,24 @@ public class MyCodeTest { This naturally composes together well with any `RavenwoodRule` that your test may need. +While `SetFlagsRule` is generally a best-practice (as it can explicitly confirm behaviors for both "on" and "off" states), you may need to write tests that use `CheckFlagsRule` (such as when writing CTS). Ravenwood currently supports `CheckFlagsRule` by offering "all-on" and "all-off" behaviors: + +``` +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.flag.junit.RavenwoodFlagsValueProvider; +import android.platform.test.ravenwood.RavenwoodRule; + +@RunWith(AndroidJUnit4.class) +public class MyCodeTest { + @Rule + public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isUnderRavenwood() + ? RavenwoodFlagsValueProvider.createAllOnCheckFlagsRule() + : DeviceFlagsValueProvider.createCheckFlagsRule(); +``` + +Ravenwood currently doesn't have knowledge of the "default" value of any flags, so using `createAllOnCheckFlagsRule()` is recommended to verify the widest possible set of behaviors. The example code above falls back to using default values from `DeviceFlagsValueProvider` when not running on Ravenwood. + ## Strategies for migration/bivalent tests Ravenwood aims to support tests that are written in a “bivalent” way, where the same test code can be dual-compiled to run on both a real Android device and under a Ravenwood environment. diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java index f4ea754c21ba..279bd72da6e7 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java @@ -1004,17 +1004,19 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH && overscrollState(event, mFirstPointerDownLocation) == OVERSCROLL_VERTICAL_EDGE) { transitionToDelegatingStateAndClear(); - } // TODO(b/319537921): should there be an else here? - //Primary pointer is swiping, so transit to PanningScalingState - transitToPanningScalingStateAndClear(); + } else { + //Primary pointer is swiping, so transit to PanningScalingState + transitToPanningScalingStateAndClear(); + } } else if (mIsSinglePanningEnabled && isActivated() && event.getPointerCount() == 1) { if (overscrollState(event, mFirstPointerDownLocation) == OVERSCROLL_VERTICAL_EDGE) { transitionToDelegatingStateAndClear(); - } // TODO(b/319537921): should there be an else here? - transitToSinglePanningStateAndClear(); + } else { + transitToSinglePanningStateAndClear(); + } } else if (!mIsTwoFingerCountReached) { // If it is a two-finger gesture, do not transition to the // delegating state to ensure the reachability of @@ -1257,17 +1259,19 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH && overscrollState(event, mFirstPointerDownLocation) == OVERSCROLL_VERTICAL_EDGE) { transitionToDelegatingStateAndClear(); + } else { + //Primary pointer is swiping, so transit to PanningScalingState + transitToPanningScalingStateAndClear(); } - //Primary pointer is swiping, so transit to PanningScalingState - transitToPanningScalingStateAndClear(); } else if (mIsSinglePanningEnabled && isActivated() && event.getPointerCount() == 1) { if (overscrollState(event, mFirstPointerDownLocation) == OVERSCROLL_VERTICAL_EDGE) { transitionToDelegatingStateAndClear(); + } else { + transitToSinglePanningStateAndClear(); } - transitToSinglePanningStateAndClear(); } else { transitionToDelegatingStateAndClear(); } diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 6f45f60f6dfa..29b9d441cf38 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -476,8 +476,12 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } else { // If the package is being updated, we'll receive a PACKAGE_ADDED // shortly, otherwise it is removed permanently. - final boolean packageRemovedPermanently = (extras == null - || !extras.getBoolean(Intent.EXTRA_REPLACING, false)); + boolean isReplacing = extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, + false); + boolean isArchival = extras != null && extras.getBoolean(Intent.EXTRA_ARCHIVAL, + false); + final boolean packageRemovedPermanently = + (extras == null || !isReplacing || (isReplacing && isArchival)); if (packageRemovedPermanently) { for (String pkgName : pkgList) { @@ -2074,6 +2078,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku private void handleNotifyAppWidgetViewDataChanged(Host host, IAppWidgetHost callbacks, int appWidgetId, int viewId, long requestId) { try { + Slog.d(TAG, "Trying to notify widget view data changed"); callbacks.viewDataChanged(appWidgetId, viewId); host.lastWidgetUpdateSequenceNo = requestId; } catch (RemoteException re) { @@ -2158,6 +2163,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku private void handleNotifyUpdateAppWidget(Host host, IAppWidgetHost callbacks, int appWidgetId, RemoteViews views, long requestId) { try { + Slog.d(TAG, "Trying to notify widget update for package " + + (views == null ? "null" : views.getPackage()) + + " with widget id: " + appWidgetId); callbacks.updateAppWidget(appWidgetId, views); host.lastWidgetUpdateSequenceNo = requestId; } catch (RemoteException re) { @@ -2196,6 +2204,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku private void handleNotifyProviderChanged(Host host, IAppWidgetHost callbacks, int appWidgetId, AppWidgetProviderInfo info, long requestId) { try { + Slog.d(TAG, "Trying to notify provider update"); callbacks.providerChanged(appWidgetId, info); host.lastWidgetUpdateSequenceNo = requestId; } catch (RemoteException re) { @@ -2239,6 +2248,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku private void handleNotifyAppWidgetRemoved(Host host, IAppWidgetHost callbacks, int appWidgetId, long requestId) { try { + Slog.d(TAG, "Trying to notify widget removed"); callbacks.appWidgetRemoved(appWidgetId); host.lastWidgetUpdateSequenceNo = requestId; } catch (RemoteException re) { @@ -2286,6 +2296,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku private void handleNotifyProvidersChanged(Host host, IAppWidgetHost callbacks) { try { + Slog.d(TAG, "Trying to notify widget providers changed"); callbacks.providersChanged(); } catch (RemoteException re) { synchronized (mLock) { diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index d779fbf2eabc..551297b253d1 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -4782,7 +4782,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } if (isCredmanIntegrationActive(response)) { - Slog.d(TAG, "Attempting to add Credential Manager callback to pinned entries"); addCredentialManagerCallback(response); } @@ -5713,7 +5712,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /* isPrimary= */ true); updateFillDialogTriggerIdsLocked(); updateTrackedIdsLocked(); - if (mCurrentViewId == null) { return; } diff --git a/services/companion/java/com/android/server/companion/AssociationStore.java b/services/companion/java/com/android/server/companion/AssociationStore.java deleted file mode 100644 index 01905bb2297f..000000000000 --- a/services/companion/java/com/android/server/companion/AssociationStore.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.companion; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.UserIdInt; -import android.companion.AssociationInfo; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Collection; -import java.util.List; - -/** - * Interface for a store of {@link AssociationInfo}-s. - */ -public interface AssociationStore { - - @IntDef(prefix = { "CHANGE_TYPE_" }, value = { - CHANGE_TYPE_ADDED, - CHANGE_TYPE_REMOVED, - CHANGE_TYPE_UPDATED_ADDRESS_CHANGED, - CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED, - }) - @Retention(RetentionPolicy.SOURCE) - @interface ChangeType {} - - int CHANGE_TYPE_ADDED = 0; - int CHANGE_TYPE_REMOVED = 1; - int CHANGE_TYPE_UPDATED_ADDRESS_CHANGED = 2; - int CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED = 3; - - /** Listener for any changes to {@link AssociationInfo}-s. */ - interface OnChangeListener { - default void onAssociationChanged( - @ChangeType int changeType, AssociationInfo association) { - switch (changeType) { - case CHANGE_TYPE_ADDED: - onAssociationAdded(association); - break; - - case CHANGE_TYPE_REMOVED: - onAssociationRemoved(association); - break; - - case CHANGE_TYPE_UPDATED_ADDRESS_CHANGED: - onAssociationUpdated(association, true); - break; - - case CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED: - onAssociationUpdated(association, false); - break; - } - } - - default void onAssociationAdded(AssociationInfo association) {} - - default void onAssociationRemoved(AssociationInfo association) {} - - default void onAssociationUpdated(AssociationInfo association, boolean addressChanged) {} - } - - /** - * @return all CDM associations. - */ - @NonNull - Collection<AssociationInfo> getAssociations(); - - /** - * @return a {@link List} of associations that belong to the user. - */ - @NonNull - List<AssociationInfo> getAssociationsForUser(@UserIdInt int userId); - - /** - * @return a {@link List} of association that belong to the package. - */ - @NonNull - List<AssociationInfo> getAssociationsForPackage( - @UserIdInt int userId, @NonNull String packageName); - - /** - * @return an association with the given address that belong to the given package if such an - * association exists, otherwise {@code null}. - */ - @Nullable - AssociationInfo getAssociationsForPackageWithAddress( - @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress); - - /** - * @return an association with the given id if such an association exists, otherwise - * {@code null}. - */ - @Nullable - AssociationInfo getAssociationById(int id); - - /** - * @return all associations with the given MAc address. - */ - @NonNull - List<AssociationInfo> getAssociationsByAddress(@NonNull String macAddress); - - /** Register a {@link OnChangeListener} */ - void registerListener(@NonNull OnChangeListener listener); - - /** Un-register a previously registered {@link OnChangeListener} */ - void unregisterListener(@NonNull OnChangeListener listener); - - /** @hide */ - static String changeTypeToString(@ChangeType int changeType) { - switch (changeType) { - case CHANGE_TYPE_ADDED: - return "ASSOCIATION_ADDED"; - - case CHANGE_TYPE_REMOVED: - return "ASSOCIATION_REMOVED"; - - case CHANGE_TYPE_UPDATED_ADDRESS_CHANGED: - return "ASSOCIATION_UPDATED"; - - case CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED: - return "ASSOCIATION_UPDATED_ADDRESS_UNCHANGED"; - - default: - return "Unknown (" + changeType + ")"; - } - } -} diff --git a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java index e4cc1f8949b5..f2409fb8843e 100644 --- a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java +++ b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java @@ -34,6 +34,9 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.CollectionUtils; +import com.android.server.companion.association.AssociationDiskStore; +import com.android.server.companion.association.AssociationRequestsProcessor; +import com.android.server.companion.association.AssociationStore; import com.android.server.companion.datatransfer.SystemDataTransferRequestStore; import java.nio.ByteBuffer; @@ -54,9 +57,9 @@ class BackupRestoreProcessor { @NonNull private final PackageManagerInternal mPackageManager; @NonNull - private final AssociationStoreImpl mAssociationStore; + private final AssociationStore mAssociationStore; @NonNull - private final PersistentDataStore mPersistentStore; + private final AssociationDiskStore mPersistentStore; @NonNull private final SystemDataTransferRequestStore mSystemDataTransferRequestStore; @NonNull @@ -71,8 +74,8 @@ class BackupRestoreProcessor { new PerUserAssociationSet(); BackupRestoreProcessor(@NonNull CompanionDeviceManagerService service, - @NonNull AssociationStoreImpl associationStore, - @NonNull PersistentDataStore persistentStore, + @NonNull AssociationStore associationStore, + @NonNull AssociationDiskStore persistentStore, @NonNull SystemDataTransferRequestStore systemDataTransferRequestStore, @NonNull AssociationRequestsProcessor associationRequestsProcessor) { mService = service; diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java index 559ebbc290f6..c801489ce963 100644 --- a/services/companion/java/com/android/server/companion/CompanionApplicationController.java +++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java @@ -37,6 +37,7 @@ import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.infra.PerUser; +import com.android.server.companion.association.AssociationStore; import com.android.server.companion.presence.CompanionDevicePresenceMonitor; import com.android.server.companion.presence.ObservableUuid; import com.android.server.companion.presence.ObservableUuidStore; diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index a478a3d84161..e4a1048e9faa 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -37,7 +37,9 @@ import static android.os.UserHandle.getCallingUserId; import static com.android.internal.util.CollectionUtils.any; import static com.android.internal.util.Preconditions.checkState; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; -import static com.android.server.companion.AssociationStore.CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED; +import static com.android.server.companion.association.AssociationStore.CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED; +import static com.android.server.companion.utils.AssociationUtils.getFirstAssociationIdForUser; +import static com.android.server.companion.utils.AssociationUtils.getLastAssociationIdForUser; import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed; import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature; import static com.android.server.companion.utils.PackageUtils.getPackageInfo; @@ -117,6 +119,11 @@ import com.android.internal.util.DumpUtils; import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.companion.association.AssociationDiskStore; +import com.android.server.companion.association.AssociationRequestsProcessor; +import com.android.server.companion.association.AssociationRevokeProcessor; +import com.android.server.companion.association.AssociationStore; +import com.android.server.companion.association.InactiveAssociationsRemovalService; import com.android.server.companion.datatransfer.SystemDataTransferProcessor; import com.android.server.companion.datatransfer.SystemDataTransferRequestStore; import com.android.server.companion.datatransfer.contextsync.CrossDeviceCall; @@ -147,8 +154,6 @@ public class CompanionDeviceManagerService extends SystemService { static final String TAG = "CDM_CompanionDeviceManagerService"; static final boolean DEBUG = false; - /** Range of Association IDs allocated for a user. */ - private static final int ASSOCIATIONS_IDS_PER_USER_RANGE = 100000; private static final long PAIR_WITHOUT_PROMPT_WINDOW_MS = 10 * 60 * 1000; // 10 min private static final String PREF_FILE_NAME = "companion_device_preferences.xml"; @@ -160,10 +165,10 @@ public class CompanionDeviceManagerService extends SystemService { private static final int MAX_CN_LENGTH = 500; private final ActivityManager mActivityManager; - private PersistentDataStore mPersistentStore; + private AssociationDiskStore mAssociationDiskStore; private final PersistUserStateHandler mUserPersistenceHandler; - private final AssociationStoreImpl mAssociationStore; + private final AssociationStore mAssociationStore; private final SystemDataTransferRequestStore mSystemDataTransferRequestStore; private AssociationRequestsProcessor mAssociationRequestsProcessor; private SystemDataTransferProcessor mSystemDataTransferProcessor; @@ -178,7 +183,7 @@ public class CompanionDeviceManagerService extends SystemService { private final IAppOpsService mAppOpsManager; private final PowerWhitelistManager mPowerWhitelistManager; private final UserManager mUserManager; - final PackageManagerInternal mPackageManagerInternal; + public final PackageManagerInternal mPackageManagerInternal; private final PowerManagerInternal mPowerManagerInternal; /** @@ -210,7 +215,7 @@ public class CompanionDeviceManagerService extends SystemService { mUserManager = context.getSystemService(UserManager.class); mUserPersistenceHandler = new PersistUserStateHandler(); - mAssociationStore = new AssociationStoreImpl(); + mAssociationStore = new AssociationStore(); mSystemDataTransferRequestStore = new SystemDataTransferRequestStore(); mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class); @@ -221,15 +226,13 @@ public class CompanionDeviceManagerService extends SystemService { public void onStart() { final Context context = getContext(); - mPersistentStore = new PersistentDataStore(); + mAssociationDiskStore = new AssociationDiskStore(); mAssociationRequestsProcessor = new AssociationRequestsProcessor( /* cdmService */ this, mAssociationStore); mBackupRestoreProcessor = new BackupRestoreProcessor( - /* cdmService */ this, mAssociationStore, mPersistentStore, + /* cdmService */ this, mAssociationStore, mAssociationDiskStore, mSystemDataTransferRequestStore, mAssociationRequestsProcessor); - loadAssociationsFromDisk(); - mObservableUuidStore.getObservableUuidsForUser(getContext().getUserId()); mAssociationStore.registerListener(mAssociationStoreChangeListener); @@ -240,13 +243,18 @@ public class CompanionDeviceManagerService extends SystemService { mCompanionAppController = new CompanionApplicationController( context, mAssociationStore, mObservableUuidStore, mDevicePresenceMonitor, mPowerManagerInternal); + + mAssociationRevokeProcessor = new AssociationRevokeProcessor(this, mAssociationStore, + mPackageManagerInternal, mDevicePresenceMonitor, mCompanionAppController, + mSystemDataTransferRequestStore); + + loadAssociationsFromDisk(); + mTransportManager = new CompanionTransportManager(context, mAssociationStore); mSystemDataTransferProcessor = new SystemDataTransferProcessor(this, mPackageManagerInternal, mAssociationStore, mSystemDataTransferRequestStore, mTransportManager); - mAssociationRevokeProcessor = new AssociationRevokeProcessor(this, mAssociationStore, - mPackageManagerInternal, mDevicePresenceMonitor, mCompanionAppController, - mSystemDataTransferRequestStore); + // TODO(b/279663946): move context sync to a dedicated system service mCrossDeviceSyncController = new CrossDeviceSyncController(getContext(), mTransportManager); @@ -261,10 +269,13 @@ public class CompanionDeviceManagerService extends SystemService { void loadAssociationsFromDisk() { final Set<AssociationInfo> allAssociations = new ArraySet<>(); synchronized (mPreviouslyUsedIds) { + List<Integer> userIds = new ArrayList<>(); + for (UserInfo user : mUserManager.getAliveUsers()) { + userIds.add(user.id); + } // The data is stored in DE directories, so we can read the data for all users now // (which would not be possible if the data was stored to CE directories). - mPersistentStore.readStateForUsers( - mUserManager.getAliveUsers(), allAssociations, mPreviouslyUsedIds); + mAssociationDiskStore.readStateForUsers(userIds, allAssociations, mPreviouslyUsedIds); } final Set<AssociationInfo> activeAssociations = @@ -288,7 +299,7 @@ public class CompanionDeviceManagerService extends SystemService { } } - mAssociationStore.setAssociations(activeAssociations); + mAssociationStore.setAssociationsToCache(activeAssociations); // IMPORTANT: only do this AFTER mAssociationStore.setAssociations(), because // persistStateForUser() queries AssociationStore. @@ -579,7 +590,7 @@ public class CompanionDeviceManagerService extends SystemService { final Map<String, Set<Integer>> usedIdsForUser = getPreviouslyUsedIdsForUser(userId); - mPersistentStore.persistStateForUser(userId, allAssociations, usedIdsForUser); + mAssociationDiskStore.persistStateForUser(userId, allAssociations, usedIdsForUser); } private void notifyListeners( @@ -643,7 +654,8 @@ public class CompanionDeviceManagerService extends SystemService { final List<AssociationInfo> associationsForPackage = mAssociationStore.getAssociationsForPackage(userId, packageName); for (AssociationInfo association : associationsForPackage) { - updateSpecialAccessPermissionForAssociatedPackage(association); + updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(), + association.getPackageName()); } mCompanionAppController.onPackagesChanged(userId); @@ -689,7 +701,7 @@ public class CompanionDeviceManagerService extends SystemService { } } - class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub { + public class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub { @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { @@ -1335,7 +1347,10 @@ public class CompanionDeviceManagerService extends SystemService { return usedIdsForPackage; } - int getNewAssociationIdForPackage(@UserIdInt int userId, @NonNull String packageName) { + /** + * Get a new association id for the package. + */ + public int getNewAssociationIdForPackage(@UserIdInt int userId, @NonNull String packageName) { synchronized (mPreviouslyUsedIds) { // First: collect all IDs currently in use for this user's Associations. final SparseBooleanArray usedIds = new SparseBooleanArray(); @@ -1380,9 +1395,12 @@ public class CompanionDeviceManagerService extends SystemService { } } - void updateSpecialAccessPermissionForAssociatedPackage(AssociationInfo association) { + /** + * Update special access for the association's package + */ + public void updateSpecialAccessPermissionForAssociatedPackage(int userId, String packageName) { final PackageInfo packageInfo = - getPackageInfo(getContext(), association.getUserId(), association.getPackageName()); + getPackageInfo(getContext(), userId, packageName); Binder.withCleanCallingIdentity(() -> updateSpecialAccessPermissionAsSystem(packageInfo)); } @@ -1536,15 +1554,6 @@ public class CompanionDeviceManagerService extends SystemService { } }; - static int getFirstAssociationIdForUser(@UserIdInt int userId) { - // We want the IDs to start from 1, not 0. - return userId * ASSOCIATIONS_IDS_PER_USER_RANGE + 1; - } - - static int getLastAssociationIdForUser(@UserIdInt int userId) { - return (userId + 1) * ASSOCIATIONS_IDS_PER_USER_RANGE; - } - private static Map<String, Set<Integer>> deepUnmodifiableCopy(Map<String, Set<Integer>> orig) { final Map<String, Set<Integer>> copy = new HashMap<>(); @@ -1668,11 +1677,17 @@ public class CompanionDeviceManagerService extends SystemService { } } - void postPersistUserState(@UserIdInt int userId) { + /** + * Persist associations + */ + public void postPersistUserState(@UserIdInt int userId) { mUserPersistenceHandler.postPersistUserState(userId); } - static class PerUserAssociationSet extends PerUser<Set<AssociationInfo>> { + /** + * Set to store associations + */ + public 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 74b4cabbab67..16877dcaf183 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java @@ -32,6 +32,9 @@ import android.os.ShellCommand; import android.util.Base64; import android.util.proto.ProtoOutputStream; +import com.android.server.companion.association.AssociationRequestsProcessor; +import com.android.server.companion.association.AssociationRevokeProcessor; +import com.android.server.companion.association.AssociationStore; import com.android.server.companion.datatransfer.SystemDataTransferProcessor; import com.android.server.companion.datatransfer.contextsync.BitmapUtils; import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController; @@ -47,7 +50,7 @@ class CompanionDeviceShellCommand extends ShellCommand { private final CompanionDeviceManagerService mService; private final AssociationRevokeProcessor mRevokeProcessor; - private final AssociationStoreImpl mAssociationStore; + private final AssociationStore mAssociationStore; private final CompanionDevicePresenceMonitor mDevicePresenceMonitor; private final CompanionTransportManager mTransportManager; @@ -56,7 +59,7 @@ class CompanionDeviceShellCommand extends ShellCommand { private final BackupRestoreProcessor mBackupRestoreProcessor; CompanionDeviceShellCommand(CompanionDeviceManagerService service, - AssociationStoreImpl associationStore, + AssociationStore associationStore, CompanionDevicePresenceMonitor devicePresenceMonitor, CompanionTransportManager transportManager, SystemDataTransferProcessor systemDataTransferProcessor, diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java index 7527efb7b19a..75cb12058247 100644 --- a/services/companion/java/com/android/server/companion/PersistentDataStore.java +++ b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.companion; +package com.android.server.companion.association; import static com.android.internal.util.CollectionUtils.forEach; import static com.android.internal.util.XmlUtils.readBooleanAttribute; @@ -25,8 +25,8 @@ import static com.android.internal.util.XmlUtils.writeBooleanAttribute; import static com.android.internal.util.XmlUtils.writeIntAttribute; import static com.android.internal.util.XmlUtils.writeLongAttribute; import static com.android.internal.util.XmlUtils.writeStringAttribute; -import static com.android.server.companion.CompanionDeviceManagerService.getFirstAssociationIdForUser; -import static com.android.server.companion.CompanionDeviceManagerService.getLastAssociationIdForUser; +import static com.android.server.companion.utils.AssociationUtils.getFirstAssociationIdForUser; +import static com.android.server.companion.utils.AssociationUtils.getLastAssociationIdForUser; import static com.android.server.companion.utils.DataStoreUtils.createStorageFileForUser; import static com.android.server.companion.utils.DataStoreUtils.fileToByteArray; import static com.android.server.companion.utils.DataStoreUtils.isEndOfTag; @@ -38,12 +38,10 @@ import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.UserIdInt; import android.companion.AssociationInfo; -import android.content.pm.UserInfo; import android.net.MacAddress; import android.os.Environment; import android.util.ArrayMap; import android.util.AtomicFile; -import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.util.Xml; @@ -51,7 +49,6 @@ import android.util.Xml; import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; -import com.android.server.companion.utils.DataStoreUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -71,6 +68,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** + * IMPORTANT: This class should NOT be directly used except {@link AssociationStore} + * * The class responsible for persisting Association records and other related information (such as * previously used IDs) to a disk, and reading the data back from the disk. * @@ -107,8 +106,6 @@ import java.util.concurrent.ConcurrentMap; * Since Android T the data is stored to "companion_device_manager.xml" file in * {@link Environment#getDataSystemDeDirectory(int) /data/system_de/}. * - * See {@link DataStoreUtils#getBaseStorageFileForUser(int, String)} - * * <p> * Since Android T the data is stored using the v1 schema. * @@ -161,9 +158,8 @@ import java.util.concurrent.ConcurrentMap; * }</pre> */ @SuppressLint("LongLogTag") -final class PersistentDataStore { - private static final String TAG = "CompanionDevice_PersistentDataStore"; - private static final boolean DEBUG = CompanionDeviceManagerService.DEBUG; +public final class AssociationDiskStore { + private static final String TAG = "CompanionDevice_AssociationDiskStore"; private static final int CURRENT_PERSISTENCE_VERSION = 1; @@ -200,11 +196,13 @@ final class PersistentDataStore { private final @NonNull ConcurrentMap<Integer, AtomicFile> mUserIdToStorageFile = new ConcurrentHashMap<>(); - void readStateForUsers(@NonNull List<UserInfo> users, + /** + * Read all associations for given users + */ + public void readStateForUsers(@NonNull List<Integer> userIds, @NonNull Set<AssociationInfo> allAssociationsOut, @NonNull SparseArray<Map<String, Set<Integer>>> previouslyUsedIdsPerUserOut) { - for (UserInfo user : users) { - final int userId = user.id; + for (int userId : userIds) { // Previously used IDs are stored in the "out" collection per-user. final Map<String, Set<Integer>> previouslyUsedIds = new ArrayMap<>(); @@ -247,12 +245,11 @@ final class PersistentDataStore { * @param associationsOut a container to read the {@link AssociationInfo}s "into". * @param previouslyUsedIdsPerPackageOut a container to read the used IDs "into". */ - void readStateForUser(@UserIdInt int userId, + private void readStateForUser(@UserIdInt int userId, @NonNull Collection<AssociationInfo> associationsOut, @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) { Slog.i(TAG, "Reading associations for user " + userId + " from disk"); final AtomicFile file = getStorageFileForUser(userId); - if (DEBUG) Log.d(TAG, " > File=" + file.getBaseFile().getPath()); // getStorageFileForUser() ALWAYS returns the SAME OBJECT, which allows us to synchronize // accesses to the file on the file system using this AtomicFile object. @@ -261,12 +258,8 @@ final class PersistentDataStore { final AtomicFile readFrom; final String rootTag; if (!file.getBaseFile().exists()) { - if (DEBUG) Log.d(TAG, " > File does not exist -> Try to read legacy file"); - legacyBaseFile = getBaseLegacyStorageFileForUser(userId); - if (DEBUG) Log.d(TAG, " > Legacy file=" + legacyBaseFile.getPath()); if (!legacyBaseFile.exists()) { - if (DEBUG) Log.d(TAG, " > Legacy file does not exist -> Abort"); return; } @@ -277,27 +270,16 @@ final class PersistentDataStore { rootTag = XML_TAG_STATE; } - if (DEBUG) Log.d(TAG, " > Reading associations..."); final int version = readStateFromFileLocked(userId, readFrom, rootTag, associationsOut, previouslyUsedIdsPerPackageOut); - if (DEBUG) { - Log.d(TAG, " > Done reading: " + associationsOut); - if (version < CURRENT_PERSISTENCE_VERSION) { - Log.d(TAG, " > File used old format: v." + version + " -> Re-write"); - } - } if (legacyBaseFile != null || version < CURRENT_PERSISTENCE_VERSION) { // The data is either in the legacy file or in the legacy format, or both. // Save the data to right file in using the current format. - if (DEBUG) { - Log.d(TAG, " > Writing the data to " + file.getBaseFile().getPath()); - } persistStateToFileLocked(file, associationsOut, previouslyUsedIdsPerPackageOut); if (legacyBaseFile != null) { // We saved the data to the right file, can delete the old file now. - if (DEBUG) Log.d(TAG, " > Deleting legacy file"); legacyBaseFile.delete(); } } @@ -314,14 +296,12 @@ final class PersistentDataStore { * @param associations a set of user's associations. * @param previouslyUsedIdsPerPackage a set previously used Association IDs for the user. */ - void persistStateForUser(@UserIdInt int userId, + public void persistStateForUser(@UserIdInt int userId, @NonNull Collection<AssociationInfo> associations, @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackage) { Slog.i(TAG, "Writing associations for user " + userId + " to disk"); - if (DEBUG) Slog.d(TAG, " > " + associations); final AtomicFile file = getStorageFileForUser(userId); - if (DEBUG) Log.d(TAG, " > File=" + file.getBaseFile().getPath()); // getStorageFileForUser() ALWAYS returns the SAME OBJECT, which allows us to synchronize // accesses to the file on the file system using this AtomicFile object. synchronized (file) { @@ -404,7 +384,10 @@ final class PersistentDataStore { u -> createStorageFileForUser(userId, FILE_NAME)); } - byte[] getBackupPayload(@UserIdInt int userId) { + /** + * Get associations backup payload from disk + */ + public byte[] getBackupPayload(@UserIdInt int userId) { Slog.i(TAG, "Fetching stored state data for user " + userId + " from disk"); final AtomicFile file = getStorageFileForUser(userId); @@ -413,7 +396,10 @@ final class PersistentDataStore { } } - void readStateFromPayload(byte[] payload, @UserIdInt int userId, + /** + * Convert payload to a set of associations + */ + public void readStateFromPayload(byte[] payload, @UserIdInt int userId, @NonNull Set<AssociationInfo> associationsOut, @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) { try (ByteArrayInputStream in = new ByteArrayInputStream(payload)) { @@ -615,7 +601,7 @@ final class PersistentDataStore { macAddress, displayName, profile, null, selfManaged, notify, revoked, pending, timeApproved, lastTimeConnected, systemDataSyncFlags); } catch (Exception e) { - if (DEBUG) Log.w(TAG, "Could not create AssociationInfo", e); + Slog.e(TAG, "Could not create AssociationInfo", e); } return associationInfo; } diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java index 1dab40ea5876..29ec7c2c9743 100644 --- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java +++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.companion; +package com.android.server.companion.association; import static android.app.PendingIntent.FLAG_CANCEL_CURRENT; import static android.app.PendingIntent.FLAG_IMMUTABLE; @@ -24,7 +24,6 @@ import static android.companion.CompanionDeviceManager.RESULT_INTERNAL_ERROR; import static android.content.ComponentName.createRelative; import static android.content.pm.PackageManager.FEATURE_WATCH; -import static com.android.server.companion.CompanionDeviceManagerService.DEBUG; import static com.android.server.companion.utils.MetricUtils.logCreateAssociation; import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature; import static com.android.server.companion.utils.PermissionsUtils.enforcePermissionForCreatingAssociation; @@ -59,6 +58,7 @@ import android.os.UserHandle; import android.util.Slog; import com.android.internal.R; +import com.android.server.companion.CompanionDeviceManagerService; import com.android.server.companion.utils.PackageUtils; import java.util.List; @@ -107,7 +107,7 @@ import java.util.List; * ResultReceiver, MacAddress) */ @SuppressLint("LongLogTag") -class AssociationRequestsProcessor { +public class AssociationRequestsProcessor { private static final String TAG = "CDM_AssociationRequestsProcessor"; // AssociationRequestsProcessor <-> UI @@ -130,12 +130,12 @@ class AssociationRequestsProcessor { private final @NonNull Context mContext; private final @NonNull CompanionDeviceManagerService mService; private final @NonNull PackageManagerInternal mPackageManager; - private final @NonNull AssociationStoreImpl mAssociationStore; + private final @NonNull AssociationStore mAssociationStore; @NonNull private final ComponentName mCompanionDeviceActivity; - AssociationRequestsProcessor(@NonNull CompanionDeviceManagerService service, - @NonNull AssociationStoreImpl associationStore) { + public AssociationRequestsProcessor(@NonNull CompanionDeviceManagerService service, + @NonNull AssociationStore associationStore) { mContext = service.getContext(); mService = service; mPackageManager = service.mPackageManagerInternal; @@ -149,7 +149,7 @@ class AssociationRequestsProcessor { * Handle incoming {@link AssociationRequest}s, sent via * {@link android.companion.ICompanionDeviceManager#associate(AssociationRequest, IAssociationRequestCallback, String, int)} */ - void processNewAssociationRequest(@NonNull AssociationRequest request, + public void processNewAssociationRequest(@NonNull AssociationRequest request, @NonNull String packageName, @UserIdInt int userId, @NonNull IAssociationRequestCallback callback) { requireNonNull(request, "Request MUST NOT be null"); @@ -161,11 +161,8 @@ class AssociationRequestsProcessor { requireNonNull(callback, "Callback MUST NOT be null"); final int packageUid = mPackageManager.getPackageUid(packageName, 0, userId); - if (DEBUG) { - Slog.d(TAG, "processNewAssociationRequest() " - + "request=" + request + ", " - + "package=u" + userId + "/" + packageName + " (uid=" + packageUid + ")"); - } + Slog.d(TAG, "processNewAssociationRequest() " + "request=" + request + ", " + "package=u" + + userId + "/" + packageName + " (uid=" + packageUid + ")"); // 1. Enforce permissions and other requirements. enforcePermissionForCreatingAssociation(mContext, request, packageUid); @@ -223,7 +220,7 @@ class AssociationRequestsProcessor { /** * Process another AssociationRequest in CompanionDeviceActivity to cancel current dialog. */ - PendingIntent buildAssociationCancellationIntent(@NonNull String packageName, + public PendingIntent buildAssociationCancellationIntent(@NonNull String packageName, @UserIdInt int userId) { requireNonNull(packageName, "Package name MUST NOT be null"); @@ -248,13 +245,6 @@ class AssociationRequestsProcessor { final int userId = request.getUserId(); final int packageUid = mPackageManager.getPackageUid(packageName, 0, userId); - if (DEBUG) { - Slog.d(TAG, "processAssociationRequestApproval()\n" - + " package=u" + userId + "/" + packageName + " (uid=" + packageUid + ")\n" - + " request=" + request + "\n" - + " macAddress=" + macAddress + "\n"); - } - // 1. Need to check permissions again in case something changed, since we first received // this request. try { @@ -288,6 +278,9 @@ class AssociationRequestsProcessor { } } + /** + * Create an association. + */ public void createAssociation(@UserIdInt int userId, @NonNull String packageName, @Nullable MacAddress macAddress, @Nullable CharSequence displayName, @Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice, @@ -309,6 +302,9 @@ class AssociationRequestsProcessor { // that there are other devices with the same profile, so the role holder won't be removed. } + /** + * Grant a role if specified and add an association to store. + */ public void maybeGrantRoleAndStoreAssociation(@NonNull AssociationInfo association, @Nullable IAssociationRequestCallback callback, @Nullable ResultReceiver resultReceiver) { @@ -331,6 +327,9 @@ class AssociationRequestsProcessor { }); } + /** + * Enable system data sync. + */ public void enableSystemDataSync(int associationId, int flags) { AssociationInfo association = mAssociationStore.getAssociationById(associationId); AssociationInfo updated = (new AssociationInfo.Builder(association)) @@ -338,6 +337,9 @@ class AssociationRequestsProcessor { mAssociationStore.updateAssociation(updated); } + /** + * Disable system data sync. + */ public void disableSystemDataSync(int associationId, int flags) { AssociationInfo association = mAssociationStore.getAssociationById(associationId); AssociationInfo updated = (new AssociationInfo.Builder(association)) @@ -350,7 +352,8 @@ class AssociationRequestsProcessor { mAssociationStore.addAssociation(association); - mService.updateSpecialAccessPermissionForAssociatedPackage(association); + mService.updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(), + association.getPackageName()); logCreateAssociation(association.getDeviceProfile()); } @@ -431,38 +434,37 @@ class AssociationRequestsProcessor { private final ResultReceiver mOnRequestConfirmationReceiver = new ResultReceiver(Handler.getMain()) { - @Override - protected void onReceiveResult(int resultCode, Bundle data) { - if (DEBUG) { - Slog.d(TAG, "mOnRequestConfirmationReceiver.onReceiveResult() " - + "code=" + resultCode + ", " + "data=" + data); - } - - if (resultCode != RESULT_CODE_ASSOCIATION_APPROVED) { - Slog.w(TAG, "Unknown result code:" + resultCode); - return; - } - - final AssociationRequest request = data.getParcelable(EXTRA_ASSOCIATION_REQUEST, android.companion.AssociationRequest.class); - final IAssociationRequestCallback callback = IAssociationRequestCallback.Stub - .asInterface(data.getBinder(EXTRA_APPLICATION_CALLBACK)); - final ResultReceiver resultReceiver = data.getParcelable(EXTRA_RESULT_RECEIVER, android.os.ResultReceiver.class); - - requireNonNull(request); - requireNonNull(callback); - requireNonNull(resultReceiver); - - final MacAddress macAddress; - if (request.isSelfManaged()) { - macAddress = null; - } else { - macAddress = data.getParcelable(EXTRA_MAC_ADDRESS, android.net.MacAddress.class); - requireNonNull(macAddress); - } - - processAssociationRequestApproval(request, callback, resultReceiver, macAddress); - } - }; + @Override + protected void onReceiveResult(int resultCode, Bundle data) { + if (resultCode != RESULT_CODE_ASSOCIATION_APPROVED) { + Slog.w(TAG, "Unknown result code:" + resultCode); + return; + } + + final AssociationRequest request = data.getParcelable(EXTRA_ASSOCIATION_REQUEST, + android.companion.AssociationRequest.class); + final IAssociationRequestCallback callback = IAssociationRequestCallback.Stub + .asInterface(data.getBinder(EXTRA_APPLICATION_CALLBACK)); + final ResultReceiver resultReceiver = data.getParcelable(EXTRA_RESULT_RECEIVER, + android.os.ResultReceiver.class); + + requireNonNull(request); + requireNonNull(callback); + requireNonNull(resultReceiver); + + final MacAddress macAddress; + if (request.isSelfManaged()) { + macAddress = null; + } else { + macAddress = data.getParcelable(EXTRA_MAC_ADDRESS, + android.net.MacAddress.class); + requireNonNull(macAddress); + } + + processAssociationRequestApproval(request, callback, resultReceiver, + macAddress); + } + }; private boolean mayAssociateWithoutPrompt(@NonNull String packageName, @UserIdInt int userId) { // Throttle frequent associations diff --git a/services/companion/java/com/android/server/companion/AssociationRevokeProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRevokeProcessor.java index 10963ea37e8e..490be0da593b 100644 --- a/services/companion/java/com/android/server/companion/AssociationRevokeProcessor.java +++ b/services/companion/java/com/android/server/companion/association/AssociationRevokeProcessor.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.companion; +package com.android.server.companion.association; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE; import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION; @@ -38,6 +38,8 @@ import android.util.Log; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.server.companion.CompanionApplicationController; +import com.android.server.companion.CompanionDeviceManagerService; import com.android.server.companion.datatransfer.SystemDataTransferRequestStore; import com.android.server.companion.presence.CompanionDevicePresenceMonitor; @@ -55,7 +57,7 @@ public class AssociationRevokeProcessor { private static final boolean DEBUG = false; private final @NonNull Context mContext; private final @NonNull CompanionDeviceManagerService mService; - private final @NonNull AssociationStoreImpl mAssociationStore; + private final @NonNull AssociationStore mAssociationStore; private final @NonNull PackageManagerInternal mPackageManagerInternal; private final @NonNull CompanionDevicePresenceMonitor mDevicePresenceMonitor; private final @NonNull SystemDataTransferRequestStore mSystemDataTransferRequestStore; @@ -90,8 +92,8 @@ public class AssociationRevokeProcessor { @GuardedBy("mRevokedAssociationsPendingRoleHolderRemoval") private final Map<Integer, String> mUidsPendingRoleHolderRemoval = new HashMap<>(); - AssociationRevokeProcessor(@NonNull CompanionDeviceManagerService service, - @NonNull AssociationStoreImpl associationStore, + public AssociationRevokeProcessor(@NonNull CompanionDeviceManagerService service, + @NonNull AssociationStore associationStore, @NonNull PackageManagerInternal packageManager, @NonNull CompanionDevicePresenceMonitor devicePresenceMonitor, @NonNull CompanionApplicationController applicationController, @@ -108,8 +110,11 @@ public class AssociationRevokeProcessor { mSystemDataTransferRequestStore = systemDataTransferRequestStore; } + /** + * Disassociate an association + */ // TODO: also revoke notification access - void disassociateInternal(int associationId) { + public void disassociateInternal(int associationId) { final AssociationInfo association = mAssociationStore.getAssociationById(associationId); final int userId = association.getUserId(); final String packageName = association.getPackageName(); @@ -168,7 +173,7 @@ public class AssociationRevokeProcessor { * {@code RoleManager.removeRoleHolderAsUser()} will kill the application's process, * which would lead to the poor UX, hence need to try later. */ - boolean maybeRemoveRoleHolderForAssociation(@NonNull AssociationInfo association) { + public boolean maybeRemoveRoleHolderForAssociation(@NonNull AssociationInfo association) { if (DEBUG) Log.d(TAG, "maybeRemoveRoleHolderForAssociation() association=" + association); final String deviceProfile = association.getDeviceProfile(); @@ -208,15 +213,6 @@ public class AssociationRevokeProcessor { return true; } - @SuppressLint("MissingPermission") - private int getPackageProcessImportance(@UserIdInt int userId, @NonNull String packageName) { - return Binder.withCleanCallingIdentity(() -> { - final int uid = - mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId); - return mActivityManager.getUidImportance(uid); - }); - } - /** * Set revoked flag for active association and add the revoked association and the uid into * the caches. @@ -225,7 +221,7 @@ public class AssociationRevokeProcessor { * @see #mUidsPendingRoleHolderRemoval * @see OnPackageVisibilityChangeListener */ - void addToPendingRoleHolderRemoval(@NonNull AssociationInfo association) { + public void addToPendingRoleHolderRemoval(@NonNull AssociationInfo association) { // First: set revoked flag association = (new AssociationInfo.Builder(association)).setRevoked(true).build(); final String packageName = association.getPackageName(); @@ -247,6 +243,28 @@ public class AssociationRevokeProcessor { } /** + * @return a copy of the revoked associations set (safeguarding against + * {@code ConcurrentModificationException}-s). + */ + @NonNull + public Set<AssociationInfo> getPendingRoleHolderRemovalAssociationsForUser( + @UserIdInt int userId) { + synchronized (mRevokedAssociationsPendingRoleHolderRemoval) { + // Return a copy. + return new ArraySet<>(mRevokedAssociationsPendingRoleHolderRemoval.forUser(userId)); + } + } + + @SuppressLint("MissingPermission") + private int getPackageProcessImportance(@UserIdInt int userId, @NonNull String packageName) { + return Binder.withCleanCallingIdentity(() -> { + final int uid = + mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId); + return mActivityManager.getUidImportance(uid); + }); + } + + /** * Remove the revoked association from the cache and also remove the uid from the map if * there are other associations with the same package still pending for role holder removal. * @@ -279,18 +297,6 @@ public class AssociationRevokeProcessor { } } - /** - * @return a copy of the revoked associations set (safeguarding against - * {@code ConcurrentModificationException}-s). - */ - @NonNull Set<AssociationInfo> getPendingRoleHolderRemovalAssociationsForUser( - @UserIdInt int userId) { - synchronized (mRevokedAssociationsPendingRoleHolderRemoval) { - // Return a copy. - return new ArraySet<>(mRevokedAssociationsPendingRoleHolderRemoval.forUser(userId)); - } - } - private String getPackageNameByUid(int uid) { synchronized (mRevokedAssociationsPendingRoleHolderRemoval) { return mUidsPendingRoleHolderRemoval.get(uid); diff --git a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java b/services/companion/java/com/android/server/companion/association/AssociationStore.java index 8c6ad3bad857..2f94bdebb988 100644 --- a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java +++ b/services/companion/java/com/android/server/companion/association/AssociationStore.java @@ -14,15 +14,15 @@ * limitations under the License. */ -package com.android.server.companion; +package com.android.server.companion.association; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.UserIdInt; import android.companion.AssociationInfo; import android.net.MacAddress; -import android.util.Log; import android.util.Slog; import android.util.SparseArray; @@ -30,6 +30,8 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.util.CollectionUtils; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -40,24 +42,69 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.StringJoiner; /** - * Implementation of the {@link AssociationStore}, with addition of the methods for modification. - * <ul> - * <li> {@link #addAssociation(AssociationInfo)} - * <li> {@link #removeAssociation(int)} - * <li> {@link #updateAssociation(AssociationInfo)} - * </ul> - * - * The class has package-private access level, and instances of the class should only be created by - * the {@link CompanionDeviceManagerService}. - * Other system component (both inside and outside if the com.android.server.companion package) - * should use public {@link AssociationStore} interface. + * Association store for CRUD. */ @SuppressLint("LongLogTag") -class AssociationStoreImpl implements AssociationStore { - private static final boolean DEBUG = false; +public class AssociationStore { + + @IntDef(prefix = { "CHANGE_TYPE_" }, value = { + CHANGE_TYPE_ADDED, + CHANGE_TYPE_REMOVED, + CHANGE_TYPE_UPDATED_ADDRESS_CHANGED, + CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ChangeType {} + + public static final int CHANGE_TYPE_ADDED = 0; + public static final int CHANGE_TYPE_REMOVED = 1; + public static final int CHANGE_TYPE_UPDATED_ADDRESS_CHANGED = 2; + public static final int CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED = 3; + + /** Listener for any changes to associations. */ + public interface OnChangeListener { + /** + * Called when there are association changes. + */ + default void onAssociationChanged( + @AssociationStore.ChangeType int changeType, AssociationInfo association) { + switch (changeType) { + case CHANGE_TYPE_ADDED: + onAssociationAdded(association); + break; + + case CHANGE_TYPE_REMOVED: + onAssociationRemoved(association); + break; + + case CHANGE_TYPE_UPDATED_ADDRESS_CHANGED: + onAssociationUpdated(association, true); + break; + + case CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED: + onAssociationUpdated(association, false); + break; + } + } + + /** + * Called when an association is added. + */ + default void onAssociationAdded(AssociationInfo association) {} + + /** + * Called when an association is removed. + */ + default void onAssociationRemoved(AssociationInfo association) {} + + /** + * Called when an association is updated. + */ + default void onAssociationUpdated(AssociationInfo association, boolean addressChanged) {} + } + private static final String TAG = "CDM_AssociationStore"; private final Object mLock = new Object(); @@ -72,17 +119,17 @@ class AssociationStoreImpl implements AssociationStore { @GuardedBy("mListeners") private final Set<OnChangeListener> mListeners = new LinkedHashSet<>(); - void addAssociation(@NonNull AssociationInfo association) { + /** + * Add an association. + */ + public void addAssociation(@NonNull AssociationInfo association) { + Slog.i(TAG, "Adding new association=" + association); + // Validity check first. checkNotRevoked(association); final int id = association.getId(); - if (DEBUG) { - Log.i(TAG, "addAssociation() " + association.toShortString()); - Log.d(TAG, " association=" + association); - } - synchronized (mLock) { if (mIdMap.containsKey(id)) { Slog.e(TAG, "Association with id " + id + " already exists."); @@ -96,34 +143,34 @@ class AssociationStoreImpl implements AssociationStore { } invalidateCacheForUserLocked(association.getUserId()); + + Slog.i(TAG, "Done adding new association."); } broadcastChange(CHANGE_TYPE_ADDED, association); } - void updateAssociation(@NonNull AssociationInfo updated) { + /** + * Update an association. + */ + public void updateAssociation(@NonNull AssociationInfo updated) { + Slog.i(TAG, "Updating new association=" + updated); // Validity check first. checkNotRevoked(updated); final int id = updated.getId(); - if (DEBUG) { - Log.i(TAG, "updateAssociation() " + updated.toShortString()); - Log.d(TAG, " updated=" + updated); - } - final AssociationInfo current; final boolean macAddressChanged; synchronized (mLock) { current = mIdMap.get(id); if (current == null) { - if (DEBUG) Log.w(TAG, "Association with id " + id + " does not exist."); + Slog.w(TAG, "Can't update association. It does not exist."); return; } - if (DEBUG) Log.d(TAG, " current=" + current); if (current.equals(updated)) { - if (DEBUG) Log.w(TAG, " No changes."); + Slog.w(TAG, "Association is the same."); return; } @@ -144,6 +191,7 @@ class AssociationStoreImpl implements AssociationStore { mAddressMap.computeIfAbsent(updatedAddress, it -> new HashSet<>()).add(id); } } + Slog.i(TAG, "Done updating association."); } final int changeType = macAddressChanged ? CHANGE_TYPE_UPDATED_ADDRESS_CHANGED @@ -151,21 +199,19 @@ class AssociationStoreImpl implements AssociationStore { broadcastChange(changeType, updated); } - void removeAssociation(int id) { - if (DEBUG) Log.i(TAG, "removeAssociation() id=" + id); + /** + * Remove an association + */ + public void removeAssociation(int id) { + Slog.i(TAG, "Removing association id=" + id); final AssociationInfo association; synchronized (mLock) { association = mIdMap.remove(id); if (association == null) { - if (DEBUG) Log.w(TAG, "Association with id " + id + " is not stored."); + Slog.w(TAG, "Can't remove association. It does not exist."); return; - } else { - if (DEBUG) { - Log.i(TAG, "removed " + association.toShortString()); - Log.d(TAG, " association=" + association); - } } final MacAddress macAddress = association.getDeviceMacAddress(); @@ -174,6 +220,8 @@ class AssociationStoreImpl implements AssociationStore { } invalidateCacheForUserLocked(association.getUserId()); + + Slog.i(TAG, "Done removing association."); } broadcastChange(CHANGE_TYPE_REMOVED, association); @@ -195,12 +243,18 @@ class AssociationStoreImpl implements AssociationStore { } } + /** + * Get associations for the user. + */ public @NonNull List<AssociationInfo> getAssociationsForUser(@UserIdInt int userId) { synchronized (mLock) { return getAssociationsForUserLocked(userId); } } + /** + * Get associations for the package + */ public @NonNull List<AssociationInfo> getAssociationsForPackage( @UserIdInt int userId, @NonNull String packageName) { final List<AssociationInfo> associationsForUser = getAssociationsForUser(userId); @@ -210,6 +264,9 @@ class AssociationStoreImpl implements AssociationStore { return Collections.unmodifiableList(associationsForPackage); } + /** + * Get associations by mac address for the package. + */ public @Nullable AssociationInfo getAssociationsForPackageWithAddress( @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) { final List<AssociationInfo> associations = getAssociationsByAddress(macAddress); @@ -217,13 +274,20 @@ class AssociationStoreImpl implements AssociationStore { it -> it.belongsToPackage(userId, packageName)); } + /** + * Get association by id. + */ public @Nullable AssociationInfo getAssociationById(int id) { synchronized (mLock) { return mIdMap.get(id); } } - public @NonNull List<AssociationInfo> getAssociationsByAddress(@NonNull String macAddress) { + /** + * Get associations by mac address. + */ + @NonNull + public List<AssociationInfo> getAssociationsByAddress(@NonNull String macAddress) { final MacAddress address = MacAddress.fromString(macAddress); synchronized (mLock) { @@ -240,7 +304,8 @@ class AssociationStoreImpl implements AssociationStore { } @GuardedBy("mLock") - private @NonNull List<AssociationInfo> getAssociationsForUserLocked(@UserIdInt int userId) { + @NonNull + private List<AssociationInfo> getAssociationsForUserLocked(@UserIdInt int userId) { final List<AssociationInfo> cached = mCachedPerUser.get(userId); if (cached != null) { return cached; @@ -262,12 +327,18 @@ class AssociationStoreImpl implements AssociationStore { mCachedPerUser.delete(userId); } + /** + * Register a listener for association changes. + */ public void registerListener(@NonNull OnChangeListener listener) { synchronized (mListeners) { mListeners.add(listener); } } + /** + * Unregister a listener previously registered for association changes. + */ public void unregisterListener(@NonNull OnChangeListener listener) { synchronized (mListeners) { mListeners.remove(listener); @@ -297,43 +368,30 @@ class AssociationStoreImpl implements AssociationStore { } } - void setAssociations(Collection<AssociationInfo> allAssociations) { + /** + * Set associations to cache. It will clear the existing cache. + */ + public void setAssociationsToCache(Collection<AssociationInfo> associations) { // Validity check first. - allAssociations.forEach(AssociationStoreImpl::checkNotRevoked); + associations.forEach(AssociationStore::checkNotRevoked); - if (DEBUG) { - Log.i(TAG, "setAssociations() n=" + allAssociations.size()); - final StringJoiner stringJoiner = new StringJoiner(", "); - allAssociations.forEach(assoc -> stringJoiner.add(assoc.toShortString())); - Log.v(TAG, " associations=" + stringJoiner); - } synchronized (mLock) { - setAssociationsLocked(allAssociations); - } - } - - @GuardedBy("mLock") - private void setAssociationsLocked(Collection<AssociationInfo> associations) { - clearLocked(); + mIdMap.clear(); + mAddressMap.clear(); + mCachedPerUser.clear(); - for (AssociationInfo association : associations) { - final int id = association.getId(); - mIdMap.put(id, association); + for (AssociationInfo association : associations) { + final int id = association.getId(); + mIdMap.put(id, association); - final MacAddress address = association.getDeviceMacAddress(); - if (address != null) { - mAddressMap.computeIfAbsent(address, it -> new HashSet<>()).add(id); + final MacAddress address = association.getDeviceMacAddress(); + if (address != null) { + mAddressMap.computeIfAbsent(address, it -> new HashSet<>()).add(id); + } } } } - @GuardedBy("mLock") - private void clearLocked() { - mIdMap.clear(); - mAddressMap.clear(); - mCachedPerUser.clear(); - } - private static void checkNotRevoked(@NonNull AssociationInfo association) { if (association.isRevoked()) { throw new IllegalArgumentException( diff --git a/services/companion/java/com/android/server/companion/InactiveAssociationsRemovalService.java b/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java index aac628cab403..894c49a2b5cf 100644 --- a/services/companion/java/com/android/server/companion/InactiveAssociationsRemovalService.java +++ b/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java @@ -14,9 +14,7 @@ * limitations under the License. */ -package com.android.server.companion; - -import static com.android.server.companion.CompanionDeviceManagerService.TAG; +package com.android.server.companion.association; import static java.util.concurrent.TimeUnit.DAYS; @@ -29,13 +27,17 @@ import android.content.Context; import android.util.Slog; import com.android.server.LocalServices; +import com.android.server.companion.CompanionDeviceManagerServiceInternal; /** - * A Job Service responsible for clean up the Association. + * A Job Service responsible for clean up idle self-managed associations. + * * The job will be executed only if the device is charging and in idle mode due to the application - * will be killed if association/role are revoked. + * will be killed if association/role are revoked. See {@link AssociationRevokeProcessor} */ public class InactiveAssociationsRemovalService extends JobService { + + private static final String TAG = "CDM_InactiveAssociationsRemovalService"; private static final String JOB_NAMESPACE = "companion"; private static final int JOB_ID = 1; private static final long ONE_DAY_INTERVAL = DAYS.toMillis(1); @@ -60,7 +62,10 @@ public class InactiveAssociationsRemovalService extends JobService { return false; } - static void schedule(Context context) { + /** + * Schedule this job. + */ + public static void schedule(Context context) { Slog.i(TAG, "Scheduling the Association Removal job"); final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class).forNamespace(JOB_NAMESPACE); diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java index 74236a402244..a08e0da90d49 100644 --- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java +++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java @@ -52,8 +52,8 @@ import android.permission.PermissionControllerManager; import android.util.Slog; import com.android.internal.R; -import com.android.server.companion.AssociationStore; import com.android.server.companion.CompanionDeviceManagerService; +import com.android.server.companion.association.AssociationStore; import com.android.server.companion.transport.CompanionTransportManager; import com.android.server.companion.utils.PackageUtils; import com.android.server.companion.utils.PermissionsUtils; diff --git a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java index 2899c055afbd..99466a966647 100644 --- a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java +++ b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java @@ -59,8 +59,8 @@ import android.os.Looper; import android.util.Log; import android.util.Slog; -import com.android.server.companion.AssociationStore; -import com.android.server.companion.AssociationStore.ChangeType; +import com.android.server.companion.association.AssociationStore; +import com.android.server.companion.association.AssociationStore.ChangeType; import java.util.ArrayList; import java.util.Arrays; diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java index 0287f6258c06..4da3f9bead4e 100644 --- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java +++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java @@ -39,7 +39,7 @@ import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ArrayUtils; -import com.android.server.companion.AssociationStore; +import com.android.server.companion.association.AssociationStore; import java.util.Arrays; import java.util.Collections; diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java index 3da9693b75c9..37bbb937d1b5 100644 --- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java +++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java @@ -44,7 +44,7 @@ import android.util.SparseArray; import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; -import com.android.server.companion.AssociationStore; +import com.android.server.companion.association.AssociationStore; import java.io.PrintWriter; import java.util.HashSet; diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java index 3861f99eb03c..6dd14ac91a34 100644 --- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java +++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java @@ -32,7 +32,7 @@ import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; -import com.android.server.companion.AssociationStore; +import com.android.server.companion.association.AssociationStore; import java.io.FileDescriptor; import java.io.IOException; diff --git a/services/companion/java/com/android/server/companion/utils/AssociationUtils.java b/services/companion/java/com/android/server/companion/utils/AssociationUtils.java new file mode 100644 index 000000000000..e4d96413cf8b --- /dev/null +++ b/services/companion/java/com/android/server/companion/utils/AssociationUtils.java @@ -0,0 +1,42 @@ +/* + * 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.utils; + +import android.annotation.UserIdInt; + +public final class AssociationUtils { + + /** Range of Association IDs allocated for a user. */ + private static final int ASSOCIATIONS_IDS_PER_USER_RANGE = 100000; + + /** + * Get the left boundary of the association id range for the user. + */ + public static int getFirstAssociationIdForUser(@UserIdInt int userId) { + // We want the IDs to start from 1, not 0. + return userId * ASSOCIATIONS_IDS_PER_USER_RANGE + 1; + } + + /** + * Get the right boundary of the association id range for the user. + */ + public static int getLastAssociationIdForUser(@UserIdInt int userId) { + return (userId + 1) * ASSOCIATIONS_IDS_PER_USER_RANGE; + } + + private AssociationUtils() {} +} diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java index 9189ea763577..1a3ef73ca2a8 100644 --- a/services/core/java/com/android/server/SystemConfig.java +++ b/services/core/java/com/android/server/SystemConfig.java @@ -264,8 +264,8 @@ public class SystemConfig { final ArrayMap<String, ArraySet<String>> mAllowIgnoreLocationSettings = new ArrayMap<>(); // These are the packages that are allow-listed to be able to access camera when - // the camera privacy state is for driver assistance apps only. - final ArrayMap<String, Boolean> mAllowlistCameraPrivacy = new ArrayMap<>(); + // the camera privacy state is enabled. + final ArraySet<String> mAllowlistCameraPrivacy = new ArraySet<>(); // These are the action strings of broadcasts which are whitelisted to // be delivered anonymously even to apps which target O+. @@ -348,6 +348,9 @@ public class SystemConfig { // marked as stopped by the system @NonNull private final Set<String> mInitialNonStoppedSystemPackages = new ArraySet<>(); + // Which packages (key) are allowed to join particular SharedUid (value). + @NonNull private final Map<String, String> mPackageToSharedUidAllowList = new ArrayMap<>(); + // A map of preloaded package names and the path to its app metadata file path. private final ArrayMap<String, String> mAppMetadataFilePaths = new ArrayMap<>(); @@ -486,7 +489,7 @@ public class SystemConfig { return mAllowedAssociations; } - public ArrayMap<String, Boolean> getCameraPrivacyAllowlist() { + public ArraySet<String> getCameraPrivacyAllowlist() { return mAllowlistCameraPrivacy; } @@ -567,6 +570,11 @@ public class SystemConfig { return mInitialNonStoppedSystemPackages; } + @NonNull + public Map<String, String> getPackageToSharedUidAllowList() { + return mPackageToSharedUidAllowList; + } + public ArrayMap<String, String> getAppMetadataFilePaths() { return mAppMetadataFilePaths; } @@ -1068,13 +1076,11 @@ public class SystemConfig { case "camera-privacy-allowlisted-app" : { if (allowOverrideAppRestrictions) { String pkgname = parser.getAttributeValue(null, "package"); - boolean isMandatory = XmlUtils.readBooleanAttribute( - parser, "mandatory", false); if (pkgname == null) { Slog.w(TAG, "<" + name + "> without package in " + permFile + " at " + parser.getPositionDescription()); } else { - mAllowlistCameraPrivacy.put(pkgname, isMandatory); + mAllowlistCameraPrivacy.add(pkgname); } } else { logNotAllowedInPartition(name, permFile, parser); @@ -1563,6 +1569,19 @@ public class SystemConfig { mInitialNonStoppedSystemPackages.add(pkgName); } } break; + case "allow-package-shareduid": { + String pkgName = parser.getAttributeValue(null, "package"); + String sharedUid = parser.getAttributeValue(null, "shareduid"); + if (TextUtils.isEmpty(pkgName)) { + Slog.w(TAG, "<" + name + "> without package in " + permFile + + " at " + parser.getPositionDescription()); + } else if (TextUtils.isEmpty(sharedUid)) { + Slog.w(TAG, "<" + name + "> without shareduid in " + permFile + + " at " + parser.getPositionDescription()); + } else { + mPackageToSharedUidAllowList.put(pkgName, sharedUid); + } + } case "asl-file": { String packageName = parser.getAttributeValue(null, "package"); String path = parser.getAttributeValue(null, "path"); diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING index 5e9d1cbb32ca..8dc15ade532e 100644 --- a/services/core/java/com/android/server/TEST_MAPPING +++ b/services/core/java/com/android/server/TEST_MAPPING @@ -163,6 +163,9 @@ } ], "file_patterns": ["PinnerService\\.java"] + }, + { + "name": "FrameworksVpnTests" } ] } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index b8e09cce93b9..258f53d982d2 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -372,6 +372,15 @@ public final class ActiveServices { @Overridable public static final long FGS_BOOT_COMPLETED_RESTRICTIONS = 296558535L; + /** + * Disables foreground service background starts in System Alert Window for all types + * unless it already has a System Overlay Window. + */ + @ChangeId + @EnabledSince(targetSdkVersion = VERSION_CODES.VANILLA_ICE_CREAM) + @Overridable + public static final long FGS_SAW_RESTRICTIONS = 319471980L; + final ActivityManagerService mAm; // Maximum number of services that we allow to start in the background @@ -8526,10 +8535,31 @@ public final class ActiveServices { } } + // The flag being enabled isn't enough to deny background start: we need to also check + // if there is a system alert UI present. if (ret == REASON_DENIED) { - if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid, - callingPackage)) { - ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION; + // Flag check: are we disabling SAW FGS background starts? + final boolean shouldDisableSaw = Flags.fgsDisableSaw() + && CompatChanges.isChangeEnabled(FGS_BOOT_COMPLETED_RESTRICTIONS, callingUid); + if (shouldDisableSaw) { + final ProcessRecord processRecord = mAm + .getProcessRecordLocked(targetService.processName, + targetService.appInfo.uid); + if (processRecord != null) { + if (processRecord.mState.hasOverlayUi()) { + if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid, + callingPackage)) { + ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION; + } + } + } else { + Slog.e(TAG, "Could not find process record for SAW check"); + } + } else { + if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid, + callingPackage)) { + ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION; + } } } diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index d1c8c303524b..4a3791396828 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -276,6 +276,11 @@ class BroadcastProcessQueue { && record.getDeliveryGroupPolicy() == BroadcastOptions.DELIVERY_GROUP_POLICY_ALL) { final BroadcastRecord replacedBroadcastRecord = replaceBroadcast(record, recordIndex); if (replacedBroadcastRecord != null) { + if (mLastDeferredStates && shouldBeDeferred() + && (record.getDeliveryState(recordIndex) + == BroadcastRecord.DELIVERY_PENDING)) { + deferredStatesApplyConsumer.accept(record, recordIndex); + } return replacedBroadcastRecord; } } diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig index c06bdf90a75a..b1823b4ee38f 100644 --- a/services/core/java/com/android/server/am/flags.aconfig +++ b/services/core/java/com/android/server/am/flags.aconfig @@ -23,6 +23,13 @@ flag { } flag { + name: "fgs_disable_saw" + namespace: "backstage_power" + description: "Disable System Alert Window FGS start" + bug: "296558535" +} + +flag { name: "bfgs_managed_network_access" namespace: "backstage_power" description: "Restrict network access for certain applications in BFGS process state" diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 19dd7b7ea2f6..d4f04b5ad760 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -3713,7 +3713,7 @@ public class AudioService extends IAudioService.Stub && AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device) && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { if (DEBUG_VOL) { - Log.d(TAG, "adjustSreamVolume: postSetAvrcpAbsoluteVolumeIndex index=" + Log.d(TAG, "adjustStreamVolume: postSetAvrcpAbsoluteVolumeIndex index=" + newIndex + "stream=" + streamType); } mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(newIndex / 10); @@ -3727,7 +3727,7 @@ public class AudioService extends IAudioService.Stub && streamType == getBluetoothContextualVolumeStream() && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { if (DEBUG_VOL) { - Log.d(TAG, "adjustSreamVolume postSetLeAudioVolumeIndex index=" + Log.d(TAG, "adjustStreamVolume postSetLeAudioVolumeIndex index=" + newIndex + " stream=" + streamType); } mDeviceBroker.postSetLeAudioVolumeIndex(newIndex, @@ -3740,7 +3740,7 @@ public class AudioService extends IAudioService.Stub // the one expected by the hearing aid if (streamType == getBluetoothContextualVolumeStream()) { if (DEBUG_VOL) { - Log.d(TAG, "adjustSreamVolume postSetHearingAidVolumeIndex index=" + Log.d(TAG, "adjustStreamVolume postSetHearingAidVolumeIndex index=" + newIndex + " stream=" + streamType); } mDeviceBroker.postSetHearingAidVolumeIndex(newIndex, streamType); @@ -4722,7 +4722,7 @@ public class AudioService extends IAudioService.Stub && streamType == getBluetoothContextualVolumeStream() && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { if (DEBUG_VOL) { - Log.d(TAG, "adjustSreamVolume postSetLeAudioVolumeIndex index=" + Log.d(TAG, "setStreamVolume postSetLeAudioVolumeIndex index=" + index + " stream=" + streamType); } mDeviceBroker.postSetLeAudioVolumeIndex(index, mStreamStates[streamType].getMaxIndex(), diff --git a/services/core/java/com/android/server/biometrics/log/ALSProbe.java b/services/core/java/com/android/server/biometrics/log/ALSProbe.java index d584c99cea72..d4e46a930124 100644 --- a/services/core/java/com/android/server/biometrics/log/ALSProbe.java +++ b/services/core/java/com/android/server/biometrics/log/ALSProbe.java @@ -179,15 +179,18 @@ final class ALSProbe implements Probe { nextConsumer.consume(current); } else if (mNextConsumer != null) { mNextConsumer.add(nextConsumer); - } else { + } else if (mLightSensor != null) { mDestroyed = false; mNextConsumer = nextConsumer; enableLightSensorLoggingLocked(); + } else { + Slog.w(TAG, "No light sensor - use current to consume"); + nextConsumer.consume(current); } } private void enableLightSensorLoggingLocked() { - if (!mEnabled) { + if (!mEnabled && mLightSensor != null) { mEnabled = true; mLastAmbientLux = -1; mSensorManager.registerListener(mLightSensorListener, mLightSensor, @@ -201,7 +204,7 @@ final class ALSProbe implements Probe { private void disableLightSensorLoggingLocked(boolean destroying) { resetTimerLocked(false /* start */); - if (mEnabled) { + if (mEnabled && mLightSensor != null) { mEnabled = false; if (!destroying) { mLastAmbientLux = -1; diff --git a/services/core/java/com/android/server/connectivity/TEST_MAPPING b/services/core/java/com/android/server/connectivity/TEST_MAPPING index 687d4b06b4c0..f50831964303 100644 --- a/services/core/java/com/android/server/connectivity/TEST_MAPPING +++ b/services/core/java/com/android/server/connectivity/TEST_MAPPING @@ -7,7 +7,7 @@ "exclude-annotation": "com.android.testutils.SkipPresubmit" } ], - "file_patterns": ["Vpn\\.java", "VpnIkeV2Utils\\.java", "VpnProfileStore\\.java"] + "file_patterns": ["VpnIkeV2Utils\\.java", "VpnProfileStore\\.java"] } ], "presubmit-large": [ @@ -26,5 +26,10 @@ ], "file_patterns": ["Vpn\\.java", "VpnIkeV2Utils\\.java", "VpnProfileStore\\.java"] } + ], + "postsubmit":[ + { + "name":"FrameworksVpnTests" + } ] }
\ No newline at end of file diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index fb4976d3cef2..b2a738fd352b 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -463,12 +463,6 @@ public class AutomaticBrightnessController { boolean userChangedAutoBrightnessAdjustment, int displayPolicy, boolean shouldResetShortTermModel) { mState = state; - // While dozing, the application processor may be suspended which will prevent us from - // receiving new information from the light sensor. On some devices, we may be able to - // switch to a wake-up light sensor instead but for now we will simply disable the sensor - // and hold onto the last computed screen auto brightness. We save the dozing flag for - // debugging purposes. - boolean dozing = (displayPolicy == DisplayPowerRequest.POLICY_DOZE); boolean changed = setBrightnessConfiguration(configuration, shouldResetShortTermModel); changed |= setDisplayPolicy(displayPolicy); if (userChangedAutoBrightnessAdjustment) { @@ -482,10 +476,10 @@ public class AutomaticBrightnessController { } final boolean userInitiatedChange = userChangedBrightness || userChangedAutoBrightnessAdjustment; - if (userInitiatedChange && enable && !dozing) { + if (userInitiatedChange && enable) { prepareBrightnessAdjustmentSample(); } - changed |= setLightSensorEnabled(enable && !dozing); + changed |= setLightSensorEnabled(enable); if (mIsBrightnessThrottled != mBrightnessThrottler.isThrottled()) { // Maximum brightness has changed, so recalculate display brightness. diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 76f303596bdb..2010aca72494 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -1485,8 +1485,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } // Use default brightness when dozing unless overridden. - if ((Float.isNaN(brightnessState)) - && Display.isDozeState(state)) { + if (Float.isNaN(brightnessState) && mPowerRequest.policy == POLICY_DOZE) { rawBrightnessState = mScreenBrightnessDozeConfig; brightnessState = clampScreenBrightness(rawBrightnessState); mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_DEFAULT); diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java index babc36efcca8..8e844501ab34 100644 --- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java +++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java @@ -200,12 +200,9 @@ public class DisplayBrightnessStrategySelector { // We are not checking the targetDisplayState, but rather relying on the policy because // a user can define a different display state(displayPowerRequest.dozeScreenState) too // in the request with the Doze policy - if (displayPowerRequest.policy == DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE) { - if (!mAllowAutoBrightnessWhileDozingConfig) { - return true; - } - } - return false; + return displayPowerRequest.policy == DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE + && !mAllowAutoBrightnessWhileDozingConfig + && BrightnessUtils.isValidBrightnessValue(displayPowerRequest.dozeScreenBrightness); } @VisibleForTesting diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java index 8eaecef6e562..d1ca49b8bf79 100644 --- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java +++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java @@ -15,6 +15,8 @@ */ package com.android.server.display.brightness.strategy; +import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE; + import android.annotation.Nullable; import android.content.Context; import android.hardware.display.BrightnessConfiguration; @@ -102,8 +104,7 @@ public class AutomaticBrightnessStrategy { boolean allowAutoBrightnessWhileDozingConfig, int brightnessReason, int policy, float lastUserSetScreenBrightness, boolean userSetBrightnessChanged) { final boolean autoBrightnessEnabledInDoze = - allowAutoBrightnessWhileDozingConfig - && Display.isDozeState(targetDisplayState); + allowAutoBrightnessWhileDozingConfig && policy == POLICY_DOZE; mIsAutoBrightnessEnabled = shouldUseAutoBrightness() && (targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze) && brightnessReason != BrightnessReason.REASON_OVERRIDE diff --git a/services/core/java/com/android/server/flags/services.aconfig b/services/core/java/com/android/server/flags/services.aconfig new file mode 100644 index 000000000000..10b5eff06e0c --- /dev/null +++ b/services/core/java/com/android/server/flags/services.aconfig @@ -0,0 +1,8 @@ +package: "com.android.server.flags" + +flag { + namespace: "wear_frameworks" + name: "enable_odp_feature_guard" + description: "Enable guard based on system feature to prevent OnDevicePersonalization service from starting on form factors." + bug: "322249125" +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index a79e7715f064..05b1cb69235b 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -54,6 +54,7 @@ import android.hardware.input.InputManager; import android.hardware.input.InputSensorInfo; import android.hardware.input.InputSettings; import android.hardware.input.KeyboardLayout; +import android.hardware.input.KeyboardLayoutSelectionResult; import android.hardware.input.TouchCalibration; import android.hardware.lights.Light; import android.hardware.lights.LightState; @@ -1244,9 +1245,9 @@ public class InputManagerService extends IInputManager.Stub } @Override // Binder call - public String getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, - @UserIdInt int userId, @NonNull InputMethodInfo imeInfo, - @Nullable InputMethodSubtype imeSubtype) { + public KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice( + InputDeviceIdentifier identifier, @UserIdInt int userId, + @NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype) { return mKeyboardLayoutManager.getKeyboardLayoutForInputDevice(identifier, userId, imeInfo, imeSubtype); } diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java index 46668de042d4..283e692ffbab 100644 --- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java +++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java @@ -16,10 +16,11 @@ package com.android.server.input; -import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEFAULT; -import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE; -import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER; -import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD; +import static android.hardware.input.KeyboardLayoutSelectionResult.FAILED; +import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_USER; +import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE; +import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD; +import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT; import android.annotation.AnyThread; import android.annotation.MainThread; @@ -46,6 +47,7 @@ import android.content.res.XmlResourceParser; import android.hardware.input.InputDeviceIdentifier; import android.hardware.input.InputManager; import android.hardware.input.KeyboardLayout; +import android.hardware.input.KeyboardLayoutSelectionResult; import android.icu.lang.UScript; import android.icu.util.ULocale; import android.os.Bundle; @@ -79,7 +81,6 @@ import com.android.internal.util.XmlUtils; import com.android.server.LocalServices; import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import com.android.server.input.KeyboardMetricsCollector.KeyboardConfigurationEvent; -import com.android.server.input.KeyboardMetricsCollector.LayoutSelectionCriteria; import com.android.server.inputmethod.InputMethodManagerInternal; import libcore.io.Streams; @@ -130,7 +131,8 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { // This cache stores "best-matched" layouts so that we don't need to run the matching // algorithm repeatedly. @GuardedBy("mKeyboardLayoutCache") - private final Map<String, KeyboardLayoutInfo> mKeyboardLayoutCache = new ArrayMap<>(); + private final Map<String, KeyboardLayoutSelectionResult> mKeyboardLayoutCache = + new ArrayMap<>(); private final Object mImeInfoLock = new Object(); @Nullable @GuardedBy("mImeInfoLock") @@ -222,17 +224,17 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { } else { Set<String> selectedLayouts = new HashSet<>(); List<ImeInfo> imeInfoList = getImeInfoListForLayoutMapping(); - List<KeyboardLayoutInfo> layoutInfoList = new ArrayList<>(); + List<KeyboardLayoutSelectionResult> resultList = new ArrayList<>(); boolean hasMissingLayout = false; for (ImeInfo imeInfo : imeInfoList) { // Check if the layout has been previously configured - KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal( + KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal( keyboardIdentifier, imeInfo); - boolean noLayoutFound = layoutInfo == null || layoutInfo.mDescriptor == null; + boolean noLayoutFound = result.getLayoutDescriptor() == null; if (!noLayoutFound) { - selectedLayouts.add(layoutInfo.mDescriptor); + selectedLayouts.add(result.getLayoutDescriptor()); } - layoutInfoList.add(layoutInfo); + resultList.add(result); hasMissingLayout |= noLayoutFound; } @@ -260,7 +262,7 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { } if (shouldLogConfiguration) { - logKeyboardConfigurationEvent(inputDevice, imeInfoList, layoutInfoList, + logKeyboardConfigurationEvent(inputDevice, imeInfoList, resultList, !mDataStore.hasInputDeviceEntry(key)); } } finally { @@ -757,10 +759,10 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { String keyboardLayoutDescriptor; if (useNewSettingsUi()) { synchronized (mImeInfoLock) { - KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal( + KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal( new KeyboardIdentifier(identifier, languageTag, layoutType), mCurrentImeInfo); - keyboardLayoutDescriptor = layoutInfo == null ? null : layoutInfo.mDescriptor; + keyboardLayoutDescriptor = result.getLayoutDescriptor(); } } else { keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(identifier); @@ -788,26 +790,26 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { } @AnyThread - @Nullable - public String getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, - @UserIdInt int userId, @NonNull InputMethodInfo imeInfo, - @Nullable InputMethodSubtype imeSubtype) { + @NonNull + public KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice( + InputDeviceIdentifier identifier, @UserIdInt int userId, + @NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype) { if (!useNewSettingsUi()) { Slog.e(TAG, "getKeyboardLayoutForInputDevice() API not supported"); - return null; + return FAILED; } InputDevice inputDevice = getInputDevice(identifier); if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) { - return null; + return FAILED; } KeyboardIdentifier keyboardIdentifier = new KeyboardIdentifier(inputDevice); - KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal( + KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal( keyboardIdentifier, new ImeInfo(userId, imeInfo, imeSubtype)); if (DEBUG) { Slog.d(TAG, "getKeyboardLayoutForInputDevice() " + identifier.toString() + ", userId : " - + userId + ", subtype = " + imeSubtype + " -> " + layoutInfo); + + userId + ", subtype = " + imeSubtype + " -> " + result); } - return layoutInfo != null ? layoutInfo.mDescriptor : null; + return result; } @AnyThread @@ -942,13 +944,13 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { } @Nullable - private KeyboardLayoutInfo getKeyboardLayoutForInputDeviceInternal( + private KeyboardLayoutSelectionResult getKeyboardLayoutForInputDeviceInternal( KeyboardIdentifier keyboardIdentifier, @Nullable ImeInfo imeInfo) { String layoutKey = new LayoutKey(keyboardIdentifier, imeInfo).toString(); synchronized (mDataStore) { String layout = mDataStore.getKeyboardLayout(keyboardIdentifier.toString(), layoutKey); if (layout != null) { - return new KeyboardLayoutInfo(layout, LAYOUT_SELECTION_CRITERIA_USER); + return new KeyboardLayoutSelectionResult(layout, LAYOUT_SELECTION_CRITERIA_USER); } } @@ -961,17 +963,17 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { KeyboardLayout[] layoutList = getKeyboardLayoutListForInputDeviceInternal( keyboardIdentifier, imeInfo); // Call auto-matching algorithm to find the best matching layout - KeyboardLayoutInfo layoutInfo = + KeyboardLayoutSelectionResult result = getDefaultKeyboardLayoutBasedOnImeInfo(keyboardIdentifier, imeInfo, layoutList); - mKeyboardLayoutCache.put(layoutKey, layoutInfo); - return layoutInfo; + mKeyboardLayoutCache.put(layoutKey, result); + return result; } } } - @Nullable - private static KeyboardLayoutInfo getDefaultKeyboardLayoutBasedOnImeInfo( + @NonNull + private static KeyboardLayoutSelectionResult getDefaultKeyboardLayoutBasedOnImeInfo( KeyboardIdentifier keyboardIdentifier, @Nullable ImeInfo imeInfo, KeyboardLayout[] layoutList) { Arrays.sort(layoutList); @@ -986,7 +988,7 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { + "vendor and product Ids. " + keyboardIdentifier + " : " + layout.getDescriptor()); } - return new KeyboardLayoutInfo(layout.getDescriptor(), + return new KeyboardLayoutSelectionResult(layout.getDescriptor(), LAYOUT_SELECTION_CRITERIA_DEVICE); } } @@ -1004,13 +1006,14 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { + "HW information (Language tag and Layout type). " + keyboardIdentifier + " : " + layoutDesc); } - return new KeyboardLayoutInfo(layoutDesc, LAYOUT_SELECTION_CRITERIA_DEVICE); + return new KeyboardLayoutSelectionResult(layoutDesc, + LAYOUT_SELECTION_CRITERIA_DEVICE); } } if (imeInfo == null || imeInfo.mImeSubtypeHandle == null || imeInfo.mImeSubtype == null) { // Can't auto select layout based on IME info is null - return null; + return FAILED; } InputMethodSubtype subtype = imeInfo.mImeSubtype; @@ -1027,9 +1030,10 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { + layoutDesc); } if (layoutDesc != null) { - return new KeyboardLayoutInfo(layoutDesc, LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD); + return new KeyboardLayoutSelectionResult(layoutDesc, + LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD); } - return null; + return FAILED; } @Nullable @@ -1246,21 +1250,23 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { } private void logKeyboardConfigurationEvent(@NonNull InputDevice inputDevice, - @NonNull List<ImeInfo> imeInfoList, @NonNull List<KeyboardLayoutInfo> layoutInfoList, + @NonNull List<ImeInfo> imeInfoList, + @NonNull List<KeyboardLayoutSelectionResult> resultList, boolean isFirstConfiguration) { - if (imeInfoList.isEmpty() || layoutInfoList.isEmpty()) { + if (imeInfoList.isEmpty() || resultList.isEmpty()) { return; } KeyboardConfigurationEvent.Builder configurationEventBuilder = new KeyboardConfigurationEvent.Builder(inputDevice).setIsFirstTimeConfiguration( isFirstConfiguration); for (int i = 0; i < imeInfoList.size(); i++) { - KeyboardLayoutInfo layoutInfo = layoutInfoList.get(i); + KeyboardLayoutSelectionResult result = resultList.get(i); String layoutName = null; int layoutSelectionCriteria = LAYOUT_SELECTION_CRITERIA_DEFAULT; - if (layoutInfo != null && layoutInfo.mDescriptor != null) { - layoutSelectionCriteria = layoutInfo.mSelectionCriteria; - KeyboardLayoutDescriptor d = KeyboardLayoutDescriptor.parse(layoutInfo.mDescriptor); + if (result != null && result.getLayoutDescriptor() != null) { + layoutSelectionCriteria = result.getSelectionCriteria(); + KeyboardLayoutDescriptor d = KeyboardLayoutDescriptor.parse( + result.getLayoutDescriptor()); if (d != null) { layoutName = d.keyboardLayoutName; } @@ -1477,33 +1483,6 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { } } - private static class KeyboardLayoutInfo { - @Nullable - private final String mDescriptor; - @LayoutSelectionCriteria - private final int mSelectionCriteria; - - private KeyboardLayoutInfo(@Nullable String descriptor, - @LayoutSelectionCriteria int selectionCriteria) { - mDescriptor = descriptor; - mSelectionCriteria = selectionCriteria; - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof KeyboardLayoutInfo) { - return Objects.equals(mDescriptor, ((KeyboardLayoutInfo) obj).mDescriptor) - && mSelectionCriteria == ((KeyboardLayoutInfo) obj).mSelectionCriteria; - } - return false; - } - - @Override - public int hashCode() { - return 31 * mSelectionCriteria + mDescriptor.hashCode(); - } - } - private interface KeyboardLayoutVisitor { void visitKeyboardLayout(Resources resources, int keyboardLayoutResId, KeyboardLayout layout); diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java index f53b9411a6a7..b8ae737919d9 100644 --- a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java +++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java @@ -16,14 +16,18 @@ package com.android.server.input; -import static java.lang.annotation.RetentionPolicy.SOURCE; +import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_USER; +import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE; +import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD; +import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT; +import static android.hardware.input.KeyboardLayoutSelectionResult.layoutSelectionCriteriaToString; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.role.RoleManager; import android.content.Intent; import android.hardware.input.KeyboardLayout; +import android.hardware.input.KeyboardLayoutSelectionResult.LayoutSelectionCriteria; import android.icu.util.ULocale; import android.text.TextUtils; import android.util.Log; @@ -40,7 +44,6 @@ import com.android.internal.os.KeyboardConfiguredProto.RepeatedKeyboardLayoutCon import com.android.internal.util.FrameworkStatsLog; import com.android.server.policy.ModifierShortcutManager; -import java.lang.annotation.Retention; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -57,32 +60,6 @@ public final class KeyboardMetricsCollector { // (requires restart) private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - @Retention(SOURCE) - @IntDef(prefix = {"LAYOUT_SELECTION_CRITERIA_"}, value = { - LAYOUT_SELECTION_CRITERIA_UNSPECIFIED, - LAYOUT_SELECTION_CRITERIA_USER, - LAYOUT_SELECTION_CRITERIA_DEVICE, - LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD, - LAYOUT_SELECTION_CRITERIA_DEFAULT - }) - public @interface LayoutSelectionCriteria { - } - - /** Unspecified layout selection criteria */ - public static final int LAYOUT_SELECTION_CRITERIA_UNSPECIFIED = 0; - - /** Manual selection by user */ - public static final int LAYOUT_SELECTION_CRITERIA_USER = 1; - - /** Auto-detection based on device provided language tag and layout type */ - public static final int LAYOUT_SELECTION_CRITERIA_DEVICE = 2; - - /** Auto-detection based on IME provided language tag and layout type */ - public static final int LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD = 3; - - /** Default selection */ - public static final int LAYOUT_SELECTION_CRITERIA_DEFAULT = 4; - @VisibleForTesting static final String DEFAULT_LAYOUT_NAME = "Default"; @@ -629,30 +606,16 @@ public final class KeyboardMetricsCollector { @Override public String toString() { - return "{keyboardLanguageTag = " + keyboardLanguageTag + " keyboardLayoutType = " + return "{keyboardLanguageTag = " + keyboardLanguageTag + + " keyboardLayoutType = " + KeyboardLayout.LayoutType.getLayoutNameFromValue(keyboardLayoutType) - + " keyboardLayoutName = " + keyboardLayoutName + " layoutSelectionCriteria = " - + getStringForSelectionCriteria(layoutSelectionCriteria) - + "imeLanguageTag = " + imeLanguageTag + " imeLayoutType = " - + KeyboardLayout.LayoutType.getLayoutNameFromValue(imeLayoutType) + "}"; - } - } - - private static String getStringForSelectionCriteria( - @LayoutSelectionCriteria int layoutSelectionCriteria) { - switch (layoutSelectionCriteria) { - case LAYOUT_SELECTION_CRITERIA_UNSPECIFIED: - return "LAYOUT_SELECTION_CRITERIA_UNSPECIFIED"; - case LAYOUT_SELECTION_CRITERIA_USER: - return "LAYOUT_SELECTION_CRITERIA_USER"; - case LAYOUT_SELECTION_CRITERIA_DEVICE: - return "LAYOUT_SELECTION_CRITERIA_DEVICE"; - case LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD: - return "LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD"; - case LAYOUT_SELECTION_CRITERIA_DEFAULT: - return "LAYOUT_SELECTION_CRITERIA_DEFAULT"; - default: - return "INVALID_CRITERIA"; + + " keyboardLayoutName = " + keyboardLayoutName + + " layoutSelectionCriteria = " + + layoutSelectionCriteriaToString(layoutSelectionCriteria) + + " imeLanguageTag = " + imeLanguageTag + + " imeLayoutType = " + KeyboardLayout.LayoutType.getLayoutNameFromValue( + imeLayoutType) + + "}"; } } diff --git a/services/core/java/com/android/server/input/debug/FocusEventDebugView.java b/services/core/java/com/android/server/input/debug/FocusEventDebugView.java index b30f5ecb2dd5..6eae9a4bbe22 100644 --- a/services/core/java/com/android/server/input/debug/FocusEventDebugView.java +++ b/services/core/java/com/android/server/input/debug/FocusEventDebugView.java @@ -229,7 +229,8 @@ public class FocusEventDebugView extends RelativeLayout { /** Report a key event to the debug view. */ @AnyThread public void reportKeyEvent(KeyEvent event) { - post(() -> handleKeyEvent(KeyEvent.obtain((KeyEvent) event))); + KeyEvent keyEvent = KeyEvent.obtain(event); + post(() -> handleKeyEvent(keyEvent)); } /** Report a motion event to the debug view. */ diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java index f1698dd0f025..73f1aad31d72 100644 --- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java +++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java @@ -36,6 +36,7 @@ import android.os.IBinder; import android.os.ResultReceiver; import android.util.EventLog; import android.util.Slog; +import android.view.MotionEvent; import android.view.inputmethod.ImeTracker; import android.view.inputmethod.InputMethod; import android.view.inputmethod.InputMethodManager; @@ -75,7 +76,7 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { @GuardedBy("ImfLock.class") @Override - public void performShowIme(IBinder showInputToken, @Nullable ImeTracker.Token statsToken, + public void performShowIme(IBinder showInputToken, @NonNull ImeTracker.Token statsToken, @InputMethod.ShowFlags int showFlags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { final IInputMethodInvoker curMethod = mService.getCurMethodLocked(); @@ -88,11 +89,12 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not. if (curMethod.showSoftInput(showInputToken, statsToken, showFlags, resultReceiver)) { if (DEBUG_IME_VISIBILITY) { - EventLog.writeEvent(IMF_SHOW_IME, statsToken.getTag(), - Objects.toString(mService.mCurFocusedWindow), + EventLog.writeEvent(IMF_SHOW_IME, + statsToken != null ? statsToken.getTag() : ImeTracker.TOKEN_NONE, + Objects.toString(mService.mImeBindingState.mFocusedWindow), InputMethodDebug.softInputDisplayReasonToString(reason), InputMethodDebug.softInputModeToString( - mService.mCurFocusedWindowSoftInputMode)); + mService.mImeBindingState.mFocusedWindowSoftInputMode)); } mService.onShowHideSoftInputRequested(true /* show */, showInputToken, reason, statsToken); @@ -102,7 +104,7 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { @GuardedBy("ImfLock.class") @Override - public void performHideIme(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken, + public void performHideIme(IBinder hideInputToken, @NonNull ImeTracker.Token statsToken, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { final IInputMethodInvoker curMethod = mService.getCurMethodLocked(); if (curMethod != null) { @@ -118,11 +120,12 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not. if (curMethod.hideSoftInput(hideInputToken, statsToken, 0, resultReceiver)) { if (DEBUG_IME_VISIBILITY) { - EventLog.writeEvent(IMF_HIDE_IME, statsToken.getTag(), - Objects.toString(mService.mCurFocusedWindow), + EventLog.writeEvent(IMF_HIDE_IME, + statsToken != null ? statsToken.getTag() : ImeTracker.TOKEN_NONE, + Objects.toString(mService.mImeBindingState.mFocusedWindow), InputMethodDebug.softInputDisplayReasonToString(reason), InputMethodDebug.softInputModeToString( - mService.mCurFocusedWindowSoftInputMode)); + mService.mImeBindingState.mFocusedWindowSoftInputMode)); } mService.onShowHideSoftInputRequested(false /* show */, hideInputToken, reason, statsToken); @@ -132,14 +135,16 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { @GuardedBy("ImfLock.class") @Override - public void applyImeVisibility(IBinder windowToken, @Nullable ImeTracker.Token statsToken, + public void applyImeVisibility(IBinder windowToken, @NonNull ImeTracker.Token statsToken, @ImeVisibilityStateComputer.VisibilityState int state) { - applyImeVisibility(windowToken, statsToken, state, -1 /* ignore reason */); + applyImeVisibility(windowToken, statsToken, state, + SoftInputShowHideReason.NOT_SET /* ignore reason */); } @GuardedBy("ImfLock.class") void applyImeVisibility(IBinder windowToken, @Nullable ImeTracker.Token statsToken, - @ImeVisibilityStateComputer.VisibilityState int state, int reason) { + @ImeVisibilityStateComputer.VisibilityState int state, + @SoftInputShowHideReason int reason) { switch (state) { case STATE_SHOW_IME: ImeTracker.forLogging().onProgress(statsToken, @@ -164,18 +169,20 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { } break; case STATE_HIDE_IME_EXPLICIT: - mService.hideCurrentInputLocked(windowToken, statsToken, 0, null, reason); + mService.hideCurrentInputLocked(windowToken, statsToken, + 0 /* flags */, null /* resultReceiver */, reason); break; case STATE_HIDE_IME_NOT_ALWAYS: mService.hideCurrentInputLocked(windowToken, statsToken, - InputMethodManager.HIDE_NOT_ALWAYS, null, reason); + InputMethodManager.HIDE_NOT_ALWAYS, null /* resultReceiver */, reason); break; case STATE_SHOW_IME_IMPLICIT: mService.showCurrentInputLocked(windowToken, statsToken, - InputMethodManager.SHOW_IMPLICIT, null, reason); + InputMethodManager.SHOW_IMPLICIT, MotionEvent.TOOL_TYPE_UNKNOWN, + null /* resultReceiver */, reason); break; case STATE_SHOW_IME_SNAPSHOT: - showImeScreenshot(windowToken, mService.getDisplayIdToShowImeLocked(), null); + showImeScreenshot(windowToken, mService.getDisplayIdToShowImeLocked()); break; case STATE_REMOVE_IME_SNAPSHOT: removeImeScreenshot(mService.getDisplayIdToShowImeLocked()); @@ -187,11 +194,10 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { @GuardedBy("ImfLock.class") @Override - public boolean showImeScreenshot(@NonNull IBinder imeTarget, int displayId, - @Nullable ImeTracker.Token statsToken) { + public boolean showImeScreenshot(@NonNull IBinder imeTarget, int displayId) { if (mImeTargetVisibilityPolicy.showImeScreenshot(imeTarget, displayId)) { mService.onShowHideSoftInputRequested(false /* show */, imeTarget, - SHOW_IME_SCREENSHOT_FROM_IMMS, statsToken); + SHOW_IME_SCREENSHOT_FROM_IMMS, null /* statsToken */); return true; } return false; @@ -201,8 +207,9 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { @Override public boolean removeImeScreenshot(int displayId) { if (mImeTargetVisibilityPolicy.removeImeScreenshot(displayId)) { - mService.onShowHideSoftInputRequested(false /* show */, mService.mCurFocusedWindow, - REMOVE_IME_SCREENSHOT_FROM_IMMS, null); + mService.onShowHideSoftInputRequested(false /* show */, + mService.mImeBindingState.mFocusedWindow, + REMOVE_IME_SCREENSHOT_FROM_IMMS, null /* statsToken */); return true; } return false; diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java index 776184fb098c..a380bc1ca171 100644 --- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java +++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java @@ -201,7 +201,7 @@ final class IInputMethodInvoker { // TODO(b/192412909): Convert this back to void method @AnyThread - boolean showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken, + boolean showSoftInput(IBinder showInputToken, @NonNull ImeTracker.Token statsToken, @InputMethod.ShowFlags int flags, ResultReceiver resultReceiver) { try { mTarget.showSoftInput(showInputToken, statsToken, flags, resultReceiver); @@ -214,8 +214,8 @@ final class IInputMethodInvoker { // TODO(b/192412909): Convert this back to void method @AnyThread - boolean hideSoftInput(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken, int flags, - ResultReceiver resultReceiver) { + boolean hideSoftInput(IBinder hideInputToken, @NonNull ImeTracker.Token statsToken, + int flags, ResultReceiver resultReceiver) { try { mTarget.hideSoftInput(hideInputToken, statsToken, flags, resultReceiver); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/inputmethod/ImeBindingState.java b/services/core/java/com/android/server/inputmethod/ImeBindingState.java new file mode 100644 index 000000000000..4c20c3b9784a --- /dev/null +++ b/services/core/java/com/android/server/inputmethod/ImeBindingState.java @@ -0,0 +1,109 @@ +/* + * 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.inputmethod; + +import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_NAME; +import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED; + +import android.annotation.Nullable; +import android.os.IBinder; +import android.util.Printer; +import android.util.proto.ProtoOutputStream; +import android.view.WindowManager; +import android.view.WindowManager.LayoutParams.SoftInputModeFlags; +import android.view.inputmethod.EditorInfo; + +import com.android.internal.inputmethod.InputMethodDebug; +import com.android.server.wm.WindowManagerInternal; + +/** + * Stores information related to one active IME client on one display. + */ +final class ImeBindingState { + + /** + * The last window token that we confirmed to be focused. This is always updated upon + * reports from the input method client. If the window state is already changed before the + * report is handled, this field just keeps the last value. + */ + @Nullable + final IBinder mFocusedWindow; + + /** + * {@link WindowManager.LayoutParams#softInputMode} of {@link #mFocusedWindow}. + * + * @see #mFocusedWindow + */ + @SoftInputModeFlags + final int mFocusedWindowSoftInputMode; + + /** + * The client by which {@link #mFocusedWindow} was reported. This gets updated whenever + * an + * IME-focusable window gained focus (without necessarily starting an input connection), + * while {@link InputMethodManagerService#mClient} only gets updated when we actually start an + * input connection. + * + * @see #mFocusedWindow + */ + @Nullable + final ClientState mFocusedWindowClient; + + /** + * The editor info by which {@link #mFocusedWindow} was reported. This differs from + * {@link InputMethodManagerService#mCurEditorInfo} the same way {@link #mFocusedWindowClient} + * differs from {@link InputMethodManagerService#mCurClient}. + * + * @see #mFocusedWindow + */ + @Nullable + final EditorInfo mFocusedWindowEditorInfo; + + void dumpDebug(ProtoOutputStream proto, WindowManagerInternal windowManagerInternal) { + proto.write(CUR_FOCUSED_WINDOW_NAME, + windowManagerInternal.getWindowName(mFocusedWindow)); + proto.write(CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE, + InputMethodDebug.softInputModeToString(mFocusedWindowSoftInputMode)); + } + + void dump(String prefix, Printer p) { + p.println(prefix + "mFocusedWindow()=" + mFocusedWindow); + p.println(prefix + "softInputMode=" + InputMethodDebug.softInputModeToString( + mFocusedWindowSoftInputMode)); + p.println(prefix + "mFocusedWindowClient=" + mFocusedWindowClient); + } + + static ImeBindingState newEmptyState() { + return new ImeBindingState( + /*focusedWindow=*/ null, + /*focusedWindowSoftInputMode=*/ SOFT_INPUT_STATE_UNSPECIFIED, + /*focusedWindowClient=*/ null, + /*focusedWindowEditorInfo=*/ null + ); + } + + ImeBindingState(@Nullable IBinder focusedWindow, + @SoftInputModeFlags int focusedWindowSoftInputMode, + @Nullable ClientState focusedWindowClient, + @Nullable EditorInfo focusedWindowEditorInfo) { + mFocusedWindow = focusedWindow; + mFocusedWindowSoftInputMode = focusedWindowSoftInputMode; + mFocusedWindowClient = focusedWindowClient; + mFocusedWindowEditorInfo = focusedWindowEditorInfo; + } +} diff --git a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java index d06c31cf36f0..85ab77355c9a 100644 --- a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java +++ b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java @@ -75,34 +75,12 @@ public final class ImeTrackerService extends IImeTracker.Stub { @NonNull @Override - public ImeTracker.Token onRequestShow(@NonNull String tag, int uid, + public ImeTracker.Token onStart(@NonNull String tag, int uid, @ImeTracker.Type int type, @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason, boolean fromUser) { final var binder = new Binder(); final var token = new ImeTracker.Token(binder, tag); - final var entry = new History.Entry(tag, uid, ImeTracker.TYPE_SHOW, ImeTracker.STATUS_RUN, - origin, reason, fromUser); - synchronized (mLock) { - mHistory.addEntry(binder, entry); - - // Register a delayed task to handle the case where the new entry times out. - mHandler.postDelayed(() -> { - synchronized (mLock) { - mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, - ImeTracker.PHASE_NOT_SET); - } - }, TIMEOUT_MS); - } - return token; - } - - @NonNull - @Override - public ImeTracker.Token onRequestHide(@NonNull String tag, int uid, - @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason, boolean fromUser) { - final var binder = new Binder(); - final var token = new ImeTracker.Token(binder, tag); - final var entry = new History.Entry(tag, uid, ImeTracker.TYPE_HIDE, ImeTracker.STATUS_RUN, - origin, reason, fromUser); + final var entry = new History.Entry(tag, uid, type, ImeTracker.STATUS_RUN, origin, reason, + fromUser); synchronized (mLock) { mHistory.addEntry(binder, entry); @@ -158,7 +136,7 @@ public final class ImeTrackerService extends IImeTracker.Stub { /** * Updates the IME request tracking token with new information available in IMMS. * - * @param statsToken the token corresponding to the current IME request. + * @param statsToken the token tracking the current IME request. * @param requestWindowName the name of the window that created the IME request. */ public void onImmsUpdate(@NonNull ImeTracker.Token statsToken, @@ -223,7 +201,7 @@ public final class ImeTrackerService extends IImeTracker.Stub { * Sets the live entry corresponding to the tracking token, if it exists, as finished, * and uploads the data for metrics. * - * @param statsToken the token corresponding to the current IME request. + * @param statsToken the token tracking the current IME request. * @param status the finish status of the IME request. * @param phase the phase the IME request finished at, if it exists * (or {@link ImeTracker#PHASE_NOT_SET} otherwise). diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java index 29fa36982351..9f2b84d9bfa5 100644 --- a/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java +++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java @@ -17,7 +17,6 @@ package com.android.server.inputmethod; import android.annotation.NonNull; -import android.annotation.Nullable; import android.os.IBinder; import android.os.ResultReceiver; import android.view.inputmethod.ImeTracker; @@ -34,12 +33,12 @@ interface ImeVisibilityApplier { * Performs showing IME on top of the given window. * * @param showInputToken A token that represents the requester to show IME. - * @param statsToken A token that tracks the progress of an IME request. + * @param statsToken The token tracking the current IME request. * @param resultReceiver If non-null, this will be called back to the caller when * it has processed request to tell what it has done. * @param reason The reason for requesting to show IME. */ - default void performShowIme(IBinder showInputToken, @Nullable ImeTracker.Token statsToken, + default void performShowIme(IBinder showInputToken, @NonNull ImeTracker.Token statsToken, @InputMethod.ShowFlags int showFlags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {} @@ -47,12 +46,12 @@ interface ImeVisibilityApplier { * Performs hiding IME to the given window * * @param hideInputToken A token that represents the requester to hide IME. - * @param statsToken A token that tracks the progress of an IME request. + * @param statsToken The token tracking the current IME request. * @param resultReceiver If non-null, this will be called back to the caller when * it has processed request to tell what it has done. * @param reason The reason for requesting to hide IME. */ - default void performHideIme(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken, + default void performHideIme(IBinder hideInputToken, @NonNull ImeTracker.Token statsToken, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {} /** @@ -60,10 +59,10 @@ interface ImeVisibilityApplier { * according to the given visibility state. * * @param windowToken The token of a window for applying the IME visibility - * @param statsToken A token that tracks the progress of an IME request. + * @param statsToken The token tracking the current IME request. * @param state The new IME visibility state for the applier to handle */ - default void applyImeVisibility(IBinder windowToken, @Nullable ImeTracker.Token statsToken, + default void applyImeVisibility(IBinder windowToken, @NonNull ImeTracker.Token statsToken, @ImeVisibilityStateComputer.VisibilityState int state) {} /** @@ -84,11 +83,9 @@ interface ImeVisibilityApplier { * * @param windowToken The token of a window to show the IME screenshot. * @param displayId The unique id to identify the display - * @param statsToken A token that tracks the progress of an IME request. * @return {@code true} if success, {@code false} otherwise. */ - default boolean showImeScreenshot(@NonNull IBinder windowToken, int displayId, - @Nullable ImeTracker.Token statsToken) { + default boolean showImeScreenshot(@NonNull IBinder windowToken, int displayId) { return false; } diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java index 0dd48ae6c9e1..cdfde87f042f 100644 --- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java +++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java @@ -212,9 +212,11 @@ public final class ImeVisibilityStateComputer { boolean visibleRequested, boolean removed) { if (mCurVisibleImeInputTarget == imeInputTarget && (!visibleRequested || removed) && mCurVisibleImeLayeringOverlay != null) { - mService.onApplyImeVisibilityFromComputer(imeInputTarget, - new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT, - SoftInputShowHideReason.HIDE_WHEN_INPUT_TARGET_INVISIBLE)); + final int reason = SoftInputShowHideReason.HIDE_WHEN_INPUT_TARGET_INVISIBLE; + final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE, + ImeTracker.ORIGIN_SERVER, reason, false /* fromUser */); + mService.onApplyImeVisibilityFromComputer(imeInputTarget, statsToken, + new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT, reason)); } mCurVisibleImeInputTarget = (visibleRequested && !removed) ? imeInputTarget : null; } @@ -224,7 +226,7 @@ public final class ImeVisibilityStateComputer { /** * Called when {@link InputMethodManagerService} is processing the show IME request. * - * @param statsToken The token for tracking this show request. + * @param statsToken The token tracking the current IME request. * @return {@code true} when the show request can proceed. */ boolean onImeShowFlags(@NonNull ImeTracker.Token statsToken, @@ -250,7 +252,7 @@ public final class ImeVisibilityStateComputer { /** * Called when {@link InputMethodManagerService} is processing the hide IME request. * - * @param statsToken The token for tracking this hide request. + * @param statsToken The token tracking the current IME request. * @return {@code true} when the hide request can proceed. */ boolean canHideIme(@NonNull ImeTracker.Token statsToken, @@ -546,7 +548,7 @@ public final class ImeVisibilityStateComputer { } } // Fallback to the focused window for some edge cases (e.g. relaunching the activity) - return mService.mCurFocusedWindow; + return mService.mImeBindingState.mFocusedWindow; } IBinder getWindowTokenFrom(ImeTargetWindowState windowState) { diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 3dedca9c42ba..2205986fe9c9 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -28,7 +28,6 @@ import static android.server.inputmethod.InputMethodManagerServiceProto.BACK_DIS import static android.server.inputmethod.InputMethodManagerServiceProto.BOUND_TO_METHOD; import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_ATTRIBUTE; import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_CLIENT; -import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_NAME; import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE; import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_ID; import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_METHOD_ID; @@ -40,7 +39,6 @@ import static android.server.inputmethod.InputMethodManagerServiceProto.IME_WIND import static android.server.inputmethod.InputMethodManagerServiceProto.IN_FULLSCREEN_MODE; import static android.server.inputmethod.InputMethodManagerServiceProto.IS_INTERACTIVE; import static android.server.inputmethod.InputMethodManagerServiceProto.LAST_IME_TARGET_WINDOW_NAME; -import static android.server.inputmethod.InputMethodManagerServiceProto.LAST_SWITCH_USER_ID; import static android.server.inputmethod.InputMethodManagerServiceProto.SHOW_IME_WITH_HARD_KEYBOARD; import static android.server.inputmethod.InputMethodManagerServiceProto.SYSTEM_READY; import static android.view.Display.DEFAULT_DISPLAY; @@ -277,9 +275,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @NonNull private final String[] mNonPreemptibleInputMethods; - @UserIdInt - private int mLastSwitchUserId; - final Context mContext; final Resources mRes; private final Handler mHandler; @@ -423,6 +418,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub private final ClientController mClientController; /** + * Holds the current IME binding state info. + */ + ImeBindingState mImeBindingState; + + /** * Set once the system is ready to run third party code. */ boolean mSystemReady; @@ -482,13 +482,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub private ClientState mCurClient; /** - * The last window token that we confirmed to be focused. This is always updated upon reports - * from the input method client. If the window state is already changed before the report is - * handled, this field just keeps the last value. - */ - IBinder mCurFocusedWindow; - - /** * The last window token that we confirmed that IME started talking to. This is always updated * upon reports from the input method. If the window state is already changed before the report * is handled, this field just keeps the last value. @@ -496,34 +489,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub IBinder mLastImeTargetWindow; /** - * {@link LayoutParams#softInputMode} of {@link #mCurFocusedWindow}. - * - * @see #mCurFocusedWindow - */ - @SoftInputModeFlags - int mCurFocusedWindowSoftInputMode; - - /** - * The client by which {@link #mCurFocusedWindow} was reported. This gets updated whenever an - * IME-focusable window gained focus (without necessarily starting an input connection), - * while {@link #mCurClient} only gets updated when we actually start an input connection. - * - * @see #mCurFocusedWindow - */ - @Nullable - ClientState mCurFocusedWindowClient; - - /** - * The editor info by which {@link #mCurFocusedWindow} was reported. This differs from - * {@link #mCurEditorInfo} the same way {@link #mCurFocusedWindowClient} differs - * from {@link #mCurClient}. - * - * @see #mCurFocusedWindow - */ - @Nullable - EditorInfo mCurFocusedWindowEditorInfo; - - /** * The {@link IRemoteInputConnection} last provided by the current client. */ IRemoteInputConnection mCurInputConnection; @@ -578,7 +543,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return mBindingController.hasMainConnection(); } - /** The token tracking the current IME request or {@code null} otherwise. */ + /** + * The token tracking the current IME show request that is waiting for a connection to an IME, + * otherwise {@code null}. + */ @Nullable private ImeTracker.Token mCurStatsToken; @@ -1128,11 +1096,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mVisibilityStateComputer.getImePolicy().setA11yRequestNoSoftKeyboard( accessibilitySoftKeyboardSetting); if (mVisibilityStateComputer.getImePolicy().isA11yRequestNoSoftKeyboard()) { - hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, - 0 /* flags */, null /* resultReceiver */, + hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */, SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE); } else if (isShowRequestedForCurrentWindow()) { - showCurrentInputImplicitLocked(mCurFocusedWindow, + showCurrentInputLocked(mImeBindingState.mFocusedWindow, + InputMethodManager.SHOW_IMPLICIT, SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE); } } else if (stylusHandwritingEnabledUri.equals(uri)) { @@ -1339,20 +1307,35 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Override public void onPackageDataCleared(String packageName, int uid) { + final int userId = getChangingUserId(); synchronized (ImfLock.class) { + final boolean isCurrentUser = (userId == mSettings.getUserId()); + final AdditionalSubtypeMap additionalSubtypeMap; + final InputMethodSettings settings; + if (isCurrentUser) { + additionalSubtypeMap = mAdditionalSubtypeMap; + settings = mSettings; + } else { + additionalSubtypeMap = AdditionalSubtypeUtils.load(userId); + settings = queryInputMethodServicesInternal(mContext, userId, + additionalSubtypeMap, DirectBootAwareness.AUTO); + } + // Note that one package may implement multiple IMEs. final ArrayList<String> changedImes = new ArrayList<>(); - for (InputMethodInfo imi : mSettings.getMethodList()) { + for (InputMethodInfo imi : settings.getMethodList()) { if (imi.getPackageName().equals(packageName)) { changedImes.add(imi.getId()); } } final AdditionalSubtypeMap newMap = - mAdditionalSubtypeMap.cloneWithRemoveOrSelf(changedImes); - if (newMap != mAdditionalSubtypeMap) { - mAdditionalSubtypeMap = newMap; + additionalSubtypeMap.cloneWithRemoveOrSelf(changedImes); + if (newMap != additionalSubtypeMap) { + if (isCurrentUser) { + mAdditionalSubtypeMap = newMap; + } AdditionalSubtypeUtils.save( - mAdditionalSubtypeMap, mSettings.getMethodMap(), mSettings.getUserId()); + newMap, settings.getMethodMap(), settings.getUserId()); } if (!changedImes.isEmpty()) { mChangedPackages.add(packageName); @@ -1627,8 +1610,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } // Hide soft input before user switch task since switch task may block main handler a while // and delayed the hideCurrentInputLocked(). - hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */, - null /* resultReceiver */, SoftInputShowHideReason.HIDE_SWITCH_USER); + hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */, + SoftInputShowHideReason.HIDE_SWITCH_USER); final UserSwitchHandlerTask task = new UserSwitchHandlerTask(this, userId, clientToBeReset); mUserSwitchHandlerTask = task; @@ -1677,8 +1660,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final int userId = mActivityManagerInternal.getCurrentUserId(); - mLastSwitchUserId = userId; - // mSettings should be created before buildInputMethodListLocked mSettings = InputMethodSettings.createEmptyMap(userId); @@ -1702,6 +1683,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mClientController = new ClientController(mPackageManagerInternal); synchronized (ImfLock.class) { mClientController.addClientControllerCallback(c -> onClientRemoved(c)); + mImeBindingState = ImeBindingState.newEmptyState(); } mPreventImeStartupUnlessTextEditor = mRes.getBoolean( @@ -1864,7 +1846,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub + " selectedIme=" + mSettings.getSelectedInputMethod()); } - mLastSwitchUserId = newUserId; if (mIsInteractive && clientToBeReset != null) { final ClientState cs = mClientController.getClient(clientToBeReset.asBinder()); if (cs == null) { @@ -2216,8 +2197,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub clearClientSessionLocked(client); clearClientSessionForAccessibilityLocked(client); if (mCurClient == client) { - hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */, - null /* resultReceiver */, SoftInputShowHideReason.HIDE_REMOVE_CLIENT); + hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */, + SoftInputShowHideReason.HIDE_REMOVE_CLIENT); if (mBoundToMethod) { mBoundToMethod = false; IInputMethodInvoker curMethod = getCurMethodLocked(); @@ -2230,9 +2211,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } mBoundToAccessibility = false; mCurClient = null; - if (mCurFocusedWindowClient == client) { - mCurFocusedWindowClient = null; - mCurFocusedWindowEditorInfo = null; + if (mImeBindingState.mFocusedWindowClient == client) { + mImeBindingState = ImeBindingState.newEmptyState(); } } } @@ -2281,7 +2261,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") void onUnbindCurrentMethodByReset() { final ImeTargetWindowState winState = mVisibilityStateComputer.getWindowStateOrNull( - mCurFocusedWindow); + mImeBindingState.mFocusedWindow); if (winState != null && !winState.isRequestedImeVisible() && !mVisibilityStateComputer.isInputShown()) { // Normally, the focus window will apply the IME visibility state to @@ -2291,7 +2271,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // service, that wouldn't make the attached IME token validity check in time) // As a result, we have to notify WM to apply IME visibility before clearing the // binding states in the first place. - mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, mCurStatsToken, + final var statsToken = createStatsTokenForFocusedClient(false /* show */, + SoftInputShowHideReason.UNBIND_CURRENT_METHOD); + mVisibilityApplier.applyImeVisibility(mImeBindingState.mFocusedWindow, statsToken, STATE_HIDE_IME); } } @@ -2325,7 +2307,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") private boolean isShowRequestedForCurrentWindow() { final ImeTargetWindowState state = mVisibilityStateComputer.getWindowStateOrNull( - mCurFocusedWindow); + mImeBindingState.mFocusedWindow); return state != null && state.isRequestedImeVisible(); } @@ -2343,10 +2325,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub getCurTokenLocked(), mCurTokenDisplayId, getCurIdLocked(), startInputReason, restarting, UserHandle.getUserId(mCurClient.mUid), - mCurClient.mSelfReportedDisplayId, - mCurFocusedWindow, mCurEditorInfo, mCurFocusedWindowSoftInputMode, - getSequenceNumberLocked()); - mImeTargetWindowMap.put(startInputToken, mCurFocusedWindow); + mCurClient.mSelfReportedDisplayId, mImeBindingState.mFocusedWindow, mCurEditorInfo, + mImeBindingState.mFocusedWindowSoftInputMode, getSequenceNumberLocked()); + mImeTargetWindowMap.put(startInputToken, mImeBindingState.mFocusedWindow); mStartInputHistory.addEntry(info); // Seems that PackageManagerInternal#grantImplicitAccess() doesn't handle cross-user @@ -2369,10 +2350,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (isShowRequestedForCurrentWindow()) { if (DEBUG) Slog.v(TAG, "Attach new input asks to show input"); // Re-use current statsToken, if it exists. - final ImeTracker.Token statsToken = mCurStatsToken; + final var statsToken = mCurStatsToken != null ? mCurStatsToken + : createStatsTokenForFocusedClient(true /* show */, + SoftInputShowHideReason.ATTACH_NEW_INPUT); mCurStatsToken = null; - showCurrentInputLocked(mCurFocusedWindow, statsToken, - mVisibilityStateComputer.getShowFlags(), + showCurrentInputLocked(mImeBindingState.mFocusedWindow, statsToken, + mVisibilityStateComputer.getShowFlags(), MotionEvent.TOOL_TYPE_UNKNOWN, null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT); } @@ -2445,7 +2428,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // Compute the final shown display ID with validated cs.selfReportedDisplayId for this // session & other conditions. ImeTargetWindowState winState = mVisibilityStateComputer.getWindowStateOrNull( - mCurFocusedWindow); + mImeBindingState.mFocusedWindow); if (winState == null) { return InputBindResult.NOT_IME_TARGET_WINDOW; } @@ -2464,8 +2447,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } if (mVisibilityStateComputer.getImePolicy().isImeHiddenByDisplayPolicy()) { - hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */, - null /* resultReceiver */, + hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */, SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE); return InputBindResult.NO_IME; } @@ -3385,8 +3367,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Override public boolean showSoftInput(IInputMethodClient client, IBinder windowToken, - @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, - int lastClickTooType, ResultReceiver resultReceiver, + @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, + int lastClickToolType, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showSoftInput"); int uid = Binder.getCallingUid(); @@ -3402,7 +3384,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final long ident = Binder.clearCallingIdentity(); try { if (DEBUG) Slog.v(TAG, "Client requesting input be shown"); - return showCurrentInputLocked(windowToken, statsToken, flags, lastClickTooType, + return showCurrentInputLocked(windowToken, statsToken, flags, lastClickToolType, resultReceiver, reason); } finally { Binder.restoreCallingIdentity(ident); @@ -3634,7 +3616,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub Binder.withCleanCallingIdentity(() -> { Objects.requireNonNull(windowToken, "windowToken must not be null"); synchronized (ImfLock.class) { - if (mCurFocusedWindow != windowToken || mCurPerceptible == perceptible) { + if (mImeBindingState.mFocusedWindow != windowToken + || mCurPerceptible == perceptible) { return; } mCurPerceptible = perceptible; @@ -3644,24 +3627,18 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @GuardedBy("ImfLock.class") - boolean showCurrentInputLocked(IBinder windowToken, @Nullable ImeTracker.Token statsToken, - @InputMethodManager.ShowFlags int flags, ResultReceiver resultReceiver, - @SoftInputShowHideReason int reason) { + private boolean showCurrentInputLocked(IBinder windowToken, + @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason) { + final var statsToken = createStatsTokenForFocusedClient(true /* show */, reason); return showCurrentInputLocked(windowToken, statsToken, flags, - MotionEvent.TOOL_TYPE_UNKNOWN, resultReceiver, reason); + MotionEvent.TOOL_TYPE_UNKNOWN, null /* resultReceiver */, reason); } @GuardedBy("ImfLock.class") - private boolean showCurrentInputLocked(IBinder windowToken, - @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, - int lastClickToolType, ResultReceiver resultReceiver, + boolean showCurrentInputLocked(IBinder windowToken, + @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, + int lastClickToolType, @Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { - // Create statsToken is none exists. - if (statsToken == null) { - statsToken = createStatsTokenForFocusedClient(true /* show */, - ImeTracker.ORIGIN_SERVER_START_INPUT, reason, false /* fromUser */); - } - if (!mVisibilityStateComputer.onImeShowFlags(statsToken, flags)) { return false; } @@ -3699,7 +3676,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Override public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken, - @Nullable ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags, + @NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { int uid = Binder.getCallingUid(); ImeTracing.getInstance().triggerManagerServiceDump( @@ -3728,17 +3705,29 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } } - @GuardedBy("ImfLock.class") - boolean hideCurrentInputLocked(IBinder windowToken, @Nullable ImeTracker.Token statsToken, - @InputMethodManager.HideFlags int flags, ResultReceiver resultReceiver, - @SoftInputShowHideReason int reason) { - // Create statsToken is none exists. - if (statsToken == null) { - final boolean fromUser = reason == SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_BACK_KEY; - statsToken = createStatsTokenForFocusedClient(false /* show */, - ImeTracker.ORIGIN_SERVER_HIDE_INPUT, reason, fromUser); + @Override + @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD) + public void hideSoftInputFromServerForTest() { + super.hideSoftInputFromServerForTest_enforcePermission(); + + synchronized (ImfLock.class) { + hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */, + SoftInputShowHideReason.HIDE_SOFT_INPUT); } + } + @GuardedBy("ImfLock.class") + private boolean hideCurrentInputLocked(IBinder windowToken, + @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason) { + final var statsToken = createStatsTokenForFocusedClient(false /* show */, reason); + return hideCurrentInputLocked(windowToken, statsToken, flags, null /* resultReceiver */, + reason); + } + + @GuardedBy("ImfLock.class") + boolean hideCurrentInputLocked(IBinder windowToken, @NonNull ImeTracker.Token statsToken, + @InputMethodManager.HideFlags int flags, @Nullable ResultReceiver resultReceiver, + @SoftInputShowHideReason int reason) { if (!mVisibilityStateComputer.canHideIme(statsToken, flags)) { return false; } @@ -3897,7 +3886,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final boolean shouldClearFlag = mImePlatformCompatUtils.shouldClearShowForcedFlag(cs.mUid); final boolean showForced = mVisibilityStateComputer.mShowForced; - if (mCurFocusedWindow != windowToken && showForced && shouldClearFlag) { + if (mImeBindingState.mFocusedWindow != windowToken + && showForced && shouldClearFlag) { mVisibilityStateComputer.mShowForced = false; } @@ -3915,9 +3905,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub Slog.w(TAG, "If you need to impersonate a foreground user/profile from" + " a background user, use EditorInfo.targetInputMethodUser with" + " INTERACT_ACROSS_USERS_FULL permission."); - hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, - 0 /* flags */, - null /* resultReceiver */, + hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */, SoftInputShowHideReason.HIDE_INVALID_USER); return InputBindResult.INVALID_USER; } @@ -3978,7 +3966,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub + " cs=" + cs); } - final boolean sameWindowFocused = mCurFocusedWindow == windowToken; + final boolean sameWindowFocused = mImeBindingState.mFocusedWindow == windowToken; final boolean isTextEditor = (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0; final boolean startInputByWinGainedFocus = (startInputFlags & StartInputFlags.WINDOW_GAINED_FOCUS) != 0; @@ -4009,10 +3997,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub null, null, null, null, -1, false); } - mCurFocusedWindow = windowToken; - mCurFocusedWindowSoftInputMode = softInputMode; - mCurFocusedWindowClient = cs; - mCurFocusedWindowEditorInfo = editorInfo; + mImeBindingState = new ImeBindingState(windowToken, softInputMode, cs, editorInfo); mCurPerceptible = true; // We want to start input before showing the IME, but after closing @@ -4025,11 +4010,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final ImeVisibilityResult imeVisRes = mVisibilityStateComputer.computeState(windowState, isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags)); if (imeVisRes != null) { + boolean isShow = false; switch (imeVisRes.getReason()) { case SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY: case SoftInputShowHideReason.SHOW_AUTO_EDITOR_FORWARD_NAV: case SoftInputShowHideReason.SHOW_STATE_VISIBLE_FORWARD_NAV: case SoftInputShowHideReason.SHOW_STATE_ALWAYS_VISIBLE: + isShow = true; + if (editorInfo != null) { res = startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection, editorInfo, startInputFlags, @@ -4039,10 +4027,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } break; } - - mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, null /* statsToken */, + final var statsToken = createStatsTokenForFocusedClient(isShow, imeVisRes.getReason()); + mVisibilityApplier.applyImeVisibility(mImeBindingState.mFocusedWindow, statsToken, imeVisRes.getState(), imeVisRes.getReason()); - if (imeVisRes.getReason() == SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW) { // If focused display changed, we should unbind current method // to make app window in previous display relayout after Ime @@ -4068,13 +4055,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @GuardedBy("ImfLock.class") - private void showCurrentInputImplicitLocked(@NonNull IBinder windowToken, - @SoftInputShowHideReason int reason) { - showCurrentInputLocked(windowToken, null /* statsToken */, InputMethodManager.SHOW_IMPLICIT, - null /* resultReceiver */, reason); - } - - @GuardedBy("ImfLock.class") private boolean canInteractWithImeLocked(int uid, IInputMethodClient client, String methodName, @Nullable ImeTracker.Token statsToken) { if (mCurClient == null || client == null @@ -4088,7 +4068,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub throw new IllegalArgumentException("unknown client " + client.asBinder()); } ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN); - if (!isImeClientFocused(mCurFocusedWindow, cs)) { + if (!isImeClientFocused(mImeBindingState.mFocusedWindow, cs)) { Slog.w(TAG, String.format("Ignoring %s of uid %d : %s", methodName, uid, client)); return false; } @@ -4100,8 +4080,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") private boolean canShowInputMethodPickerLocked(IInputMethodClient client) { final int uid = Binder.getCallingUid(); - if (mCurFocusedWindowClient != null && client != null - && mCurFocusedWindowClient.mClient.asBinder() == client.asBinder()) { + if (mImeBindingState.mFocusedWindowClient != null && client != null + && mImeBindingState.mFocusedWindowClient.mClient.asBinder() == client.asBinder()) { return true; } if (mSettings.getUserId() != UserHandle.getUserId(uid)) { @@ -4724,12 +4704,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub proto.write(CUR_METHOD_ID, getSelectedMethodIdLocked()); proto.write(CUR_SEQ, getSequenceNumberLocked()); proto.write(CUR_CLIENT, Objects.toString(mCurClient)); - proto.write(CUR_FOCUSED_WINDOW_NAME, - mWindowManagerInternal.getWindowName(mCurFocusedWindow)); + mImeBindingState.dumpDebug(proto, mWindowManagerInternal); proto.write(LAST_IME_TARGET_WINDOW_NAME, mWindowManagerInternal.getWindowName(mLastImeTargetWindow)); - proto.write(CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE, - InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode)); + proto.write(CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE, InputMethodDebug.softInputModeToString( + mImeBindingState.mFocusedWindowSoftInputMode)); if (mCurEditorInfo != null) { mCurEditorInfo.dumpDebug(proto, CUR_ATTRIBUTE); } @@ -4739,7 +4718,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub proto.write(CUR_TOKEN, Objects.toString(getCurTokenLocked())); proto.write(CUR_TOKEN_DISPLAY_ID, mCurTokenDisplayId); proto.write(SYSTEM_READY, mSystemReady); - proto.write(LAST_SWITCH_USER_ID, mLastSwitchUserId); proto.write(HAVE_CONNECTION, hasConnectionLocked()); proto.write(BOUND_TO_METHOD, mBoundToMethod); proto.write(IS_INTERACTIVE, mIsInteractive); @@ -4776,15 +4754,17 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread private void applyImeVisibility(IBinder token, IBinder windowToken, boolean setVisible, - @Nullable ImeTracker.Token statsToken) { + @NonNull ImeTracker.Token statsToken) { try { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.applyImeVisibility"); synchronized (ImfLock.class) { if (!calledWithValidTokenLocked(token)) { ImeTracker.forLogging().onFailed(statsToken, - ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY); + ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME); return; } + ImeTracker.forLogging().onProgress(statsToken, + ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME); final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom( windowToken); mVisibilityApplier.applyImeVisibility(requestToken, statsToken, @@ -4849,12 +4829,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(requestImeToken); final WindowManagerInternal.ImeTargetInfo info = mWindowManagerInternal.onToggleImeRequested( - show, mCurFocusedWindow, requestToken, mCurTokenDisplayId); + show, mImeBindingState.mFocusedWindow, requestToken, mCurTokenDisplayId); mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry( - mCurFocusedWindowClient, mCurFocusedWindowEditorInfo, info.focusedWindowName, - mCurFocusedWindowSoftInputMode, reason, mInFullscreenMode, - info.requestWindowName, info.imeControlTargetName, info.imeLayerTargetName, - info.imeSurfaceParentName)); + mImeBindingState.mFocusedWindowClient, mImeBindingState.mFocusedWindowEditorInfo, + info.focusedWindowName, mImeBindingState.mFocusedWindowSoftInputMode, reason, + mInFullscreenMode, info.requestWindowName, info.imeControlTargetName, + info.imeLayerTargetName, info.imeSurfaceParentName)); if (statsToken != null) { mImeTrackerService.onImmsUpdate(statsToken, info.requestWindowName); @@ -4862,17 +4842,21 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @BinderThread - private void hideMySoftInput(@NonNull IBinder token, @InputMethodManager.HideFlags int flags, - @SoftInputShowHideReason int reason) { + private void hideMySoftInput(@NonNull IBinder token, @NonNull ImeTracker.Token statsToken, + @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason) { try { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideMySoftInput"); synchronized (ImfLock.class) { if (!calledWithValidTokenLocked(token)) { + ImeTracker.forLogging().onFailed(statsToken, + ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME); return; } + ImeTracker.forLogging().onProgress(statsToken, + ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME); final long ident = Binder.clearCallingIdentity(); try { - hideCurrentInputLocked(mLastImeTargetWindow, null /* statsToken */, flags, + hideCurrentInputLocked(mLastImeTargetWindow, statsToken, flags, null /* resultReceiver */, reason); } finally { Binder.restoreCallingIdentity(ident); @@ -4884,18 +4868,22 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @BinderThread - private void showMySoftInput(@NonNull IBinder token, @InputMethodManager.ShowFlags int flags) { + private void showMySoftInput(@NonNull IBinder token, @NonNull ImeTracker.Token statsToken, + @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason) { try { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showMySoftInput"); synchronized (ImfLock.class) { if (!calledWithValidTokenLocked(token)) { + ImeTracker.forLogging().onFailed(statsToken, + ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME); return; } + ImeTracker.forLogging().onProgress(statsToken, + ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME); final long ident = Binder.clearCallingIdentity(); try { - showCurrentInputLocked(mLastImeTargetWindow, null /* statsToken */, flags, - null /* resultReceiver */, - SoftInputShowHideReason.SHOW_SOFT_INPUT_FROM_IME); + showCurrentInputLocked(mLastImeTargetWindow, statsToken, flags, + MotionEvent.TOOL_TYPE_UNKNOWN, null /* resultReceiver */, reason); } finally { Binder.restoreCallingIdentity(ident); } @@ -4912,10 +4900,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } } - void onApplyImeVisibilityFromComputer(IBinder windowToken, + void onApplyImeVisibilityFromComputer(IBinder windowToken, @NonNull ImeTracker.Token statsToken, @NonNull ImeVisibilityResult result) { synchronized (ImfLock.class) { - mVisibilityApplier.applyImeVisibility(windowToken, null, result.getState(), + mVisibilityApplier.applyImeVisibility(windowToken, statsToken, result.getState(), result.getReason()); } } @@ -5016,10 +5004,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub case MSG_HIDE_ALL_INPUT_METHODS: synchronized (ImfLock.class) { - final @SoftInputShowHideReason int reason = (int) msg.obj; - hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */, - null /* resultReceiver */, reason); - + @SoftInputShowHideReason final int reason = (int) msg.obj; + hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */, reason); } return true; case MSG_REMOVE_IME_SURFACE: { @@ -5038,7 +5024,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub IBinder windowToken = (IBinder) msg.obj; synchronized (ImfLock.class) { try { - if (windowToken == mCurFocusedWindow + if (windowToken == mImeBindingState.mFocusedWindow && mEnabledSession != null && mEnabledSession.mSession != null) { mEnabledSession.mSession.removeImeSurface(); } @@ -5113,7 +5099,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub case MSG_START_HANDWRITING: synchronized (ImfLock.class) { IInputMethodInvoker curMethod = getCurMethodLocked(); - if (curMethod == null || mCurFocusedWindow == null) { + if (curMethod == null || mImeBindingState.mFocusedWindow == null) { return true; } final HandwritingModeController.HandwritingSession session = @@ -5121,7 +5107,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub msg.arg1 /*requestId*/, msg.arg2 /*pid*/, mBindingController.getCurMethodUid(), - mCurFocusedWindow); + mImeBindingState.mFocusedWindow); if (session == null) { Slog.e(TAG, "Failed to start handwriting session for requestId: " + msg.arg1); @@ -5174,10 +5160,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // Handle IME visibility when interactive changed before finishing the input to // ensure we preserve the last state as possible. final ImeVisibilityResult imeVisRes = mVisibilityStateComputer.onInteractiveChanged( - mCurFocusedWindow, interactive); + mImeBindingState.mFocusedWindow, interactive); if (imeVisRes != null) { - mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, null, - imeVisRes.getState(), imeVisRes.getReason()); + // Pass in a null statsToken as the IME snapshot is not tracked by ImeTracker. + mVisibilityApplier.applyImeVisibility(mImeBindingState.mFocusedWindow, + null /* statsToken */, imeVisRes.getState(), imeVisRes.getReason()); } // Eligible IME processes use new "setInteractive" protocol. mCurClient.mClient.setInteractive(mIsInteractive, mInFullscreenMode); @@ -5867,7 +5854,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Override public void reportImeControl(@Nullable IBinder windowToken) { synchronized (ImfLock.class) { - if (mCurFocusedWindow != windowToken) { + if (mImeBindingState.mFocusedWindow != windowToken) { // mCurPerceptible was set by the focused window, but it is no longer in // control, so we reset mCurPerceptible. mCurPerceptible = true; @@ -5881,7 +5868,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // Hide the IME method menu only when the IME surface parent is changed by the // input target changed, in case seeing the dialog dismiss flickering during // the next focused window starting the input connection. - if (mLastImeTargetWindow != mCurFocusedWindow) { + if (mLastImeTargetWindow != mImeBindingState.mFocusedWindow) { mMenuController.hideInputMethodMenuLocked(); } } @@ -6175,11 +6162,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub client = mCurClient; p.println(" mCurClient=" + client + " mCurSeq=" + getSequenceNumberLocked()); p.println(" mCurPerceptible=" + mCurPerceptible); - p.println(" mCurFocusedWindow=" + mCurFocusedWindow - + " softInputMode=" - + InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode) - + " client=" + mCurFocusedWindowClient); - focusedWindowClient = mCurFocusedWindowClient; + mImeBindingState.dump(" ", p); p.println(" mCurId=" + getCurIdLocked() + " mHaveConnection=" + hasConnectionLocked() + " mBoundToMethod=" + mBoundToMethod + " mVisibleBound=" + mBindingController.isVisibleBound()); @@ -6230,7 +6213,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub p.println("No input method client."); } - if (focusedWindowClient != null && client != focusedWindowClient) { + if (mImeBindingState.mFocusedWindowClient != null + && client != mImeBindingState.mFocusedWindowClient) { p.println(" "); p.println("Warning: Current input method client doesn't match the last focused. " + "window."); @@ -6238,7 +6222,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub p.println(" "); pw.flush(); try { - TransferPipe.dumpAsync(focusedWindowClient.mClient.asBinder(), fd, args); + TransferPipe.dumpAsync( + mImeBindingState.mFocusedWindowClient.mClient.asBinder(), fd, args); } catch (IOException | RemoteException e) { p.println("Failed to dump input method client in focused window: " + e); } @@ -6310,8 +6295,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @ShellCommandResult private int onCommandWithSystemIdentity(@Nullable String cmd) { switch (TextUtils.emptyIfNull(cmd)) { - case "get-last-switch-user-id": - return mService.getLastSwitchUserId(this); case "tracing": return mService.handleShellCommandTraceInputMethod(this); case "ime": { // For "adb shell ime <command>". @@ -6425,15 +6408,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // ---------------------------------------------------------------------- // Shell command handlers: - @BinderThread - @ShellCommandResult - private int getLastSwitchUserId(@NonNull ShellCommand shellCommand) { - synchronized (ImfLock.class) { - shellCommand.getOutPrintWriter().println(mLastSwitchUserId); - return ShellCommandResult.SUCCESS; - } - } - /** * Handles {@code adb shell ime list}. * @param shellCommand {@link ShellCommand} object that is handling this command. @@ -6684,8 +6658,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final String nextIme; final List<InputMethodInfo> nextEnabledImes; if (userId == mSettings.getUserId()) { - hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, - 0 /* flags */, null /* resultReceiver */, + hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */, SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND); mBindingController.unbindCurrentMethod(); @@ -6814,27 +6787,21 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub * Creates an IME request tracking token for the current focused client. * * @param show whether this is a show or a hide request. - * @param origin the origin of the IME request. * @param reason the reason why the IME request was created. - * @param fromUser whether this request was created directly from user interaction. */ @NonNull private ImeTracker.Token createStatsTokenForFocusedClient(boolean show, - @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason, boolean fromUser) { - final int uid = mCurFocusedWindowClient != null - ? mCurFocusedWindowClient.mUid + @SoftInputShowHideReason int reason) { + final int uid = mImeBindingState.mFocusedWindowClient != null + ? mImeBindingState.mFocusedWindowClient.mUid : -1; - final var packageName = mCurFocusedWindowEditorInfo != null - ? mCurFocusedWindowEditorInfo.packageName + final var packageName = mImeBindingState.mFocusedWindowEditorInfo != null + ? mImeBindingState.mFocusedWindowEditorInfo.packageName : "uid(" + uid + ")"; - if (show) { - return ImeTracker.forLogging() - .onRequestShow(packageName, uid, origin, reason, fromUser); - } else { - return ImeTracker.forLogging() - .onRequestHide(packageName, uid, origin, reason, fromUser); - } + return ImeTracker.forLogging().onStart(packageName, uid, + show ? ImeTracker.TYPE_SHOW : ImeTracker.TYPE_HIDE, ImeTracker.ORIGIN_SERVER, + reason, false /* fromUser */); } private static final class InputMethodPrivilegedOperationsImpl @@ -6909,12 +6876,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread @Override - public void hideMySoftInput(@InputMethodManager.HideFlags int flags, - @SoftInputShowHideReason int reason, AndroidFuture future /* T=Void */) { + public void hideMySoftInput(@NonNull ImeTracker.Token statsToken, + @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason, + AndroidFuture future /* T=Void */) { @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future; try { - mImms.hideMySoftInput(mToken, flags, reason); + mImms.hideMySoftInput(mToken, statsToken, flags, reason); typedFuture.complete(null); } catch (Throwable e) { typedFuture.completeExceptionally(e); @@ -6923,12 +6891,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread @Override - public void showMySoftInput(@InputMethodManager.ShowFlags int flags, + public void showMySoftInput(@NonNull ImeTracker.Token statsToken, + @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason, AndroidFuture future /* T=Void */) { @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future; try { - mImms.showMySoftInput(mToken, flags); + mImms.showMySoftInput(mToken, statsToken, flags, reason); typedFuture.complete(null); } catch (Throwable e) { typedFuture.completeExceptionally(e); @@ -6987,7 +6956,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread @Override public void applyImeVisibilityAsync(IBinder windowToken, boolean setVisible, - @Nullable ImeTracker.Token statsToken) { + @NonNull ImeTracker.Token statsToken) { mImms.applyImeVisibility(mToken, windowToken, setVisible, statsToken); } diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java index db6a9af0fe62..9caf5cfe67f3 100644 --- a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java +++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java @@ -184,6 +184,13 @@ public class ZeroJankProxy extends IInputMethodManager.Stub { return true; } + @Override + @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD) + public void hideSoftInputFromServerForTest() throws RemoteException { + super.hideSoftInputFromServerForTest_enforcePermission(); + mInner.hideSoftInputFromServerForTest(); + } + @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) @Override public void startInputOrWindowGainedFocusAsync( diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 1f7d5490dd6d..2a487856bfb8 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -851,7 +851,6 @@ class MediaRouter2ServiceImpl { } } - @RequiresPermission(value = Manifest.permission.MEDIA_ROUTING_CONTROL, conditional = true) private boolean checkMediaRoutingControlPermission( int callerUid, int callerPid, @Nullable String callerPackageName) { return PermissionChecker.checkPermissionForDataDelivery( diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java index 7af5c08f5ffa..1dc86f2b7f1e 100644 --- a/services/core/java/com/android/server/media/MediaSession2Record.java +++ b/services/core/java/com/android/server/media/MediaSession2Record.java @@ -56,7 +56,6 @@ public class MediaSession2Record extends MediaSessionRecordImpl { private boolean mIsClosed; private final int mPid; - private final ForegroundServiceDelegationOptions mForegroundServiceDelegationOptions; public MediaSession2Record( Session2Token sessionToken, @@ -76,25 +75,6 @@ public class MediaSession2Record extends MediaSessionRecordImpl { .build(); mPid = pid; mPolicies = policies; - mForegroundServiceDelegationOptions = - new ForegroundServiceDelegationOptions.Builder() - .setClientPid(mPid) - .setClientUid(getUid()) - .setClientPackageName(getPackageName()) - .setClientAppThread(null) - .setSticky(false) - .setClientInstanceName( - "MediaSessionFgsDelegate_" - + getUid() - + "_" - + mPid - + "_" - + getPackageName()) - .setForegroundServiceTypes(0) - .setDelegationService( - ForegroundServiceDelegationOptions - .DELEGATION_SERVICE_MEDIA_PLAYBACK) - .build(); } } @@ -119,7 +99,10 @@ public class MediaSession2Record extends MediaSessionRecordImpl { @Override public ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions() { - return mForegroundServiceDelegationOptions; + // For an app to be eligible for FGS delegation, it needs a media session liked to a media + // notification. Currently, notifications cannot be linked to MediaSession2 so it is not + // supported. + return null; } @Override diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 42851747c5cb..e2163c54f4e2 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -169,7 +169,8 @@ public class MediaSessionService extends SystemService implements Monitor { private UsageStatsManagerInternal mUsageStatsManagerInternal; /* Maps uid with all user engaging session tokens associated to it */ - private final SparseArray<Set<MediaSession.Token>> mUserEngagingSessions = new SparseArray<>(); + private final SparseArray<Set<MediaSessionRecordImpl>> mUserEngagingSessions = + new SparseArray<>(); // The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile) // It's always not null after the MediaSessionService is started. @@ -625,9 +626,7 @@ public class MediaSessionService extends SystemService implements Monitor { } ForegroundServiceDelegationOptions foregroundServiceDelegationOptions = record.getForegroundServiceDelegationOptions(); - if (foregroundServiceDelegationOptions == null - || foregroundServiceDelegationOptions.mClientPid == Process.INVALID_PID) { - // This record doesn't support FGS delegation. In practice, this is MediaSession2. + if (foregroundServiceDelegationOptions == null) { return; } if (allowRunningInForeground) { @@ -640,23 +639,21 @@ public class MediaSessionService extends SystemService implements Monitor { } private void reportMediaInteractionEvent(MediaSessionRecordImpl record, boolean userEngaged) { - if (!android.app.usage.Flags.userInteractionTypeApi() - || !(record instanceof MediaSessionRecord)) { + if (!android.app.usage.Flags.userInteractionTypeApi()) { return; } String packageName = record.getPackageName(); int sessionUid = record.getUid(); - MediaSession.Token token = ((MediaSessionRecord) record).getSessionToken(); if (userEngaged) { if (!mUserEngagingSessions.contains(sessionUid)) { mUserEngagingSessions.put(sessionUid, new HashSet<>()); reportUserInteractionEvent( USAGE_STATS_ACTION_START, record.getUserId(), packageName); } - mUserEngagingSessions.get(sessionUid).add(token); + mUserEngagingSessions.get(sessionUid).add(record); } else if (mUserEngagingSessions.contains(sessionUid)) { - mUserEngagingSessions.get(sessionUid).remove(token); + mUserEngagingSessions.get(sessionUid).remove(record); if (mUserEngagingSessions.get(sessionUid).isEmpty()) { reportUserInteractionEvent( USAGE_STATS_ACTION_STOP, record.getUserId(), packageName); diff --git a/services/core/java/com/android/server/net/TEST_MAPPING b/services/core/java/com/android/server/net/TEST_MAPPING index e0376ed6461b..8db59055c1f4 100644 --- a/services/core/java/com/android/server/net/TEST_MAPPING +++ b/services/core/java/com/android/server/net/TEST_MAPPING @@ -28,5 +28,10 @@ } ] } + ], + "postsubmit":[ + { + "name":"FrameworksVpnTests" + } ] } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 3a7ac0bd659d..ba5882cc7e98 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -12050,11 +12050,12 @@ public class NotificationManagerService extends SystemService { @Override public void onServiceAdded(ManagedServiceInfo info) { if (lifetimeExtensionRefactor()) { - // Generally, only System or System UI should have the permissions to call - // registerSystemService. - // isCallerSystemorPhone tells us whether the caller is System. Then, if it's not - // the system, we know it's system UI. - info.isSystemUi = !isCallerSystemOrPhone(); + // We explicitly check the status bar permission for the uid in the info object. + // We can't use the calling uid here because it's probably always system server. + // Note that this will also be true for the shell. + info.isSystemUi = getContext().checkPermission( + android.Manifest.permission.STATUS_BAR_SERVICE, -1, info.uid) + == PERMISSION_GRANTED; } final INotificationListener listener = (INotificationListener) info.service; final NotificationRankingUpdate update; diff --git a/services/core/java/com/android/server/notification/ZenModeEventLogger.java b/services/core/java/com/android/server/notification/ZenModeEventLogger.java index b9a267f37ff9..8e37b4f24880 100644 --- a/services/core/java/com/android/server/notification/ZenModeEventLogger.java +++ b/services/core/java/com/android/server/notification/ZenModeEventLogger.java @@ -32,6 +32,7 @@ import android.app.NotificationManager; import android.content.pm.PackageManager; import android.os.Process; import android.service.notification.DNDPolicyProto; +import android.service.notification.ZenAdapters; import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeConfig.ConfigChangeOrigin; import android.service.notification.ZenModeConfig.ZenRule; @@ -591,9 +592,11 @@ class ZenModeEventLogger { // This applies to both call and message senders, but not conversation senders, // where they use the same enum values. proto.write(DNDPolicyProto.ALLOW_CALLS_FROM, - ZenModeConfig.getZenPolicySenders(mNewPolicy.allowCallsFrom())); + ZenAdapters.notificationPolicySendersToZenPolicyPeopleType( + mNewPolicy.allowCallsFrom())); proto.write(DNDPolicyProto.ALLOW_MESSAGES_FROM, - ZenModeConfig.getZenPolicySenders(mNewPolicy.allowMessagesFrom())); + ZenAdapters.notificationPolicySendersToZenPolicyPeopleType( + mNewPolicy.allowMessagesFrom())); proto.write(DNDPolicyProto.ALLOW_CONVERSATIONS_FROM, mNewPolicy.allowConversationsFrom()); diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 6857869e3776..bc86c8216952 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -84,6 +84,7 @@ import android.provider.Settings.Global; import android.service.notification.Condition; import android.service.notification.ConditionProviderService; import android.service.notification.DeviceEffectsApplier; +import android.service.notification.ZenAdapters; import android.service.notification.ZenDeviceEffects; import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeConfig.ConfigChangeOrigin; diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java index a4c4347b7cef..0abe50f12772 100644 --- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java +++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java @@ -16,6 +16,8 @@ package com.android.server.ondeviceintelligence; +import static android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException.PROCESSING_UPDATE_STATUS_CONNECTION_FAILED; + import android.Manifest; import android.annotation.NonNull; import android.app.AppGlobals; @@ -36,6 +38,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; +import android.os.Bundle; import android.os.ICancellationSignal; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; @@ -43,8 +46,12 @@ import android.os.RemoteCallback; import android.os.RemoteException; import android.os.UserHandle; import android.provider.DeviceConfig; +import android.service.ondeviceintelligence.IOnDeviceIntelligenceService; import android.service.ondeviceintelligence.IOnDeviceTrustedInferenceService; +import android.service.ondeviceintelligence.IRemoteProcessingService; import android.service.ondeviceintelligence.IRemoteStorageService; +import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback; +import android.service.ondeviceintelligence.OnDeviceIntelligenceService; import android.text.TextUtils; import android.util.Slog; @@ -59,10 +66,11 @@ import java.util.Objects; import java.util.Set; /** - * This is the system service for handling calls on the {@link OnDeviceIntelligenceManager}. This + * This is the system service for handling calls on the + * {@link android.app.ondeviceintelligence.OnDeviceIntelligenceManager}. This * service holds connection references to the underlying remote services i.e. the isolated service - * {@link android.service.ondeviceintelligence.OnDeviceTrustedInferenceService} and a regular - * service counter part {@link android.service.ondeviceintelligence.OnDeviceIntelligenceService}. + * {@link OnDeviceTrustedInferenceService} and a regular + * service counter part {@link OnDeviceIntelligenceService}. * * Note: Both the remote services run under the SYSTEM user, as we cannot have separate instance of * the Inference service for each user, due to possible high memory footprint. @@ -313,10 +321,48 @@ public class OnDeviceIntelligenceManagerService extends SystemService { mRemoteOnDeviceIntelligenceService = new RemoteOnDeviceIntelligenceService(mContext, ComponentName.unflattenFromString(serviceName), UserHandle.SYSTEM.getIdentifier()); + mRemoteOnDeviceIntelligenceService.setServiceLifecycleCallbacks( + new ServiceConnector.ServiceLifecycleCallbacks<>() { + @Override + public void onConnected( + @NonNull IOnDeviceIntelligenceService service) { + try { + service.registerRemoteServices( + getRemoteProcessingService()); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to send connected event", ex); + } + } + }); } } } + @NonNull + private IRemoteProcessingService.Stub getRemoteProcessingService() { + return new IRemoteProcessingService.Stub() { + @Override + public void updateProcessingState( + Bundle processingState, + IProcessingUpdateStatusCallback callback) { + try { + ensureRemoteTrustedInferenceServiceInitialized(); + mRemoteInferenceService.post( + service -> service.updateProcessingState( + processingState, callback)); + } catch (RemoteException unused) { + try { + callback.onFailure( + PROCESSING_UPDATE_STATUS_CONNECTION_FAILED, + "Received failure invoking the remote processing service."); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to send failure status.", ex); + } + } + } + }; + } + private void ensureRemoteTrustedInferenceServiceInitialized() throws RemoteException { synchronized (mLock) { if (mRemoteInferenceService == null) { @@ -332,6 +378,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService { public void onConnected( @NonNull IOnDeviceTrustedInferenceService service) { try { + ensureRemoteIntelligenceServiceInitialized(); service.registerRemoteStorageService( getIRemoteStorageService()); } catch (RemoteException ex) { @@ -358,8 +405,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService { @Override public void getReadOnlyFeatureFileDescriptorMap( Feature feature, - RemoteCallback remoteCallback) - throws RemoteException { + RemoteCallback remoteCallback) { mRemoteOnDeviceIntelligenceService.post( service -> service.getReadOnlyFeatureFileDescriptorMap( feature, remoteCallback)); diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java index 8452c0e61a81..4eb8b2b980cb 100644 --- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java +++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java @@ -21,7 +21,6 @@ import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled; import android.Manifest; import android.annotation.Nullable; import android.annotation.RequiresPermission; -import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.admin.DevicePolicyManager; import android.app.role.RoleManager; @@ -39,6 +38,7 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; +import android.os.UserManager; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.ArrayMap; @@ -96,6 +96,7 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000; private final Object mLock = new Object(); + private final Injector mInjector; private final Context mContext; private final AppOpsManager mAppOps; private final TelephonyManager mTelephonyManager; @@ -345,6 +346,18 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { AtomicFile getMappingFile() { return mMappingFile; } + + UserManager getUserManager() { + return mContext.getSystemService(UserManager.class); + } + + DevicePolicyManager getDevicePolicyManager() { + return mContext.getSystemService(DevicePolicyManager.class); + } + + void setSystemProperty(String key, String value) { + SystemProperties.set(key, value); + } } BugreportManagerServiceImpl(Context context) { @@ -356,6 +369,7 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) BugreportManagerServiceImpl(Injector injector) { + mInjector = injector; mContext = injector.getContext(); mAppOps = mContext.getSystemService(AppOpsManager.class); mTelephonyManager = mContext.getSystemService(TelephonyManager.class); @@ -388,12 +402,7 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { int callingUid = Binder.getCallingUid(); enforcePermission(callingPackage, callingUid, bugreportMode == BugreportParams.BUGREPORT_MODE_TELEPHONY /* checkCarrierPrivileges */); - final long identity = Binder.clearCallingIdentity(); - try { - ensureUserCanTakeBugReport(bugreportMode); - } finally { - Binder.restoreCallingIdentity(identity); - } + ensureUserCanTakeBugReport(bugreportMode); Slogf.i(TAG, "Starting bugreport for %s / %d", callingPackage, callingUid); synchronized (mLock) { @@ -432,7 +441,6 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { @RequiresPermission(value = Manifest.permission.DUMP, conditional = true) public void retrieveBugreport(int callingUidUnused, String callingPackage, int userId, FileDescriptor bugreportFd, String bugreportFile, - boolean keepBugreportOnRetrievalUnused, IDumpstateListener listener) { int callingUid = Binder.getCallingUid(); enforcePermission(callingPackage, callingUid, false); @@ -564,54 +572,59 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { } /** - * Validates that the current user is an admin user or, when bugreport is requested remotely - * that the current user is an affiliated user. + * Validates that the calling user is an admin user or, when bugreport is requested remotely + * that the user is an affiliated user. * - * @throws IllegalArgumentException if the current user is not an admin user + * @throws IllegalArgumentException if the calling user or the parent of the calling profile + * user is not an admin user. */ private void ensureUserCanTakeBugReport(int bugreportMode) { - UserInfo currentUser = null; + // Get the calling userId before clearing the caller identity. + int effectiveCallingUserId = UserHandle.getUserId(Binder.getCallingUid()); + boolean isAdminUser = false; + final long identity = Binder.clearCallingIdentity(); try { - currentUser = ActivityManager.getService().getCurrentUser(); - } catch (RemoteException e) { - // Impossible to get RemoteException for an in-process call. - } - - if (currentUser == null) { - logAndThrow("There is no current user, so no bugreport can be requested."); + UserInfo profileParent = + mInjector.getUserManager().getProfileParent(effectiveCallingUserId); + if (profileParent == null) { + isAdminUser = mInjector.getUserManager().isUserAdmin(effectiveCallingUserId); + } else { + // If the caller is a profile, we need to check its parent user instead. + // Therefore setting the profile parent user as the effective calling user. + effectiveCallingUserId = profileParent.id; + isAdminUser = profileParent.isAdmin(); + } + } finally { + Binder.restoreCallingIdentity(identity); } - - if (!currentUser.isAdmin()) { + if (!isAdminUser) { if (bugreportMode == BugreportParams.BUGREPORT_MODE_REMOTE - && isCurrentUserAffiliated(currentUser.id)) { + && isUserAffiliated(effectiveCallingUserId)) { return; } - logAndThrow(TextUtils.formatSimple("Current user %s is not an admin user." - + " Only admin users are allowed to take bugreport.", currentUser.id)); + logAndThrow(TextUtils.formatSimple("Calling user %s is not an admin user." + + " Only admin users and their profiles are allowed to take bugreport.", + effectiveCallingUserId)); } } /** - * Returns {@code true} if the device has device owner and the current user is affiliated + * Returns {@code true} if the device has device owner and the specified user is affiliated * with the device owner. */ - private boolean isCurrentUserAffiliated(int currentUserId) { - DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + private boolean isUserAffiliated(int userId) { + DevicePolicyManager dpm = mInjector.getDevicePolicyManager(); int deviceOwnerUid = dpm.getDeviceOwnerUserId(); if (deviceOwnerUid == UserHandle.USER_NULL) { return false; } - int callingUserId = UserHandle.getUserId(Binder.getCallingUid()); - - Slog.i(TAG, "callingUid: " + callingUserId + " deviceOwnerUid: " + deviceOwnerUid - + " currentUserId: " + currentUserId); - - if (callingUserId != deviceOwnerUid) { - logAndThrow("Caller is not device owner on provisioned device."); + if (DEBUG) { + Slog.d(TAG, "callingUid: " + userId + " deviceOwnerUid: " + deviceOwnerUid); } - if (!dpm.isAffiliatedUser(currentUserId)) { - logAndThrow("Current user is not affiliated to the device owner."); + + if (userId != deviceOwnerUid && !dpm.isAffiliatedUser(userId)) { + logAndThrow("User " + userId + " is not affiliated to the device owner."); } return true; } @@ -728,7 +741,7 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { @GuardedBy("mLock") private IDumpstate startAndGetDumpstateBinderServiceLocked() { // Start bugreport service. - SystemProperties.set("ctl.start", BUGREPORT_SERVICE); + mInjector.setSystemProperty("ctl.start", BUGREPORT_SERVICE); IDumpstate ds = null; boolean timedOut = false; @@ -760,7 +773,7 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { // This tells init to cancel bugreportd service. Note that this is achieved through // setting a system property which is not thread-safe. So the lock here offers // thread-safety only among callers of the API. - SystemProperties.set("ctl.stop", BUGREPORT_SERVICE); + mInjector.setSystemProperty("ctl.stop", BUGREPORT_SERVICE); } @RequiresPermission(android.Manifest.permission.DUMP) diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 9afdde53643c..b5476fdd3050 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -760,13 +760,18 @@ public class ComputerEngine implements Computer { if (pkgName == null) { if (!mCrossProfileIntentResolverEngine.shouldSkipCurrentProfile(this, intent, resolvedType, userId)) { - /* - Check for results in the current profile only if there is no - {@link CrossProfileIntentFilter} for user with flag - {@link PackageManager.SKIP_CURRENT_PROFILE} set. - */ - result.addAll(filterIfNotSystemUser(mComponentResolver.queryActivities(this, - intent, resolvedType, flags, userId), userId)); + + final List<ResolveInfo> queryResult = mComponentResolver.queryActivities(this, + intent, resolvedType, flags, userId); + // If the user doesn't exist, the queryResult is null + if (queryResult != null) { + /* + Check for results in the current profile only if there is no + {@link CrossProfileIntentFilter} for user with flag + {@link PackageManager.SKIP_CURRENT_PROFILE} set. + */ + result.addAll(filterIfNotSystemUser(queryResult, userId)); + } } addInstant = isInstantAppResolutionAllowed(intent, result, userId, false /*skipPackageCheck*/, flags); @@ -788,9 +793,13 @@ public class ComputerEngine implements Computer { if (setting != null && setting.getAndroidPackage() != null && (resolveForStart || !shouldFilterApplication(setting, filterCallingUid, userId))) { - result.addAll(filterIfNotSystemUser(mComponentResolver.queryActivities(this, + final List<ResolveInfo> queryResult = mComponentResolver.queryActivities(this, intent, resolvedType, flags, setting.getAndroidPackage().getActivities(), - userId), userId)); + userId); + // If the user doesn't exist, the queryResult is null + if (queryResult != null) { + result.addAll(filterIfNotSystemUser(queryResult, userId)); + } } if (result == null || result.size() == 0) { // the caller wants to resolve for a particular package; however, there diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 186cf5e37003..4bfd077760e4 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -1080,7 +1080,7 @@ final class InstallPackageHelper { reconciledPackages = ReconcilePackageUtils.reconcilePackages( requests, Collections.unmodifiableMap(mPm.mPackages), versionInfos, mSharedLibraries, mPm.mSettings.getKeySetManagerService(), - mPm.mSettings); + mPm.mSettings, mPm.mInjector.getSystemConfig()); } catch (ReconcileFailure e) { for (InstallRequest request : requests) { request.setError("Reconciliation failed...", e); @@ -3810,7 +3810,7 @@ final class InstallPackageHelper { mPm.mPackages, Collections.singletonMap(pkgName, mPm.getSettingsVersionForPackage(parsedPackage)), mSharedLibraries, mPm.mSettings.getKeySetManagerService(), - mPm.mSettings); + mPm.mSettings, mPm.mInjector.getSystemConfig()); if ((scanFlags & SCAN_AS_APEX) == 0) { appIdCreated = optimisticallyRegisterAppId(installRequest); } else { diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java index 9a7916a7b215..90d6adc4fa52 100644 --- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java +++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java @@ -17,6 +17,7 @@ package com.android.server.pm; import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE; +import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; import static android.content.pm.SigningDetails.CapabilityMergeRule.MERGE_RESTRICTED_CAPABILITY; @@ -25,6 +26,7 @@ import static com.android.server.pm.PackageManagerService.SCAN_BOOTING; import static com.android.server.pm.PackageManagerService.SCAN_DONT_KILL_APP; import static com.android.server.pm.PackageManagerService.TAG; +import android.content.pm.Flags; import android.content.pm.PackageManager; import android.content.pm.SharedLibraryInfo; import android.content.pm.SigningDetails; @@ -36,6 +38,7 @@ import android.util.Slog; import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; +import com.android.server.SystemConfig; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.utils.WatchedLongSparseArray; @@ -53,14 +56,17 @@ import java.util.Map; * as install) led to the request. */ final class ReconcilePackageUtils { - private static final boolean ALLOW_NON_PRELOADS_SYSTEM_SIGNATURE = Build.IS_DEBUGGABLE || true; + // TODO(b/308573259): with allow-list, we should be able to disallow such installs even in + // debuggable builds. + private static final boolean ALLOW_NON_PRELOADS_SYSTEM_SHAREDUIDS = Build.IS_DEBUGGABLE + || !Flags.restrictNonpreloadsSystemShareduids(); public static List<ReconciledPackage> reconcilePackages( List<InstallRequest> installRequests, Map<String, AndroidPackage> allPackages, Map<String, Settings.VersionInfo> versionInfos, SharedLibrariesImpl sharedLibraries, - KeySetManagerService ksms, Settings settings) + KeySetManagerService ksms, Settings settings, SystemConfig systemConfig) throws ReconcileFailure { final List<ReconciledPackage> result = new ArrayList<>(installRequests.size()); @@ -187,11 +193,19 @@ final class ReconcilePackageUtils { SigningDetails.CertCapabilities.PERMISSION)) { Slog.d(TAG, "Non-preload app associated with system signature: " + signatureCheckPs.getPackageName()); - if (!ALLOW_NON_PRELOADS_SYSTEM_SIGNATURE) { - throw new ReconcileFailure( - INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, - "Non-preload app associated with system signature: " - + signatureCheckPs.getPackageName()); + if (sharedUserSetting != null && !ALLOW_NON_PRELOADS_SYSTEM_SHAREDUIDS) { + // Check the allow-list. + var allowList = systemConfig.getPackageToSharedUidAllowList(); + var sharedUidName = allowList.get(signatureCheckPs.getPackageName()); + if (sharedUidName == null + || !sharedUserSetting.name.equals(sharedUidName)) { + var msg = "Non-preload app " + signatureCheckPs.getPackageName() + + " signed with platform signature and joining shared uid: " + + sharedUserSetting.name; + Slog.e(TAG, msg + ", allowList: " + allowList); + throw new ReconcileFailure( + INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID, msg); + } } } diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index fe65010b7281..59d621959ac5 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -3386,12 +3386,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile } else if (tagName.equals("verifier")) { final String deviceIdentity = parser.getAttributeValue(null, "device"); - try { - mVerifierDeviceIdentity = VerifierDeviceIdentity.parse(deviceIdentity); - } catch (IllegalArgumentException e) { - Slog.w(PackageManagerService.TAG, "Discard invalid verifier device id: " - + e.getMessage()); - } + mVerifierDeviceIdentity = VerifierDeviceIdentity.parse(deviceIdentity); } else if (TAG_READ_EXTERNAL_STORAGE.equals(tagName)) { // No longer used. } else if (tagName.equals("keyset-settings")) { @@ -3419,7 +3414,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile } str.close(); - } catch (IOException | XmlPullParserException | ArrayIndexOutOfBoundsException e) { + } catch (IOException | XmlPullParserException | ArrayIndexOutOfBoundsException + | IllegalArgumentException e) { // Remove corrupted file and retry. atomicFile.failRead(str, e); @@ -4558,6 +4554,10 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile for (int i = 0; i < size; i++) { final PackageSetting ps = mPackages.valueAt(i); if (ps.getPkg() == null) { + // This would force-create correct per-user state. + ps.setInstalled(false, userHandle); + // Make sure the app is excluded from storage mapping for this user. + writeKernelMappingLPr(ps); continue; } final boolean shouldMaybeInstall = ps.isSystem() && diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index c1ab3f9e3eb9..211b7546bd8a 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -3795,6 +3795,7 @@ public class ShortcutService extends IShortcutService.Stub { } final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); + final boolean archival = intent.getBooleanExtra(Intent.EXTRA_ARCHIVAL, false); switch (action) { case Intent.ACTION_PACKAGE_ADDED: @@ -3805,7 +3806,7 @@ public class ShortcutService extends IShortcutService.Stub { } break; case Intent.ACTION_PACKAGE_REMOVED: - if (!replacing) { + if (!replacing || (replacing && archival)) { handlePackageRemoved(packageName, userId); } break; diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java index 47032ea2d6af..754b141ff10d 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java @@ -408,6 +408,9 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte /** * Gets the permission states for requested package, persistent device and user. + * <p> + * <strong>Note: </strong>Default device permissions are not inherited in this API. Returns the + * exact permission states for the requested device. * * @param packageName name of the package you are checking against * @param deviceId id of the persistent device you are checking against diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index e9a7fe1371ac..ec4b38b10af2 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -3504,7 +3504,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) { StatusBarManagerInternal statusbar = getStatusBarManagerInternal(); if (statusbar != null) { - statusbar.moveFocusedTaskToFullscreen(event.getDisplayId()); + statusbar.moveFocusedTaskToFullscreen(getTargetDisplayIdForKeyEvent(event)); logKeyboardSystemsEvent(event, KeyboardLogEvent.MULTI_WINDOW_NAVIGATION); return true; } @@ -3514,7 +3514,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) { StatusBarManagerInternal statusbar = getStatusBarManagerInternal(); if (statusbar != null) { - statusbar.enterDesktop(event.getDisplayId()); + statusbar.enterDesktop(getTargetDisplayIdForKeyEvent(event)); logKeyboardSystemsEvent(event, KeyboardLogEvent.DESKTOP_MODE); return true; } @@ -6951,4 +6951,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { == PERMISSION_GRANTED; } } + + private int getTargetDisplayIdForKeyEvent(KeyEvent event) { + int displayId = event.getDisplayId(); + + if (displayId == INVALID_DISPLAY) { + displayId = mTopFocusedDisplayId; + } + + if (displayId == INVALID_DISPLAY) { + return DEFAULT_DISPLAY; + } else { + return displayId; + } + } } diff --git a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java index 32a21c587f08..cebf7fb9a627 100644 --- a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java +++ b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java @@ -21,7 +21,6 @@ import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; import static android.Manifest.permission.WRITE_MEDIA_STORAGE; import static android.app.AppOpsManager.OP_LEGACY_STORAGE; import static android.app.AppOpsManager.OP_NONE; -import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION; import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT; import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; @@ -148,7 +147,7 @@ public abstract class SoftRestrictedPermissionPolicy { pkg.hasPreserveLegacyExternalStorage(); targetSDK = getMinimumTargetSDK(context, appInfo, user); - shouldApplyRestriction = (flags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0; + shouldApplyRestriction = !isWhiteListed; isForcedScopedStorage = sForcedScopedStorageAppWhitelist .contains(appInfo.packageName); } else { diff --git a/services/core/java/com/android/server/power/Android.bp b/services/core/java/com/android/server/power/Android.bp index 607d435b5410..863ff76cb0c7 100644 --- a/services/core/java/com/android/server/power/Android.bp +++ b/services/core/java/com/android/server/power/Android.bp @@ -9,4 +9,5 @@ aconfig_declarations { java_aconfig_library { name: "backstage_power_flags_lib", aconfig_declarations: "backstage_power_flags", + sdk_version: "system_current", } diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java index f8c678aa3aa3..52ef87cf949f 100644 --- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java +++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java @@ -45,11 +45,9 @@ import static android.hardware.SensorPrivacyManager.Sources.OTHER; import static android.hardware.SensorPrivacyManager.Sources.QS_TILE; import static android.hardware.SensorPrivacyManager.Sources.SETTINGS; import static android.hardware.SensorPrivacyManager.Sources.SHELL; -import static android.hardware.SensorPrivacyManager.StateTypes.AUTOMOTIVE_DRIVER_ASSISTANCE_APPS; -import static android.hardware.SensorPrivacyManager.StateTypes.AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS; -import static android.hardware.SensorPrivacyManager.StateTypes.AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS; import static android.hardware.SensorPrivacyManager.StateTypes.DISABLED; import static android.hardware.SensorPrivacyManager.StateTypes.ENABLED; +import static android.hardware.SensorPrivacyManager.StateTypes.ENABLED_EXCEPT_ALLOWLISTED_APPS; import static android.hardware.SensorPrivacyManager.TOGGLE_TYPE_HARDWARE; import static android.hardware.SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE; import static android.os.UserHandle.USER_NULL; @@ -57,11 +55,9 @@ import static android.service.SensorPrivacyIndividualEnabledSensorProto.UNKNOWN; import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION; import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__ACTION_UNKNOWN; -import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS; -import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS; -import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS; import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_OFF; import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON; +import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON_EXCEPT_ALLOWLISTED_APPS; import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__CAMERA; import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__MICROPHONE; import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__SENSOR_UNKNOWN; @@ -98,7 +94,6 @@ import android.content.pm.PackageManagerInternal; import android.content.res.Configuration; import android.database.ContentObserver; import android.graphics.drawable.Icon; -import android.hardware.CameraPrivacyAllowlistEntry; import android.hardware.ISensorPrivacyListener; import android.hardware.ISensorPrivacyManager; import android.hardware.SensorPrivacyManager; @@ -153,7 +148,6 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; @@ -170,18 +164,12 @@ public final class SensorPrivacyService extends SystemService { public static final int REMINDER_DIALOG_DELAY_MILLIS = 500; @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) - private static final int ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS = - PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS; - @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) - private static final int ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS = - PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS; - @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) - private static final int ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS = - PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS; - @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) private static final int ACTION__TOGGLE_ON = PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON; @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + private static final int ACTION__TOGGLE_ON_EXCEPT_ALLOWLISTED_APPS = + PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON_EXCEPT_ALLOWLISTED_APPS; + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) private static final int ACTION__TOGGLE_OFF = PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_OFF; @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) @@ -208,8 +196,7 @@ public final class SensorPrivacyService extends SystemService { private CallStateHelper mCallStateHelper; private KeyguardManager mKeyguardManager; - List<CameraPrivacyAllowlistEntry> mCameraPrivacyAllowlist = - new ArrayList<CameraPrivacyAllowlistEntry>(); + List<String> mCameraPrivacyAllowlist = new ArrayList<String>(); private int mCurrentUser = USER_NULL; @@ -227,14 +214,8 @@ public final class SensorPrivacyService extends SystemService { mPackageManagerInternal = getLocalService(PackageManagerInternal.class); mNotificationManager = mContext.getSystemService(NotificationManager.class); mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl(); - ArrayMap<String, Boolean> cameraPrivacyAllowlist = - SystemConfig.getInstance().getCameraPrivacyAllowlist(); - - for (Map.Entry<String, Boolean> entry : cameraPrivacyAllowlist.entrySet()) { - CameraPrivacyAllowlistEntry ent = new CameraPrivacyAllowlistEntry(); - ent.packageName = entry.getKey(); - ent.isMandatory = entry.getValue(); - mCameraPrivacyAllowlist.add(ent); + for (String entry : SystemConfig.getInstance().getCameraPrivacyAllowlist()) { + mCameraPrivacyAllowlist.add(entry); } } @@ -908,14 +889,8 @@ public final class SensorPrivacyService extends SystemService { case DISABLED : logAction = ACTION__TOGGLE_ON; break; - case AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS : - logAction = ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS; - break; - case AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS : - logAction = ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS; - break; - case AUTOMOTIVE_DRIVER_ASSISTANCE_APPS : - logAction = ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS; + case ENABLED_EXCEPT_ALLOWLISTED_APPS : + logAction = ACTION__TOGGLE_ON_EXCEPT_ALLOWLISTED_APPS; break; default : logAction = ACTION__ACTION_UNKNOWN; @@ -981,11 +956,23 @@ public final class SensorPrivacyService extends SystemService { @Override @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY) - public List<CameraPrivacyAllowlistEntry> getCameraPrivacyAllowlist() { + public List<String> getCameraPrivacyAllowlist() { enforceObserveSensorPrivacyPermission(); return mCameraPrivacyAllowlist; } + /** + * Sets camera privacy allowlist. + * @param allowlist List of automotive driver assistance packages for + * privacy allowlisting. + * @hide + */ + @Override + public void setCameraPrivacyAllowlist(List<String> allowlist) { + enforceManageSensorPrivacyPermission(); + mCameraPrivacyAllowlist = new ArrayList<>(allowlist); + } + @Override @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY) @@ -1005,23 +992,9 @@ public final class SensorPrivacyService extends SystemService { return true; } else if (state == DISABLED) { return false; - } else if (state == AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS) { - for (CameraPrivacyAllowlistEntry entry : mCameraPrivacyAllowlist) { - if ((packageName.equals(entry.packageName)) && !entry.isMandatory) { - return false; - } - } - return true; - } else if (state == AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS) { - for (CameraPrivacyAllowlistEntry entry : mCameraPrivacyAllowlist) { - if ((packageName.equals(entry.packageName)) && entry.isMandatory) { - return false; - } - } - return true; - } else if (state == AUTOMOTIVE_DRIVER_ASSISTANCE_APPS) { - for (CameraPrivacyAllowlistEntry entry : mCameraPrivacyAllowlist) { - if (packageName.equals(entry.packageName)) { + } else if (state == ENABLED_EXCEPT_ALLOWLISTED_APPS) { + for (String entry : mCameraPrivacyAllowlist) { + if (packageName.equals(entry)) { return false; } } @@ -1616,20 +1589,7 @@ public final class SensorPrivacyService extends SystemService { setToggleSensorPrivacy(userId, SHELL, sensor, false); } break; - case "automotive_driver_assistance_apps" : { - if (Flags.cameraPrivacyAllowlist()) { - int sensor = sensorStrToId(getNextArgRequired()); - if ((!isAutomotive(mContext)) || (sensor != CAMERA)) { - pw.println("Command not valid for this sensor"); - return -1; - } - - setToggleSensorPrivacyState(userId, SHELL, sensor, - AUTOMOTIVE_DRIVER_ASSISTANCE_APPS); - } - } - break; - case "automotive_driver_assistance_helpful_apps" : { + case "enable_except_allowlisted_apps" : { if (Flags.cameraPrivacyAllowlist()) { int sensor = sensorStrToId(getNextArgRequired()); if ((!isAutomotive(mContext)) || (sensor != CAMERA)) { @@ -1638,20 +1598,7 @@ public final class SensorPrivacyService extends SystemService { } setToggleSensorPrivacyState(userId, SHELL, sensor, - AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS); - } - } - break; - case "automotive_driver_assistance_required_apps" : { - if (Flags.cameraPrivacyAllowlist()) { - int sensor = sensorStrToId(getNextArgRequired()); - if ((!isAutomotive(mContext)) || (sensor != CAMERA)) { - pw.println("Command not valid for this sensor"); - return -1; - } - - setToggleSensorPrivacyState(userId, SHELL, sensor, - AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS); + ENABLED_EXCEPT_ALLOWLISTED_APPS); } } break; @@ -1679,18 +1626,9 @@ public final class SensorPrivacyService extends SystemService { pw.println(""); if (Flags.cameraPrivacyAllowlist()) { if (isAutomotive(mContext)) { - pw.println(" automotive_driver_assistance_apps USER_ID SENSOR"); - pw.println(" Disable privacy for automotive apps which help you" - + " drive and apps which are required by OEM"); - pw.println(""); - pw.println(" automotive_driver_assistance_helpful_apps " - + "USER_ID SENSOR"); - pw.println(" Disable privacy for automotive apps which " - + "help you drive."); - pw.println(""); - pw.println(" automotive_driver_assistance_required_apps " + pw.println(" enable_except_allowlisted_apps " + "USER_ID SENSOR"); - pw.println(" Disable privacy for automotive apps which are " + pw.println(" Enable privacy except for automotive apps which are " + "required by OEM."); pw.println(""); } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java index 601c7f450d4f..9616c286f1b3 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java @@ -39,6 +39,7 @@ import android.util.SparseArray; import android.view.DisplayInfo; import android.view.View; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.utils.TimingsTraceAndSlog; import libcore.io.IoUtils; @@ -65,7 +66,7 @@ public class WallpaperCropper { * Maximum acceptable parallax. * A value of 1 means "the additional width for parallax is at most 100% of the screen width" */ - private static final float MAX_PARALLAX = 1f; + @VisibleForTesting static final float MAX_PARALLAX = 1f; /** * We define three ways to adjust a crop. These modes are used depending on the situation: @@ -73,10 +74,9 @@ public class WallpaperCropper { * - When going from folded to unfolded, we want to add content * - For a screen rotation, we want to keep the same amount of content */ - private static final int ADD = 1; - private static final int REMOVE = 2; - private static final int BALANCE = 3; - + @VisibleForTesting static final int ADD = 1; + @VisibleForTesting static final int REMOVE = 2; + @VisibleForTesting static final int BALANCE = 3; private final WallpaperDisplayHelper mWallpaperDisplayHelper; @@ -209,7 +209,8 @@ public class WallpaperCropper { * Given a crop, a displaySize for the orientation of that crop, compute the visible part of the * crop. This removes any additional width used for parallax. No-op if displaySize == null. */ - private static Rect noParallax(Rect crop, Point displaySize, Point bitmapSize, boolean rtl) { + @VisibleForTesting + static Rect noParallax(Rect crop, Point displaySize, Point bitmapSize, boolean rtl) { if (displaySize == null) return crop; Rect adjustedCrop = getAdjustedCrop(crop, bitmapSize, displaySize, true, rtl, ADD); // only keep the visible part (without parallax) @@ -240,12 +241,13 @@ public class WallpaperCropper { * </li> * </ul> */ - private static Rect getAdjustedCrop(Rect crop, Point bitmapSize, Point screenSize, + @VisibleForTesting + static Rect getAdjustedCrop(Rect crop, Point bitmapSize, Point screenSize, boolean parallax, boolean rtl, int mode) { Rect adjustedCrop = new Rect(crop); float cropRatio = ((float) crop.width()) / crop.height(); float screenRatio = ((float) screenSize.x) / screenSize.y; - if (cropRatio >= screenRatio) { + if (cropRatio > screenRatio) { if (!parallax) { // rotate everything 90 degrees clockwise, compute the result, and rotate back int newLeft = bitmapSize.y - crop.bottom; diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java index e9c40964aee4..043470f62850 100644 --- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java +++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java @@ -30,6 +30,7 @@ import android.os.PatternMatcher; import android.os.Process; import android.os.ResultReceiver; import android.os.ShellCallback; +import android.os.SystemClock; import android.os.UserHandle; import android.util.Slog; import android.webkit.IWebViewUpdateService; @@ -37,6 +38,7 @@ import android.webkit.WebViewProviderInfo; import android.webkit.WebViewProviderResponse; import com.android.internal.util.DumpUtils; +import com.android.modules.expresslog.Histogram; import com.android.server.LocalServices; import com.android.server.SystemService; @@ -52,6 +54,14 @@ public class WebViewUpdateService extends SystemService { private static final String TAG = "WebViewUpdateService"; + private static final Histogram sPrepareWebViewInSystemServerLatency = new Histogram( + "webview.value_prepare_webview_in_system_server_latency", + new Histogram.ScaledRangeOptions(20, 0, 1, 1.5f)); + + private static final Histogram sAppWaitingForRelroCompletionDelay = new Histogram( + "webview.value_app_waiting_for_relro_completion_delay", + new Histogram.ScaledRangeOptions(20, 0, 1, 1.4f)); + private BroadcastReceiver mWebViewUpdatedReceiver; private WebViewUpdateServiceInterface mImpl; @@ -132,7 +142,10 @@ public class WebViewUpdateService extends SystemService { } public void prepareWebViewInSystemServer() { + long currentTimeMs = SystemClock.uptimeMillis(); mImpl.prepareWebViewInSystemServer(); + sPrepareWebViewInSystemServerLatency.logSample( + (float) (SystemClock.uptimeMillis() - currentTimeMs)); } private static String packageNameFromIntent(Intent intent) { @@ -204,8 +217,12 @@ public class WebViewUpdateService extends SystemService { throw new IllegalStateException("Cannot create a WebView from the SystemServer"); } + long startTimeMs = SystemClock.uptimeMillis(); final WebViewProviderResponse webViewProviderResponse = WebViewUpdateService.this.mImpl.waitForAndGetProvider(); + long endTimeMs = SystemClock.uptimeMillis(); + sAppWaitingForRelroCompletionDelay.logSample((float) (endTimeMs - startTimeMs)); + if (webViewProviderResponse.packageInfo != null) { grantVisibilityToCaller( webViewProviderResponse.packageInfo.packageName, Binder.getCallingUid()); diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java index 1d6ad6d3a6d9..532ff984ae56 100644 --- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java +++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java @@ -23,12 +23,15 @@ import android.content.pm.Signature; import android.os.AsyncTask; import android.os.Trace; import android.os.UserHandle; +import android.util.AndroidRuntimeException; import android.util.Slog; import android.webkit.UserPackage; import android.webkit.WebViewFactory; import android.webkit.WebViewProviderInfo; import android.webkit.WebViewProviderResponse; +import com.android.modules.expresslog.Counter; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -357,6 +360,12 @@ class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface { mNumRelroCreationsFinished = 0; mNumRelroCreationsStarted = mSystemInterface.onWebViewProviderChanged(newPackage); + Counter.logIncrement("webview.value_on_webview_provider_changed_counter"); + if (newPackage.packageName.equals(getDefaultWebViewPackage().packageName)) { + Counter.logIncrement( + "webview.value_on_webview_provider_changed_" + + "with_default_package_counter"); + } // If the relro creations finish before we know the number of started creations // we will have to do any cleanup/notifying here. checkIfRelrosDoneLocked(); @@ -388,9 +397,15 @@ class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface { @Override public WebViewProviderInfo getDefaultWebViewPackage() { - throw new IllegalStateException( - "getDefaultWebViewPackage shouldn't be called if update_service_v2 flag is" - + " disabled."); + for (WebViewProviderInfo provider : getWebViewPackages()) { + if (provider.availableByDefault) { + return provider; + } + } + + // This should be unreachable because the config parser enforces that there is at least + // one availableByDefault provider. + throw new AndroidRuntimeException("No available by default WebView Provider."); } private static class ProviderAndPackageInfo { diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java index 596de686089e..fb338ba245e1 100644 --- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java +++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java @@ -31,6 +31,8 @@ import android.webkit.WebViewFactory; import android.webkit.WebViewProviderInfo; import android.webkit.WebViewProviderResponse; +import com.android.modules.expresslog.Counter; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -96,6 +98,9 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { private boolean mWebViewPackageDirty = false; private boolean mAnyWebViewInstalled = false; + // Keeps track of whether we attempted to repair WebView before. + private boolean mAttemptedToRepairBefore = false; + private static final int NUMBER_OF_RELROS_UNKNOWN = Integer.MAX_VALUE; // The WebView package currently in use (or the one we are preparing). @@ -136,6 +141,7 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { boolean removedOrChangedOldPackage = false; String oldProviderName = null; PackageInfo newPackage = null; + boolean repairNeeded = false; synchronized (mLock) { try { newPackage = findPreferredWebViewPackage(); @@ -161,6 +167,7 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { Slog.e(TAG, "Could not find valid WebView package to create relro with " + e); } + repairNeeded = shouldTriggerRepairLocked(); } if (updateWebView && !removedOrChangedOldPackage && oldProviderName != null) { @@ -170,12 +177,18 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { // only kills dependents of packages that are being removed. mSystemInterface.killPackageDependents(oldProviderName); } + if (repairNeeded) { + attemptRepair(); + } return; } } } private boolean shouldTriggerRepairLocked() { + if (mAttemptedToRepairBefore) { + return false; + } if (mCurrentWebViewPackage == null) { return true; } @@ -189,6 +202,26 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { } } + private void attemptRepair() { + // We didn't find a valid WebView implementation. Try explicitly re-installing and + // re-enabling the default package for all users in case it was disabled. If this actually + // changes the state, we will see the PackageManager broadcast shortly and try again. + synchronized (mLock) { + if (mAttemptedToRepairBefore) { + return; + } + mAttemptedToRepairBefore = true; + } + Slog.w( + TAG, + "No provider available for all users, trying to install and enable " + + mDefaultProvider.packageName); + mSystemInterface.installExistingPackageForAllUsers( + mContext, mDefaultProvider.packageName); + mSystemInterface.enablePackageForAllUsers( + mContext, mDefaultProvider.packageName, true); + } + @Override public void prepareWebViewInSystemServer() { try { @@ -211,18 +244,7 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { } if (repairNeeded) { - // We didn't find a valid WebView implementation. Try explicitly re-installing and - // re-enabling the default package for all users in case it was disabled, even if we - // already did the one-time migration before. If this actually changes the state, we - // will see the PackageManager broadcast shortly and try again. - Slog.w( - TAG, - "No provider available for all users, trying to install and enable " - + mDefaultProvider.packageName); - mSystemInterface.installExistingPackageForAllUsers( - mContext, mDefaultProvider.packageName); - mSystemInterface.enablePackageForAllUsers( - mContext, mDefaultProvider.packageName, true); + attemptRepair(); } } catch (Throwable t) { @@ -332,6 +354,7 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { PackageInfo oldPackage = null; PackageInfo newPackage = null; boolean providerChanged = false; + boolean repairNeeded = false; synchronized (mLock) { oldPackage = mCurrentWebViewPackage; @@ -354,11 +377,19 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { if (providerChanged) { onWebViewProviderChanged(newPackage); } + // Choosing another provider shouldn't break our state. Only check if repair + // is needed if this function is called as a result of a user change. + if (newProviderName == null) { + repairNeeded = shouldTriggerRepairLocked(); + } } // Kill apps using the old provider only if we changed provider if (providerChanged && oldPackage != null) { mSystemInterface.killPackageDependents(oldPackage.packageName); } + if (repairNeeded) { + attemptRepair(); + } // Return the new provider, this is not necessarily the one we were asked to switch to, // but the persistent setting will now be pointing to the provider we were asked to // switch to anyway. @@ -383,6 +414,12 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { mNumRelroCreationsFinished = 0; mNumRelroCreationsStarted = mSystemInterface.onWebViewProviderChanged(newPackage); + Counter.logIncrement("webview.value_on_webview_provider_changed_counter"); + if (newPackage.packageName.equals(getDefaultWebViewPackage().packageName)) { + Counter.logIncrement( + "webview.value_on_webview_provider_changed_" + + "with_default_package_counter"); + } // If the relro creations finish before we know the number of started creations // we will have to do any cleanup/notifying here. checkIfRelrosDoneLocked(); @@ -450,6 +487,7 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { * for all users, otherwise use the default provider. */ private PackageInfo findPreferredWebViewPackage() throws WebViewPackageMissingException { + Counter.logIncrement("webview.value_find_preferred_webview_package_counter"); // If the user has chosen provider, use that (if it's installed and enabled for all // users). String userChosenPackageName = mSystemInterface.getUserChosenWebViewProvider(mContext); @@ -479,12 +517,15 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { PackageInfo packageInfo = mSystemInterface.getPackageInfoForProvider(mDefaultProvider); if (validityResult(mDefaultProvider, packageInfo) == VALIDITY_OK) { return packageInfo; + } else { + Counter.logIncrement("webview.value_default_webview_package_invalid_counter"); } } catch (NameNotFoundException e) { Slog.w(TAG, "Default WebView package (" + mDefaultProvider.packageName + ") not found"); } // This should never happen during normal operation (only with modified system images). + Counter.logIncrement("webview.value_webview_not_usable_for_all_users_counter"); mAnyWebViewInstalled = false; throw new WebViewPackageMissingException("Could not find a loadable WebView package"); } diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index d08e272d76dd..418998870f16 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -817,14 +817,9 @@ final class AccessibilityController { switch (transition) { case WindowManagerPolicy.TRANSIT_ENTER: case WindowManagerPolicy.TRANSIT_SHOW: { - if (!isMagnifierActivated) { + if (!isMagnifierActivated || !windowState.shouldMagnify()) { break; } - if (Flags.doNotCheckIntersectionWhenNonMagnifiableWindowTransitions()) { - if (!windowState.shouldMagnify()) { - break; - } - } switch (type) { case WindowManager.LayoutParams.TYPE_APPLICATION: case WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION: @@ -1214,6 +1209,7 @@ final class AccessibilityController { private boolean mShown; private boolean mLastSurfaceShown; private int mAlpha; + private int mPreviousAlpha; private volatile boolean mInvalidated; @@ -1349,6 +1345,7 @@ final class AccessibilityController { // using WindowManagerGlobalLock. Grab copies of these values before // drawing on the canvas so that drawing can be performed outside of the lock. int alpha; + boolean redrawBounds; Rect drawingRect = null; Region drawingBounds = null; synchronized (mService.mGlobalLock) { @@ -1366,7 +1363,13 @@ final class AccessibilityController { mInvalidated = false; alpha = mAlpha; - if (alpha > 0) { + // For b/325863281, we should ensure the drawn border path is cleared when + // alpha = 0. Therefore, we cache the last used alpha when drawing as + // mPreviousAlpha and check it here. If mPreviousAlpha > 0, which means + // the border is showing now, then we should still redraw the clear path + // on the canvas so the border is cleared. + redrawBounds = mAlpha > 0 || mPreviousAlpha > 0; + if (redrawBounds) { drawingBounds = new Region(mBounds); // Empty dirty rectangle means unspecified. if (mDirtyRect.isEmpty()) { @@ -1383,7 +1386,7 @@ final class AccessibilityController { final boolean showSurface; // Draw without holding WindowManagerGlobalLock. - if (alpha > 0) { + if (redrawBounds) { Canvas canvas = null; try { canvas = mSurface.lockCanvas(drawingRect); @@ -1397,11 +1400,11 @@ final class AccessibilityController { mPaint.setAlpha(alpha); canvas.drawPath(drawingBounds.getBoundaryPath(), mPaint); mSurface.unlockCanvasAndPost(canvas); - showSurface = true; - } else { - showSurface = false; + mPreviousAlpha = alpha; } + showSurface = alpha > 0; + if (showSurface && !mLastSurfaceShown) { mTransaction.show(mSurfaceControl).apply(); mLastSurfaceShown = true; diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 92fde18233a9..d30a2167a183 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -553,7 +553,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A boolean launchFailed; // set if a launched failed, to abort on 2nd try boolean delayedResume; // not yet resumed because of stopped app switches? boolean finishing; // activity in pending finish list? - int configChangeFlags; // which config values have changed private boolean keysPaused; // has key dispatching been paused for it? int launchMode; // the launch mode activity attribute. int lockTaskLaunchMode; // the lockTaskMode manifest attribute, subject to override @@ -1295,10 +1294,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (mDeferHidingClient) { pw.println(prefix + "mDeferHidingClient=" + mDeferHidingClient); } - if (configChangeFlags != 0) { - pw.print(prefix); pw.print(" configChangeFlags="); - pw.println(Integer.toHexString(configChangeFlags)); - } if (mServiceConnectionsHolder != null) { pw.print(prefix); pw.print("connections="); pw.println(mServiceConnectionsHolder); } @@ -2894,6 +2889,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A /** Makes starting window always fill the associated task. */ private void attachStartingSurfaceToAssociatedTask() { + if (mSyncState == SYNC_STATE_NONE && isEmbedded()) { + // Collect this activity since it's starting window will reparent to task. To ensure + // any starting window's transaction will occur in order. + mTransitionController.collect(this); + } // Associate the configuration of starting window with the task. overrideConfigurationPropagation(mStartingWindow, mStartingData.mAssociatedTask); getSyncTransaction().reparent(mStartingWindow.mSurfaceControl, @@ -4059,7 +4059,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A try { if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + this); mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), - DestroyActivityItem.obtain(token, finishing, configChangeFlags)); + DestroyActivityItem.obtain(token, finishing)); } catch (Exception e) { // We can just ignore exceptions here... if the process has crashed, our death // notification will clean things up. @@ -4101,8 +4101,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } - configChangeFlags = 0; - return removedFromHistory; } @@ -5021,7 +5019,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return PauseActivityItem.obtain(token); case STOPPING: case STOPPED: - return StopActivityItem.obtain(token, configChangeFlags); + return StopActivityItem.obtain(token); default: // Do not send a result immediately if the activity is in state INITIALIZING, // RESTARTING_PROCESS, FINISHING, DESTROYING, or DESTROYED. @@ -6295,7 +6293,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A try { mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), PauseActivityItem.obtain(token, finishing, false /* userLeaving */, - configChangeFlags, false /* dontReport */, mAutoEnteringPip)); + false /* dontReport */, mAutoEnteringPip)); } catch (Exception e) { Slog.w(TAG, "Exception thrown sending pause: " + intent.getComponent(), e); } @@ -6609,7 +6607,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A EventLogTags.writeWmStopActivity( mUserId, System.identityHashCode(this), shortComponentName); mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), - StopActivityItem.obtain(token, configChangeFlags)); + StopActivityItem.obtain(token)); mAtmService.mH.postDelayed(mStopTimeoutRunnable, STOP_TIMEOUT); } catch (Exception e) { @@ -6867,6 +6865,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // stop tracking mSplashScreenStyleSolidColor = true; + mAtmService.mBackNavigationController.removePredictiveSurfaceIfNeeded(this); if (mStartingWindow != null) { ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Finish starting %s" + ": first real window is shown, no animation", win.mToken); @@ -9852,7 +9851,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (shouldRelaunchLocked(changes, mTmpConfig)) { // Aha, the activity isn't handling the change, so DIE DIE DIE. - configChangeFlags |= changes; if (mVisible && mAtmService.mTmpUpdateConfigurationResult.mIsUpdating && !mTransitionController.isShellTransitionsEnabled()) { startFreezingScreenLocked(app, mAtmService.mTmpUpdateConfigurationResult.changes); @@ -9881,7 +9879,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A ProtoLog.v(WM_DEBUG_STATES, "Config is relaunching invisible " + "activity %s called by %s", this, Debug.getCallers(4)); } - relaunchActivityLocked(preserveWindow); + relaunchActivityLocked(preserveWindow, changes); // All done... tell the caller we weren't able to keep this activity around. return false; @@ -10023,9 +10021,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A | CONFIG_SCREEN_LAYOUT)) != 0; } - void relaunchActivityLocked(boolean preserveWindow) { + void relaunchActivityLocked(boolean preserveWindow, int configChangeFlags) { if (mAtmService.mSuppressResizeConfigChanges && preserveWindow) { - configChangeFlags = 0; return; } if (!preserveWindow) { @@ -10098,8 +10095,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // The activity may be waiting for stop, but that is no longer appropriate for it. mTaskSupervisor.mStoppingActivities.remove(this); - - configChangeFlags = 0; } /** @@ -10168,7 +10163,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // {@link ActivityTaskManagerService.activityStopped}). try { mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), - StopActivityItem.obtain(token, 0 /* configChanges */)); + StopActivityItem.obtain(token)); } catch (RemoteException e) { Slog.w(TAG, "Exception thrown during restart " + this, e); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 218b7512b861..e283f3e8ef0e 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -4163,19 +4163,21 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Override public void onPictureInPictureUiStateChanged(PictureInPictureUiState pipState) { enforceTaskPermission("onPictureInPictureUiStateChanged"); - // The PictureInPictureUiState is sent to current pip task if there is any - // -or- the top standard task (state like entering PiP does not require a pinned task). - final Task task; - if (mRootWindowContainer.getDefaultTaskDisplayArea().hasPinnedTask()) { - task = mRootWindowContainer.getDefaultTaskDisplayArea().getRootPinnedTask(); - } else { - task = mRootWindowContainer.getDefaultTaskDisplayArea().getRootTask( - t -> t.isActivityTypeStandard()); - } - if (task != null && task.getTopMostActivity() != null - && !task.getTopMostActivity().isState(FINISHING, DESTROYING, DESTROYED)) { - mWindowManager.mAtmService.mActivityClientController.onPictureInPictureUiStateChanged( - task.getTopMostActivity(), pipState); + synchronized (mGlobalLock) { + // The PictureInPictureUiState is sent to current pip task if there is any + // -or- the top standard task (state like entering PiP does not require a pinned task). + final Task task; + if (mRootWindowContainer.getDefaultTaskDisplayArea().hasPinnedTask()) { + task = mRootWindowContainer.getDefaultTaskDisplayArea().getRootPinnedTask(); + } else { + task = mRootWindowContainer.getDefaultTaskDisplayArea().getRootTask( + t -> t.isActivityTypeStandard()); + } + if (task != null && task.getTopMostActivity() != null + && !task.getTopMostActivity().isState(FINISHING, DESTROYING, DESTROYED)) { + mWindowManager.mAtmService.mActivityClientController + .onPictureInPictureUiStateChanged(task.getTopMostActivity(), pipState); + } } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index d1d498dbac46..2cda1f55b038 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -74,6 +74,7 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLAS import static com.android.server.wm.ActivityTaskManagerService.ANIMATE; import static com.android.server.wm.ActivityTaskManagerService.H.FIRST_SUPERVISOR_TASK_MSG; import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE; +import static com.android.server.wm.ClientLifecycleManager.shouldDispatchCompatClientTransactionIndependently; import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_ALLOWLISTED; import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE; import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE_PRIV; @@ -948,6 +949,13 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } // Schedule transaction. + if (shouldDispatchCompatClientTransactionIndependently(r.mTargetSdk)) { + // LaunchActivityItem has @UnsupportedAppUsage usages. + // Guard the bundleClientTransactionFlag feature with targetSDK on Android 15+. + // To not bundle the transaction, dispatch the pending before schedule new + // transaction. + mService.getLifecycleManager().dispatchPendingTransaction(proc.getThread()); + } mService.getLifecycleManager().scheduleTransactionAndLifecycleItems( proc.getThread(), launchActivityItem, lifecycleItem, // Immediately dispatch the transaction, so that if it fails, the server can diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index b51f89931128..e15512678104 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -61,6 +61,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.TransitionAnimation; import com.android.internal.protolog.common.ProtoLog; import com.android.server.wm.utils.InsetUtils; +import com.android.window.flags.Flags; import java.io.PrintWriter; import java.util.ArrayList; @@ -644,6 +645,10 @@ class BackNavigationController { return false; } + void removePredictiveSurfaceIfNeeded(ActivityRecord openActivity) { + mAnimationHandler.markWindowHasDrawn(openActivity); + } + private class NavigationMonitor { // The window which triggering the back navigation. private WindowState mNavigatingWindow; @@ -897,7 +902,8 @@ class BackNavigationController { mWindowManagerService = wms; final Context context = wms.mContext; mShowWindowlessSurface = context.getResources().getBoolean( - com.android.internal.R.bool.config_predictShowStartingSurface); + com.android.internal.R.bool.config_predictShowStartingSurface) + && Flags.activitySnapshotByDefault(); } private static final int UNKNOWN = 0; private static final int TASK_SWITCH = 1; @@ -1032,6 +1038,23 @@ class BackNavigationController { return isAnimateTarget(wc, mCloseAdaptor.mTarget, mSwitchType); } + void markWindowHasDrawn(ActivityRecord activity) { + if (!mComposed || mWaitTransition) { + return; + } + boolean allWindowDrawn = true; + for (int i = mOpenAnimAdaptor.mAdaptors.length - 1; i >= 0; --i) { + final BackWindowAnimationAdaptor next = mOpenAnimAdaptor.mAdaptors[i]; + if (isAnimateTarget(activity, next.mTarget, mSwitchType)) { + next.mAppWindowDrawn = true; + } + allWindowDrawn &= next.mAppWindowDrawn; + } + if (allWindowDrawn) { + mOpenAnimAdaptor.cleanUpWindowlessSurface(true); + } + } + private static boolean isAnimateTarget(@NonNull WindowContainer window, @NonNull WindowContainer animationTarget, int switchType) { if (switchType == TASK_SWITCH) { @@ -1134,15 +1157,17 @@ class BackNavigationController { final BackWindowAnimationAdaptor adaptor = new BackWindowAnimationAdaptor(target, isOpen, switchType); final SurfaceControl.Transaction pt = target.getPendingTransaction(); - target.startAnimation(pt, adaptor, false /* hidden */, ANIMATION_TYPE_PREDICT_BACK); // Workaround to show TaskFragment which can be hide in Transitions and won't show // during isAnimating. if (isOpen && target.asActivityRecord() != null) { final TaskFragment fragment = target.asActivityRecord().getTaskFragment(); if (fragment != null) { + // Ensure task fragment surface has updated, in case configuration has changed. + fragment.updateOrganizedTaskFragmentSurface(); pt.show(fragment.mSurfaceControl); } } + target.startAnimation(pt, adaptor, false /* hidden */, ANIMATION_TYPE_PREDICT_BACK); return adaptor; } @@ -1181,8 +1206,6 @@ class BackNavigationController { for (int i = mAdaptors.length - 1; i >= 0; --i) { mAdaptors[i].mTarget.cancelAnimation(); } - mRequestedStartingSurfaceId = INVALID_TASK_ID; - mStartingSurface = null; if (mCloseTransaction != null) { mCloseTransaction.apply(); mCloseTransaction = null; @@ -1235,7 +1258,7 @@ class BackNavigationController { represent.allowEnterPip); } - void createStartingSurface(ActivityRecord[] visibleOpenActivities) { + void createStartingSurface(@Nullable TaskSnapshot snapshot) { if (mAdaptors[0].mSwitchType == DIALOG_CLOSE) { return; } @@ -1253,7 +1276,6 @@ class BackNavigationController { if (mainActivity == null) { return; } - final TaskSnapshot snapshot = getSnapshot(mainOpen, visibleOpenActivities); // If there is only one adaptor, attach the windowless window to top activity, // because fixed rotation only applies on activity. // Note that embedded activity won't use fixed rotation. @@ -1321,11 +1343,13 @@ class BackNavigationController { .removeWindowlessStartingSurface(mRequestedStartingSurfaceId, !openTransitionMatch); mRequestedStartingSurfaceId = INVALID_TASK_ID; + mStartingSurface = null; } } private static class BackWindowAnimationAdaptor implements AnimationAdapter { SurfaceControl mCapturedLeash; + boolean mAppWindowDrawn; private final Rect mBounds = new Rect(); private final WindowContainer mTarget; private final boolean mIsOpen; @@ -1507,9 +1531,16 @@ class BackNavigationController { private void applyPreviewStrategy( @NonNull BackWindowAnimationAdaptorWrapper openAnimationAdaptor, @NonNull ActivityRecord[] visibleOpenActivities) { + boolean needsLaunchBehind = true; if (isSupportWindowlessSurface() && mShowWindowlessSurface && !mIsLaunchBehind) { - openAnimationAdaptor.createStartingSurface(visibleOpenActivities); - } else { + final WindowContainer mainOpen = openAnimationAdaptor.mAdaptors[0].mTarget; + final TaskSnapshot snapshot = getSnapshot(mainOpen, visibleOpenActivities); + openAnimationAdaptor.createStartingSurface(snapshot); + // set LaunchBehind if we are creating splash screen surface. + needsLaunchBehind = snapshot == null + && openAnimationAdaptor.mRequestedStartingSurfaceId != INVALID_TASK_ID; + } + if (needsLaunchBehind) { for (int i = visibleOpenActivities.length - 1; i >= 0; --i) { setLaunchBehind(visibleOpenActivities[i]); } diff --git a/services/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java index e48e4e84d60d..816fe1dbae94 100644 --- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java +++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java @@ -22,6 +22,7 @@ import android.app.servertransaction.ActivityLifecycleItem; import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.ClientTransactionItem; import android.os.Binder; +import android.os.Build; import android.os.IBinder; import android.os.RemoteException; import android.os.Trace; @@ -179,6 +180,22 @@ class ClientLifecycleManager { Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); } + /** Executes the pending transaction for the given client process. */ + void dispatchPendingTransaction(@NonNull IApplicationThread client) { + if (!Flags.bundleClientTransactionFlag()) { + return; + } + final ClientTransaction pendingTransaction = mPendingTransactions.remove(client.asBinder()); + if (pendingTransaction != null) { + try { + scheduleTransaction(pendingTransaction); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to deliver pending transaction", e); + // TODO(b/323801078): apply cleanup for individual transaction item if needed. + } + } + } + /** * Called to when {@link WindowSurfacePlacer#continueLayout}. * Dispatches all pending transactions unless there is an ongoing/scheduled layout, in which @@ -233,4 +250,17 @@ class ClientLifecycleManager { && !mWms.mWindowPlacerLocked.isTraversalScheduled() && !mWms.mWindowPlacerLocked.isInLayout(); } + + /** + * Guards the bundleClientTransactionFlag feature with targetSDK on Android 15+. + * + * Suppressing because it can't guard with @EnabledSince on VANILLA_ICE_CREAM yet since the + * version is not published. + * + * TODO(b/324203798): update in V + */ + @SuppressWarnings("AndroidFrameworkCompatChange") + static boolean shouldDispatchCompatClientTransactionIndependently(int appTargetSdk) { + return appTargetSdk <= Build.VERSION_CODES.UPSIDE_DOWN_CAKE; + } } diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java index 8717098ff8e8..a914c0712319 100644 --- a/services/core/java/com/android/server/wm/ContentRecorder.java +++ b/services/core/java/com/android/server/wm/ContentRecorder.java @@ -650,6 +650,14 @@ final class ContentRecorder implements WindowContainerListener { if (isCurrentlyRecording() && mLastRecordedBounds != null) { mMediaProjectionManager.notifyActiveProjectionCapturedContentVisibilityChanged( isVisibleRequested); + + if (mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK) { + // If capturing a task, then the toggle visibility of the recorded surface to match + // visibility of the task, so we don't capture any mid-transition frames + mRecordedWindowContainer.getSyncTransaction() + .setVisibility(mRecordedSurface, isVisibleRequested); + mRecordedWindowContainer.scheduleAnimation(); + } } } diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index ea31e632cfb8..9fee3433f6be 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -215,11 +215,11 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { * when {@link android.inputmethodservice.InputMethodService} requests to show IME * on {@param imeTarget}. * - * @param imeTarget imeTarget on which IME show request is coming from. - * @param statsToken the token tracking the current IME show request or {@code null} otherwise. + * @param imeTarget imeTarget on which IME request is coming from. + * @param statsToken the token tracking the current IME request. */ void scheduleShowImePostLayout(InsetsControlTarget imeTarget, - @Nullable ImeTracker.Token statsToken) { + @NonNull ImeTracker.Token statsToken) { boolean targetChanged = isTargetChangedWithinActivity(imeTarget); mImeRequester = imeTarget; // Cancel the pre-existing stats token, if any. diff --git a/services/core/java/com/android/server/wm/InsetsControlTarget.java b/services/core/java/com/android/server/wm/InsetsControlTarget.java index b74eb56ebdca..cc3de7a3462c 100644 --- a/services/core/java/com/android/server/wm/InsetsControlTarget.java +++ b/services/core/java/com/android/server/wm/InsetsControlTarget.java @@ -60,8 +60,8 @@ interface InsetsControlTarget { * Instructs the control target to show inset sources. * * @param types to specify which types of insets source window should be shown. - * @param fromIme {@code true} if IME show request originated from {@link InputMethodService}. - * @param statsToken the token tracking the current IME show request or {@code null} otherwise. + * @param fromIme {@code true} if the IME request originated from {@link InputMethodService}. + * @param statsToken the token tracking the current IME request or {@code null} otherwise. */ default void showInsets(@InsetsType int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) { @@ -71,8 +71,8 @@ interface InsetsControlTarget { * Instructs the control target to hide inset sources. * * @param types to specify which types of insets source window should be hidden. - * @param fromIme {@code true} if IME hide request originated from {@link InputMethodService}. - * @param statsToken the token tracking the current IME hide request or {@code null} otherwise. + * @param fromIme {@code true} if the IME request originated from {@link InputMethodService}. + * @param statsToken the token tracking the current IME request or {@code null} otherwise. */ default void hideInsets(@InsetsType int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) { diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 85d81c4db2ca..78ababc6473f 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -1881,7 +1881,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { mAtmService.getLifecycleManager().scheduleTransactionItem(prev.app.getThread(), PauseActivityItem.obtain(prev.token, prev.finishing, userLeaving, - prev.configChangeFlags, pauseImmediately, autoEnteringPip)); + pauseImmediately, autoEnteringPip)); } catch (Exception e) { // Ignore exception, if process died other code will cleanup. Slog.w(TAG, "Exception thrown during pause", e); diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 594043d380c9..9b19a707d7be 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -349,7 +349,7 @@ class WallpaperController { final Rect lastWallpaperBounds = wallpaperWin.getParentFrame(); int screenWidth = lastWallpaperBounds.width(); int screenHeight = lastWallpaperBounds.height(); - float screenRatio = ((float) screenWidth) / screenHeight; + float screenRatio = (float) screenWidth / screenHeight; Point screenSize = new Point(screenWidth, screenHeight); WallpaperWindowToken token = wallpaperWin.mToken.asWallpaperToken(); @@ -399,20 +399,32 @@ class WallpaperController { Point bitmapSize = new Point( wallpaperWin.mRequestedWidth, wallpaperWin.mRequestedHeight); SparseArray<Rect> cropHints = token.getCropHints(); - wallpaperFrame = mWallpaperCropUtils.getCrop( - screenSize, bitmapSize, cropHints, wallpaperWin.isRtl()); - - cropZoom = wallpaperFrame.isEmpty() ? 1f - : ((float) screenHeight) / wallpaperFrame.height() / wallpaperWin.mVScale; - - // A positive x / y offset shifts the wallpaper to the right / bottom respectively. - cropOffsetX = -wallpaperFrame.left - + (int) ((cropZoom - 1f) * wallpaperFrame.height() * screenRatio / 2f); - cropOffsetY = -wallpaperFrame.top - + (int) ((cropZoom - 1f) * wallpaperFrame.height() / 2f); - - diffWidth = (int) (wallpaperFrame.width() * wallpaperWin.mHScale) - screenWidth; - diffHeight = (int) (wallpaperFrame.height() * wallpaperWin.mVScale) - screenHeight; + wallpaperFrame = bitmapSize.x <= 0 || bitmapSize.y <= 0 ? wallpaperWin.getFrame() + : mWallpaperCropUtils.getCrop(screenSize, bitmapSize, cropHints, + wallpaperWin.isRtl()); + int frameWidth = wallpaperFrame.width(); + int frameHeight = wallpaperFrame.height(); + float frameRatio = (float) frameWidth / frameHeight; + + // If the crop is proportionally wider/taller than the screen, scale it so that its + // height/width matches the screen height/width, and use the additional width/height + // for parallax (respectively). + boolean scaleHeight = frameRatio >= screenRatio; + cropZoom = wallpaperFrame.isEmpty() ? 1f : scaleHeight + ? (float) screenHeight / frameHeight / wallpaperWin.mVScale + : (float) screenWidth / frameWidth / wallpaperWin.mHScale; + + // The dimensions of the frame, without the additional width or height for parallax. + float w = scaleHeight ? frameHeight * screenRatio : frameWidth; + float h = scaleHeight ? frameHeight : frameWidth / screenRatio; + + // Note: a positive x/y offset shifts the wallpaper to the right/bottom respectively. + cropOffsetX = -wallpaperFrame.left + (int) ((cropZoom - 1f) * w / 2f); + cropOffsetY = -wallpaperFrame.top + (int) ((cropZoom - 1f) * h / 2f); + + // Available width or height for parallax + diffWidth = (int) ((frameWidth - w) * wallpaperWin.mHScale); + diffHeight = (int) ((frameHeight - h) * wallpaperWin.mVScale); } else { wallpaperFrame = wallpaperWin.getFrame(); cropZoom = 1f; diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 5df2edc808f6..77319cc0ba8a 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -829,20 +829,20 @@ public abstract class WindowManagerInternal { * Show IME on imeTargetWindow once IME has finished layout. * * @param imeTargetWindowToken token of the (IME target) window which IME should be shown. - * @param statsToken the token tracking the current IME show request or {@code null} otherwise. + * @param statsToken the token tracking the current IME request. */ public abstract void showImePostLayout(IBinder imeTargetWindowToken, - @Nullable ImeTracker.Token statsToken); + @NonNull ImeTracker.Token statsToken); /** * Hide IME using imeTargetWindow when requested. * - * @param imeTargetWindowToken token of the (IME target) window on which requests hiding IME. + * @param imeTargetWindowToken token of the (IME target) window which requests hiding IME. * @param displayId the id of the display the IME is on. - * @param statsToken the token tracking the current IME hide request or {@code null} otherwise. + * @param statsToken the token tracking the current IME request. */ public abstract void hideIme(IBinder imeTargetWindowToken, int displayId, - @Nullable ImeTracker.Token statsToken); + @NonNull ImeTracker.Token statsToken); /** * Tell window manager about a package that should be running with a restricted range of diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 7e06129832a8..ae5a5cb7316c 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -8258,12 +8258,17 @@ public class WindowManagerService extends IWindowManager.Stub @Override public void showImePostLayout(IBinder imeTargetWindowToken, - @Nullable ImeTracker.Token statsToken) { + @NonNull ImeTracker.Token statsToken) { synchronized (mGlobalLock) { InputTarget imeTarget = getInputTargetFromWindowTokenLocked(imeTargetWindowToken); if (imeTarget == null) { + ImeTracker.forLogging().onFailed(statsToken, + ImeTracker.PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET); return; } + ImeTracker.forLogging().onProgress(statsToken, + ImeTracker.PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET); + Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0); final InsetsControlTarget controlTarget = imeTarget.getImeControlTarget(); imeTarget = controlTarget.getWindow(); @@ -8278,7 +8283,7 @@ public class WindowManagerService extends IWindowManager.Stub @Override public void hideIme(IBinder imeTargetWindowToken, int displayId, - @Nullable ImeTracker.Token statsToken) { + @NonNull ImeTracker.Token statsToken) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.hideIme"); synchronized (mGlobalLock) { WindowState imeTarget = mWindowMap.get(imeTargetWindowToken); diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index d6fc01aeadd2..a7eb444ecb59 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -329,15 +329,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub deferred); wctApplied.meet(); if (needsSetReady) { - // TODO(b/294925498): Remove this once we have accurate ready - // tracking. - if (hasActivityLaunch(wct) && !mService.mRootWindowContainer - .allPausedActivitiesComplete()) { - // WCT is launching an activity, so we need to wait for its - // lifecycle events. - return; - } - nextTransition.setAllReady(); + setAllReadyIfNeeded(nextTransition, wct); } }); return nextTransition.getToken(); @@ -390,7 +382,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } } - private static boolean hasActivityLaunch(WindowContainerTransaction wct) { + private static boolean hasActivityLaunch(@NonNull WindowContainerTransaction wct) { for (int i = 0; i < wct.getHierarchyOps().size(); ++i) { if (wct.getHierarchyOps().get(i).getType() == HIERARCHY_OP_TYPE_LAUNCH_TASK) { return true; @@ -399,6 +391,46 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub return false; } + private boolean isCreatedTaskFragmentReady(@NonNull WindowContainerTransaction wct) { + for (int i = 0; i < wct.getHierarchyOps().size(); ++i) { + final WindowContainerTransaction.HierarchyOp op = wct.getHierarchyOps().get(i); + if (op.getType() != HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION + || op.getTaskFragmentOperation().getOpType() + != OP_TYPE_CREATE_TASK_FRAGMENT) { + continue; + } + final IBinder tfToken = op.getTaskFragmentOperation() + .getTaskFragmentCreationParams().getFragmentToken(); + final TaskFragment taskFragment = getTaskFragment(tfToken); + if (taskFragment != null && !taskFragment.isReadyToTransit()) { + return false; + } + } + return true; + } + + private void setAllReadyIfNeeded(@NonNull Transition transition, + @NonNull WindowContainerTransaction wct) { + // TODO(b/294925498): Remove this once we have accurate ready tracking. + if (hasActivityLaunch(wct) && !mService.mRootWindowContainer + .allPausedActivitiesComplete()) { + // WCT is launching an activity, so we need to wait for its + // lifecycle events. + return; + } + if (!isCreatedTaskFragmentReady(wct)) { + // When the organizer intercepts a #startActivity, it will create an empty TaskFragment + // for that specific incoming starting activity. We don't want to set all ready here, + // because we requires that #startActivity to be included in this transition, and NOT be + // in its own transition. + // TODO(b/232042367): explicitly ensure the #startActivity and this transaction are in + // the same transition instead of relying on this possible racing condition. + return; + } + + transition.setAllReady(); + } + @Override public int startLegacyTransition(int type, @NonNull RemoteAnimationAdapter adapter, @NonNull IWindowContainerTransactionCallback callback, @@ -529,7 +561,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } mTransitionController.requestStartTransition(transition, null /* startTask */, remoteTransition, null /* displayChange */); - transition.setAllReady(); + setAllReadyIfNeeded(transition, wct); }; mTransitionController.startCollectOrQueue(transition, doApply); } finally { diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java index 6d5fc80b8d1e..b0e71bda787a 100644 --- a/services/core/java/com/android/server/wm/WindowTracing.java +++ b/services/core/java/com/android/server/wm/WindowTracing.java @@ -109,6 +109,9 @@ class WindowTracing { return; } synchronized (mEnabledLock) { + if (!android.tracing.Flags.perfettoProtologTracing()) { + ((LegacyProtoLogImpl) ProtoLog.getSingleInstance()).startProtoLog(pw); + } logAndPrintln(pw, "Start tracing to " + mTraceFile + "."); mBuffer.resetBuffer(); mEnabled = mEnabledLockFree = true; @@ -136,6 +139,9 @@ class WindowTracing { writeTraceToFileLocked(); logAndPrintln(pw, "Trace written to " + mTraceFile + "."); } + if (!android.tracing.Flags.perfettoProtologTracing()) { + ((LegacyProtoLogImpl) ProtoLog.getSingleInstance()).stopProtoLog(pw, true); + } } /** diff --git a/services/core/jni/com_android_server_hint_HintManagerService.cpp b/services/core/jni/com_android_server_hint_HintManagerService.cpp index ccd9bd0a50ca..b2bdaa35f28c 100644 --- a/services/core/jni/com_android_server_hint_HintManagerService.cpp +++ b/services/core/jni/com_android_server_hint_HintManagerService.cpp @@ -24,16 +24,17 @@ #include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedPrimitiveArray.h> #include <powermanager/PowerHalController.h> +#include <powermanager/PowerHintSessionWrapper.h> #include <utils/Log.h> #include <unordered_map> #include "jni.h" -using aidl::android::hardware::power::IPowerHintSession; using aidl::android::hardware::power::SessionHint; using aidl::android::hardware::power::SessionMode; using aidl::android::hardware::power::WorkDuration; +using android::power::PowerHintSessionWrapper; using android::base::StringPrintf; @@ -49,7 +50,7 @@ static struct { } gWorkDurationInfo; static power::PowerHalController gPowerHalController; -static std::unordered_map<jlong, std::shared_ptr<IPowerHintSession>> gSessionMap; +static std::unordered_map<jlong, std::shared_ptr<PowerHintSessionWrapper>> gSessionMap; static std::mutex gSessionMapLock; static int64_t getHintSessionPreferredRate() { @@ -76,45 +77,45 @@ static jlong createHintSession(JNIEnv* env, int32_t tgid, int32_t uid, } static void pauseHintSession(JNIEnv* env, int64_t session_ptr) { - auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr); + auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr); appSession->pause(); } static void resumeHintSession(JNIEnv* env, int64_t session_ptr) { - auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr); + auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr); appSession->resume(); } static void closeHintSession(JNIEnv* env, int64_t session_ptr) { - auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr); + auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr); appSession->close(); std::unique_lock<std::mutex> sessionLock(gSessionMapLock); gSessionMap.erase(session_ptr); } static void updateTargetWorkDuration(int64_t session_ptr, int64_t targetDurationNanos) { - auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr); + auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr); appSession->updateTargetWorkDuration(targetDurationNanos); } static void reportActualWorkDuration(int64_t session_ptr, const std::vector<WorkDuration>& actualDurations) { - auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr); + auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr); appSession->reportActualWorkDuration(actualDurations); } static void sendHint(int64_t session_ptr, SessionHint hint) { - auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr); + auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr); appSession->sendHint(hint); } static void setThreads(int64_t session_ptr, const std::vector<int32_t>& threadIds) { - auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr); + auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr); appSession->setThreads(threadIds); } static void setMode(int64_t session_ptr, SessionMode mode, bool enabled) { - auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr); + auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr); appSession->setMode(mode, enabled); } diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java index 027337f04b6e..f5e1e41dbae4 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java @@ -113,8 +113,8 @@ public class CredentialManagerUi { /** Creates intent that is ot be invoked to cancel an in-progress UI session. */ public Intent createCancelIntent(IBinder requestId, String packageName) { - return IntentFactory.createCancelUiIntent(requestId, /*shouldShowCancellationUi=*/ true, - packageName); + return IntentFactory.createCancelUiIntent(mContext, requestId, + /*shouldShowCancellationUi=*/ true, packageName); } /** diff --git a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java index 3cb98eb4cd7a..eff53de75ff4 100644 --- a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java @@ -41,6 +41,8 @@ import android.service.credentials.CredentialProviderService; import android.service.credentials.PermissionUtils; import android.util.Slog; +import com.android.server.credentials.metrics.ApiStatus; + import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -180,7 +182,7 @@ public class GetCandidateRequestSession extends RequestSession<GetCredentialRequ } else { Slog.w(TAG, "onUiCancellation called but finalResponseReceiver not found"); } - finishSession(/*propagateCancellation=*/false); + finishSession(/*propagateCancellation=*/false, ApiStatus.FAILURE.getMetricCode()); } @Override @@ -221,9 +223,10 @@ public class GetCandidateRequestSession extends RequestSession<GetCredentialRequ resultData.putParcelable( CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE, response); mFinalResponseReceiver.send(Constants.SUCCESS_CREDMAN_SELECTOR, resultData); - finishSession(/*propagateCancellation=*/ false); + finishSession(/*propagateCancellation=*/ false, ApiStatus.SUCCESS.getMetricCode()); } else { Slog.w(TAG, "onFinalResponseReceived result receiver not found for pinned entry"); + finishSession(/*propagateCancellation=*/ false, ApiStatus.FAILURE.getMetricCode()); } } diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java index 633c9c4cb8e1..a5b9aa68b22e 100644 --- a/services/credentials/java/com/android/server/credentials/RequestSession.java +++ b/services/credentials/java/com/android/server/credentials/RequestSession.java @@ -175,7 +175,7 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential () -> { Slog.d(TAG, "Cancellation invoked from the client - clearing session"); boolean isUiActive = maybeCancelUi(); - finishSession(!isUiActive); + finishSession(!isUiActive, ApiStatus.CLIENT_CANCELED.getMetricCode()); } ); } @@ -231,7 +231,8 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential return; } if (isSessionCancelled()) { - finishSession(/*propagateCancellation=*/true); + finishSession(/*propagateCancellation=*/true, + ApiStatus.CLIENT_CANCELED.getMetricCode()); return; } String providerId = selection.getProviderId(); @@ -257,11 +258,12 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential } } - protected void finishSession(boolean propagateCancellation) { + protected void finishSession(boolean propagateCancellation, int apiStatus) { Slog.i(TAG, "finishing session with propagateCancellation " + propagateCancellation); if (propagateCancellation) { mProviders.values().forEach(ProviderSession::cancelProviderRemoteSession); } + mRequestSessionMetric.logApiCalledAtFinish(apiStatus); mRequestSessionStatus = RequestSessionStatus.COMPLETE; mProviders.clear(); clearRequestSessionLocked(); @@ -326,7 +328,8 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential mRequestSessionMetric.logCandidatePhaseMetrics(mProviders); if (isSessionCancelled()) { - finishSession(/*propagateCancellation=*/true); + finishSession(/*propagateCancellation=*/true, + ApiStatus.CLIENT_CANCELED.getMetricCode()); return providerDataList; } @@ -353,23 +356,20 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential return; } if (isSessionCancelled()) { - mRequestSessionMetric.logApiCalledAtFinish( - /*apiStatus=*/ ApiStatus.CLIENT_CANCELED.getMetricCode()); - finishSession(/*propagateCancellation=*/true); + finishSession(/*propagateCancellation=*/true, + ApiStatus.CLIENT_CANCELED.getMetricCode()); return; } try { invokeClientCallbackSuccess(response); - mRequestSessionMetric.logApiCalledAtFinish( - /*apiStatus=*/ ApiStatus.SUCCESS.getMetricCode()); + finishSession(/*propagateCancellation=*/false, + ApiStatus.SUCCESS.getMetricCode()); } catch (RemoteException e) { mRequestSessionMetric.collectFinalPhaseProviderMetricStatus( /*has_exception=*/ true, ProviderStatusForMetrics.FINAL_FAILURE); Slog.e(TAG, "Issue while responding to client with a response : " + e); - mRequestSessionMetric.logApiCalledAtFinish( - /*apiStatus=*/ ApiStatus.FAILURE.getMetricCode()); + finishSession(/*propagateCancellation=*/false, ApiStatus.FAILURE.getMetricCode()); } - finishSession(/*propagateCancellation=*/false); } /** @@ -387,9 +387,7 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential return; } if (isSessionCancelled()) { - mRequestSessionMetric.logApiCalledAtFinish( - /*apiStatus=*/ ApiStatus.CLIENT_CANCELED.getMetricCode()); - finishSession(/*propagateCancellation=*/true); + finishSession(/*propagateCancellation=*/true, ApiStatus.CLIENT_CANCELED.getMetricCode()); return; } @@ -399,8 +397,14 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential Slog.e(TAG, "Issue while responding to client with error : " + e); } boolean isUserCanceled = errorType.contains(MetricUtilities.USER_CANCELED_SUBSTRING); - mRequestSessionMetric.logFailureOrUserCancel(isUserCanceled); - finishSession(/*propagateCancellation=*/false); + if (isUserCanceled) { + mRequestSessionMetric.setHasExceptionFinalPhase(/* has_exception */ false); + finishSession(/*propagateCancellation=*/false, + ApiStatus.USER_CANCELED.getMetricCode()); + } else { + finishSession(/*propagateCancellation=*/false, + ApiStatus.FAILURE.getMetricCode()); + } } /** @@ -419,7 +423,7 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential @Override public void binderDied() { Slog.d(TAG, "Client binder died - clearing session"); - finishSession(isUiWaitingForData()); + finishSession(isUiWaitingForData(), ApiStatus.CLIENT_CANCELED.getMetricCode()); } } } diff --git a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java index 8adcfbcb974c..a77bd3e280dd 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java @@ -247,7 +247,7 @@ public class RequestSessionMetric { * * @param exceptionBitFinalPhase represents if the final phase provider had an exception */ - private void setHasExceptionFinalPhase(boolean exceptionBitFinalPhase) { + public void setHasExceptionFinalPhase(boolean exceptionBitFinalPhase) { try { mChosenProviderFinalPhaseMetric.setHasException(exceptionBitFinalPhase); } catch (Exception e) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java index 12f44074a4ad..6aeb4fd53905 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java @@ -225,7 +225,7 @@ final class DevicePolicyEngine { synchronized (mLock) { PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId); - if (Flags.devicePolicySizeTrackingEnabled() && false) { + if (Flags.devicePolicySizeTrackingInternalEnabled()) { if (!handleAdminPolicySizeLimit(localPolicyState, enforcingAdmin, value, policyDefinition, userId)) { return; @@ -350,7 +350,7 @@ final class DevicePolicyEngine { } PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId); - if (Flags.devicePolicySizeTrackingEnabled() && false) { + if (Flags.devicePolicySizeTrackingInternalEnabled()) { decreasePolicySizeForAdmin(localPolicyState, enforcingAdmin); } @@ -496,7 +496,7 @@ final class DevicePolicyEngine { synchronized (mLock) { PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition); - if (Flags.devicePolicySizeTrackingEnabled() && false) { + if (Flags.devicePolicySizeTrackingInternalEnabled()) { if (!handleAdminPolicySizeLimit(globalPolicyState, enforcingAdmin, value, policyDefinition, UserHandle.USER_ALL)) { return; @@ -568,7 +568,7 @@ final class DevicePolicyEngine { synchronized (mLock) { PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition); - if (Flags.devicePolicySizeTrackingEnabled() && false) { + if (Flags.devicePolicySizeTrackingInternalEnabled()) { decreasePolicySizeForAdmin(policyState, enforcingAdmin); } @@ -1892,7 +1892,7 @@ final class DevicePolicyEngine { private void writeEnforcingAdminSizeInner(TypedXmlSerializer serializer) throws IOException { - if (Flags.devicePolicySizeTrackingEnabled() && false) { + if (Flags.devicePolicySizeTrackingInternalEnabled()) { if (mAdminPolicySize != null) { for (int i = 0; i < mAdminPolicySize.size(); i++) { int userId = mAdminPolicySize.keyAt(i); @@ -1916,7 +1916,7 @@ final class DevicePolicyEngine { private void writeMaxPolicySizeInner(TypedXmlSerializer serializer) throws IOException { - if (!Flags.devicePolicySizeTrackingEnabled() || true) { + if (!Flags.devicePolicySizeTrackingInternalEnabled()) { return; } serializer.startTag(/* namespace= */ null, TAG_MAX_POLICY_SIZE_LIMIT); @@ -2081,7 +2081,7 @@ final class DevicePolicyEngine { private void readMaxPolicySizeInner(TypedXmlPullParser parser) throws XmlPullParserException, IOException { - if (!Flags.devicePolicySizeTrackingEnabled() || true) { + if (!Flags.devicePolicySizeTrackingInternalEnabled()) { return; } mPolicySizeLimit = parser.getAttributeInt(/* namespace= */ null, ATTR_POLICY_SUM_SIZE); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 0f97f4a7cdc0..c37946b4d750 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -27,6 +27,7 @@ import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_SECU import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AIRPLANE_MODE; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_APPS_CONTROL; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_RESTRICTIONS; +import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ASSIST_CONTENT; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIO_OUTPUT; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AUTOFILL; @@ -83,7 +84,6 @@ import static android.Manifest.permission.MANAGE_DEVICE_POLICY_THREAD_NETWORK; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_TIME; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER; -import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ASSIST_CONTENT; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_VPN; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WALLPAPER; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WIFI; @@ -196,11 +196,11 @@ import static android.app.admin.DevicePolicyManager.STATUS_CANNOT_ADD_MANAGED_PR import static android.app.admin.DevicePolicyManager.STATUS_DEVICE_ADMIN_NOT_SUPPORTED; import static android.app.admin.DevicePolicyManager.STATUS_HAS_DEVICE_OWNER; import static android.app.admin.DevicePolicyManager.STATUS_HAS_PAIRED; +import static android.app.admin.DevicePolicyManager.STATUS_HEADLESS_ONLY_SYSTEM_USER; import static android.app.admin.DevicePolicyManager.STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED; import static android.app.admin.DevicePolicyManager.STATUS_MANAGED_USERS_NOT_SUPPORTED; import static android.app.admin.DevicePolicyManager.STATUS_NONSYSTEM_USER_EXISTS; import static android.app.admin.DevicePolicyManager.STATUS_NOT_SYSTEM_USER; -import static android.app.admin.DevicePolicyManager.STATUS_HEADLESS_ONLY_SYSTEM_USER; import static android.app.admin.DevicePolicyManager.STATUS_OK; import static android.app.admin.DevicePolicyManager.STATUS_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS; import static android.app.admin.DevicePolicyManager.STATUS_SYSTEM_USER; @@ -12062,7 +12062,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } if (packageList != null) { - if (!Flags.devicePolicySizeTrackingEnabled()) { + if (!Flags.devicePolicySizeTrackingInternalEnabled()) { for (String pkg : packageList) { PolicySizeVerifier.enforceMaxPackageNameLength(pkg); } @@ -13771,7 +13771,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return; } - if (!Flags.devicePolicySizeTrackingEnabled()) { + if (!Flags.devicePolicySizeTrackingInternalEnabled()) { PolicySizeVerifier.enforceMaxStringLength(accountType, "account type"); } @@ -14385,7 +14385,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { public void setLockTaskPackages(ComponentName who, String callerPackageName, String[] packages) throws SecurityException { Objects.requireNonNull(packages, "packages is null"); - if (!Flags.devicePolicySizeTrackingEnabled()) { + if (!Flags.devicePolicySizeTrackingInternalEnabled()) { for (String pkg : packages) { PolicySizeVerifier.enforceMaxPackageNameLength(pkg); } @@ -23389,7 +23389,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG); } - private boolean isUnicornFlagEnabled() { + static boolean isUnicornFlagEnabled() { return false; } @@ -24235,7 +24235,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setMaxPolicyStorageLimit(String callerPackageName, int storageLimit) { - if (!Flags.devicePolicySizeTrackingEnabled() || true) { + if (!Flags.devicePolicySizeTrackingInternalEnabled()) { return; } CallerIdentity caller = getCallerIdentity(callerPackageName); @@ -24247,7 +24247,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public int getMaxPolicyStorageLimit(String callerPackageName) { - if (!Flags.devicePolicySizeTrackingEnabled() || true) { + if (!Flags.devicePolicySizeTrackingInternalEnabled()) { return -1; } CallerIdentity caller = getCallerIdentity(callerPackageName); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java index c108deaf33bc..a7adc5b6d925 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java @@ -66,6 +66,10 @@ final class PolicyEnforcerCallbacks { private static final String LOG_TAG = "PolicyEnforcerCallbacks"; static boolean setAutoTimezoneEnabled(@Nullable Boolean enabled, @NonNull Context context) { + if (!DevicePolicyManagerService.isUnicornFlagEnabled()) { + Slogf.w(LOG_TAG, "Trying to enforce setAutoTimezoneEnabled while flag is off."); + return true; + } return Binder.withCleanCallingIdentity(() -> { Objects.requireNonNull(context); @@ -79,6 +83,10 @@ final class PolicyEnforcerCallbacks { static boolean setPermissionGrantState( @Nullable Integer grantState, @NonNull Context context, int userId, @NonNull PolicyKey policyKey) { + if (!DevicePolicyManagerService.isUnicornFlagEnabled()) { + Slogf.w(LOG_TAG, "Trying to enforce setPermissionGrantState while flag is off."); + return true; + } return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> { if (!(policyKey instanceof PackagePermissionPolicyKey)) { throw new IllegalArgumentException("policyKey is not of type " diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java index 82d5247ebed8..209107e50902 100644 --- a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java @@ -37,6 +37,7 @@ import android.os.Handler; import android.util.ArraySet; import android.util.Dumpable; import android.view.Display; +import android.view.DisplayInfo; import android.view.Surface; import com.android.server.policy.BookStylePreferredScreenCalculator.PreferredScreen; @@ -65,6 +66,7 @@ public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceSt private final Handler mHandler = new Handler(); private final PostureEstimator mPostureEstimator; private final DisplayManager mDisplayManager; + private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo(); /** * Creates {@link BookStyleClosedStatePredicate}. It is expected that the device has a pair @@ -140,10 +142,11 @@ public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceSt public void onDisplayChanged(int displayId) { if (displayId == DEFAULT_DISPLAY) { final Display display = mDisplayManager.getDisplay(displayId); + display.getDisplayInfo(mDefaultDisplayInfo); int displayState = display.getState(); boolean isDisplayOn = displayState == Display.STATE_ON; mPostureEstimator.onDisplayPowerStatusChanged(isDisplayOn); - mPostureEstimator.onDisplayRotationChanged(display.getRotation()); + mPostureEstimator.onDisplayRotationChanged(mDefaultDisplayInfo.rotation); } } diff --git a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java index 8d01b7a9c523..901f24dd9b0b 100644 --- a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java +++ b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java @@ -48,6 +48,7 @@ import android.os.Handler; import android.testing.AndroidTestingRunner; import android.testing.TestableContext; import android.view.Display; +import android.view.DisplayInfo; import android.view.Surface; import androidx.test.platform.app.InstrumentationRegistry; @@ -629,7 +630,11 @@ public final class BookStyleDeviceStatePolicyTest { } private void sendScreenRotation(int rotation) { - when(mDisplay.getRotation()).thenReturn(rotation); + doAnswer(invocation -> { + final DisplayInfo displayInfo = invocation.getArgument(0); + displayInfo.rotation = rotation; + return null; + }).when(mDisplay).getDisplayInfo(any()); mDisplayListenerCaptor.getAllValues().forEach((l) -> l.onDisplayChanged(DEFAULT_DISPLAY)); } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index e19f08cb04a1..9d95c5b4f649 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -2774,9 +2774,12 @@ public final class SystemServer implements Dumpable { t.traceEnd(); // OnDevicePersonalizationSystemService - t.traceBegin("StartOnDevicePersonalizationSystemService"); - mSystemServiceManager.startService(ON_DEVICE_PERSONALIZATION_SYSTEM_SERVICE_CLASS); - t.traceEnd(); + if (!com.android.server.flags.Flags.enableOdpFeatureGuard() + || SystemProperties.getBoolean("ro.system_settings.service.odp_enabled", true)) { + t.traceBegin("StartOnDevicePersonalizationSystemService"); + mSystemServiceManager.startService(ON_DEVICE_PERSONALIZATION_SYSTEM_SERVICE_CLASS); + t.traceEnd(); + } // Profiling if (android.server.Flags.telemetryApisService()) { diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt index fc2eb2652d2d..c0d988d0c46b 100644 --- a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt +++ b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt @@ -20,27 +20,63 @@ import android.app.AppOpsManager import android.companion.virtual.VirtualDeviceManager import android.os.Handler import android.os.UserHandle +import android.permission.flags.Flags import android.util.ArrayMap import android.util.ArraySet +import android.util.LongSparseArray +import android.util.Slog +import android.util.SparseArray import android.util.SparseBooleanArray import android.util.SparseIntArray import com.android.internal.annotations.VisibleForTesting +import com.android.internal.util.IntPair import com.android.server.appop.AppOpsCheckingServiceInterface import com.android.server.appop.AppOpsCheckingServiceInterface.AppOpsModeChangedListener import com.android.server.permission.access.AccessCheckingService import com.android.server.permission.access.AppOpUri +import com.android.server.permission.access.DevicePermissionUri +import com.android.server.permission.access.GetStateScope import com.android.server.permission.access.PackageUri +import com.android.server.permission.access.PermissionUri import com.android.server.permission.access.UidUri +import com.android.server.permission.access.appop.AppOpModes.MODE_ALLOWED +import com.android.server.permission.access.appop.AppOpModes.MODE_FOREGROUND +import com.android.server.permission.access.appop.AppOpModes.MODE_IGNORED import com.android.server.permission.access.collection.forEachIndexed import com.android.server.permission.access.collection.set +import com.android.server.permission.access.permission.AppIdPermissionPolicy +import com.android.server.permission.access.permission.DevicePermissionPolicy +import com.android.server.permission.access.permission.PermissionFlags +import com.android.server.permission.access.permission.PermissionService class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingServiceInterface { private val packagePolicy = service.getSchemePolicy(PackageUri.SCHEME, AppOpUri.SCHEME) as PackageAppOpPolicy private val appIdPolicy = service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as AppIdAppOpPolicy + private val permissionPolicy = + service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as AppIdPermissionPolicy + private val devicePermissionPolicy = + service.getSchemePolicy(UidUri.SCHEME, DevicePermissionUri.SCHEME) as DevicePermissionPolicy private val context = service.context + + // Maps appop code to its runtime permission + private val runtimeAppOpToPermissionNames = SparseArray<String>() + + // Maps runtime permission to its appop codes + private val runtimePermissionNameToAppOp = ArrayMap<String, Int>() + + private var foregroundableOps = SparseBooleanArray() + + /* Maps foreground permissions to their background permission. Background permissions aren't + required to be runtime */ + private val foregroundToBackgroundPermissionName = ArrayMap<String, String>() + + /* Maps background permissions to their foreground permissions. Background permissions aren't + required to be runtime */ + private val backgroundToForegroundPermissionNames = ArrayMap<String, ArraySet<String>>() + private lateinit var handler: Handler @Volatile private var listeners = ArraySet<AppOpsModeChangedListener>() @@ -69,11 +105,60 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS } override fun systemReady() { - // Not implemented because upgrades are handled automatically. + if (Flags.runtimePermissionAppopsMappingEnabled()) { + createPermissionAppOpMapping() + val permissionListener = OnPermissionFlagsChangedListener() + permissionPolicy.addOnPermissionFlagsChangedListener(permissionListener) + devicePermissionPolicy.addOnPermissionFlagsChangedListener(permissionListener) + } + } + + private fun createPermissionAppOpMapping() { + val permissions = service.getState { with(permissionPolicy) { getPermissions() } } + + for (appOpCode in 0 until AppOpsManager._NUM_OP) { + AppOpsManager.opToPermission(appOpCode)?.let { permissionName -> + // Multiple ops might map to a single permission but only one is considered the + // runtime appop calculations. + if (appOpCode == AppOpsManager.permissionToOpCode(permissionName)) { + val permission = permissions[permissionName]!! + if (permission.isRuntime) { + runtimePermissionNameToAppOp[permissionName] = appOpCode + runtimeAppOpToPermissionNames[appOpCode] = permissionName + permission.permissionInfo.backgroundPermission?.let { + backgroundPermissionName -> + // Note: background permission may not be runtime, + // e.g. microphone/camera. + foregroundableOps[appOpCode] = true + foregroundToBackgroundPermissionName[permissionName] = + backgroundPermissionName + backgroundToForegroundPermissionNames + .getOrPut(backgroundPermissionName, ::ArraySet) + .add(permissionName) + } + } + } + } + } } override fun getNonDefaultUidModes(uid: Int, persistentDeviceId: String): SparseIntArray { - return opNameMapToOpSparseArray(getUidModes(uid)) + val appId = UserHandle.getAppId(uid) + val userId = UserHandle.getUserId(uid) + service.getState { + val modes = + with(appIdPolicy) { opNameMapToOpSparseArray(getAppOpModes(appId, userId)?.map) } + if (Flags.runtimePermissionAppopsMappingEnabled()) { + runtimePermissionNameToAppOp.forEachIndexed { _, permissionName, appOpCode -> + val mode = getUidModeFromPermissionState(appId, userId, permissionName) + if (mode != AppOpsManager.opToDefaultMode(appOpCode)) { + modes[appOpCode] = mode + } + } + } + + return modes + } } override fun getNonDefaultPackageModes(packageName: String, userId: Int): SparseIntArray { @@ -84,7 +169,13 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS val appId = UserHandle.getAppId(uid) val userId = UserHandle.getUserId(uid) val opName = AppOpsManager.opToPublicName(op) - return service.getState { with(appIdPolicy) { getAppOpMode(appId, userId, opName) } } + val permissionName = runtimeAppOpToPermissionNames[op] + + return if (!Flags.runtimePermissionAppopsMappingEnabled() || permissionName == null) { + service.getState { with(appIdPolicy) { getAppOpMode(appId, userId, opName) } } + } else { + service.getState { getUidModeFromPermissionState(appId, userId, permissionName) } + } } private fun getUidModes(uid: Int): ArrayMap<String, Int>? { @@ -93,13 +184,66 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS return service.getState { with(appIdPolicy) { getAppOpModes(appId, userId) } }?.map } - override fun setUidMode(uid: Int, persistentDeviceId: String, op: Int, mode: Int): Boolean { + private fun GetStateScope.getUidModeFromPermissionState( + appId: Int, + userId: Int, + permissionName: String + ): Int { + val permissionFlags = + with(permissionPolicy) { getPermissionFlags(appId, userId, permissionName) } + val backgroundPermissionName = foregroundToBackgroundPermissionName[permissionName] + val backgroundPermissionFlags = + if (backgroundPermissionName != null) { + with(permissionPolicy) { + getPermissionFlags(appId, userId, backgroundPermissionName) + } + } else { + PermissionFlags.RUNTIME_GRANTED + } + val result = evaluateModeFromPermissionFlags(permissionFlags, backgroundPermissionFlags) + if (result != MODE_IGNORED) { + return result + } + + val fullerPermissionName = + PermissionService.getFullerPermission(permissionName) ?: return result + return getUidModeFromPermissionState(appId, userId, fullerPermissionName) + } + + private fun evaluateModeFromPermissionFlags( + foregroundFlags: Int, + backgroundFlags: Int = PermissionFlags.RUNTIME_GRANTED + ): Int = + if (PermissionFlags.isAppOpGranted(foregroundFlags)) { + if (PermissionFlags.isAppOpGranted(backgroundFlags)) { + MODE_ALLOWED + } else { + MODE_FOREGROUND + } + } else { + MODE_IGNORED + } + + override fun setUidMode(uid: Int, persistentDeviceId: String, code: Int, mode: Int): Boolean { + if ( + Flags.runtimePermissionAppopsMappingEnabled() && code in runtimeAppOpToPermissionNames + ) { + Slog.w( + LOG_TAG, + "Cannot set UID mode for runtime permission app op, uid = $uid," + + " code = ${AppOpsManager.opToName(code)}," + + " mode = ${AppOpsManager.modeToName(mode)}", + RuntimeException() + ) + return false + } + val appId = UserHandle.getAppId(uid) val userId = UserHandle.getUserId(uid) - val opName = AppOpsManager.opToPublicName(op) - var wasChanged = false + val appOpName = AppOpsManager.opToPublicName(code) + var wasChanged: Boolean service.mutateState { - wasChanged = with(appIdPolicy) { setAppOpMode(appId, userId, opName, mode) } + wasChanged = with(appIdPolicy) { setAppOpMode(appId, userId, appOpName, mode) } } return wasChanged } @@ -114,10 +258,23 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS private fun getPackageModes(packageName: String, userId: Int): ArrayMap<String, Int>? = service.getState { with(packagePolicy) { getAppOpModes(packageName, userId) } }?.map - override fun setPackageMode(packageName: String, op: Int, mode: Int, userId: Int) { - val opName = AppOpsManager.opToPublicName(op) + override fun setPackageMode(packageName: String, appOpCode: Int, mode: Int, userId: Int) { + val appOpName = AppOpsManager.opToPublicName(appOpCode) + + if ( + Flags.runtimePermissionAppopsMappingEnabled() && + appOpCode in runtimeAppOpToPermissionNames + ) { + Slog.w( + LOG_TAG, + "(packageName=$packageName, userId=$userId)'s appop state" + + " for runtime op $appOpName should not be set directly.", + RuntimeException() + ) + return + } service.mutateState { - with(packagePolicy) { setAppOpMode(packageName, userId, opName, mode) } + with(packagePolicy) { setAppOpMode(packageName, userId, appOpName, mode) } } } @@ -128,7 +285,7 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS } override fun removePackage(packageName: String, userId: Int): Boolean { - var wasChanged = false + var wasChanged: Boolean service.mutateState { wasChanged = with(packagePolicy) { removeAppOpModes(packageName, userId) } } @@ -158,6 +315,13 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS this[AppOpsManager.strOpToOp(op)] = true } } + if (Flags.runtimePermissionAppopsMappingEnabled()) { + foregroundableOps.forEachIndexed { _, op, _ -> + if (getUidMode(uid, persistentDeviceId, op) == AppOpsManager.MODE_FOREGROUND) { + this[op] = true + } + } + } } } @@ -168,6 +332,13 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS this[AppOpsManager.strOpToOp(op)] = true } } + if (Flags.runtimePermissionAppopsMappingEnabled()) { + foregroundableOps.forEachIndexed { _, op, _ -> + if (getPackageMode(packageName, op, userId) == AppOpsManager.MODE_FOREGROUND) { + this[op] = true + } + } + } } } @@ -189,9 +360,10 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS } } - inner class OnAppIdAppOpModeChangedListener : AppIdAppOpPolicy.OnAppOpModeChangedListener() { + private inner class OnAppIdAppOpModeChangedListener : + AppIdAppOpPolicy.OnAppOpModeChangedListener() { // (uid, appOpCode) -> newMode - val pendingChanges = ArrayMap<Pair<Int, Int>, Int>() + private val pendingChanges = LongSparseArray<Int>() override fun onAppOpModeChanged( appId: Int, @@ -202,7 +374,7 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS ) { val uid = UserHandle.getUid(userId, appId) val appOpCode = AppOpsManager.strOpToOp(appOpName) - val key = Pair(uid, appOpCode) + val key = IntPair.of(uid, appOpCode) pendingChanges[key] = newMode } @@ -211,13 +383,15 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS val listenersLocal = listeners pendingChanges.forEachIndexed { _, key, mode -> listenersLocal.forEachIndexed { _, listener -> - val uid = key.first - val appOpCode = key.second + val uid = IntPair.first(key) + val appOpCode = IntPair.second(key) - listener.onUidModeChanged(uid, + listener.onUidModeChanged( + uid, appOpCode, mode, - VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT) + VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT + ) } } @@ -228,7 +402,7 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS private inner class OnPackageAppOpModeChangedListener : PackageAppOpPolicy.OnAppOpModeChangedListener() { // (packageName, userId, appOpCode) -> newMode - val pendingChanges = ArrayMap<Triple<String, Int, Int>, Int>() + private val pendingChanges = ArrayMap<Triple<String, Int, Int>, Int>() override fun onAppOpModeChanged( packageName: String, @@ -258,4 +432,130 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS pendingChanges.clear() } } + + private inner class OnPermissionFlagsChangedListener : + AppIdPermissionPolicy.OnPermissionFlagsChangedListener, + DevicePermissionPolicy.OnDevicePermissionFlagsChangedListener { + // (uid, deviceId, appOpCode) -> newMode + private val pendingChanges = ArrayMap<Triple<Int, String, Int>, Int>() + + override fun onPermissionFlagsChanged( + appId: Int, + userId: Int, + permissionName: String, + oldFlags: Int, + newFlags: Int + ) { + onDevicePermissionFlagsChanged( + appId, + userId, + VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT, + permissionName, + oldFlags, + newFlags + ) + } + + override fun onDevicePermissionFlagsChanged( + appId: Int, + userId: Int, + deviceId: String, + permissionName: String, + oldFlags: Int, + newFlags: Int + ) { + backgroundToForegroundPermissionNames[permissionName]?.let { foregroundPermissions -> + // This is a background permission; there may be multiple foreground permissions + // affected. + foregroundPermissions.forEachIndexed { _, foregroundPermissionName -> + runtimePermissionNameToAppOp[foregroundPermissionName]?.let { appOpCode -> + val foregroundPermissionFlags = + getPermissionFlags(appId, userId, foregroundPermissionName) + addPendingChangedModeIfNeeded( + appId, + userId, + deviceId, + appOpCode, + foregroundPermissionFlags, + oldFlags, + foregroundPermissionFlags, + newFlags + ) + } + } + } + ?: foregroundToBackgroundPermissionName[permissionName]?.let { backgroundPermission + -> + runtimePermissionNameToAppOp[permissionName]?.let { appOpCode -> + val backgroundPermissionFlags = + getPermissionFlags(appId, userId, backgroundPermission) + addPendingChangedModeIfNeeded( + appId, + userId, + deviceId, + appOpCode, + oldFlags, + backgroundPermissionFlags, + newFlags, + backgroundPermissionFlags + ) + } + } + ?: runtimePermissionNameToAppOp[permissionName]?.let { appOpCode -> + addPendingChangedModeIfNeeded( + appId, + userId, + deviceId, + appOpCode, + oldFlags, + PermissionFlags.RUNTIME_GRANTED, + newFlags, + PermissionFlags.RUNTIME_GRANTED + ) + } + } + + private fun getPermissionFlags(appId: Int, userId: Int, permissionName: String): Int = + service.getState { + with(permissionPolicy) { getPermissionFlags(appId, userId, permissionName) } + } + + private fun addPendingChangedModeIfNeeded( + appId: Int, + userId: Int, + deviceId: String, + appOpCode: Int, + oldForegroundFlags: Int, + oldBackgroundFlags: Int, + newForegroundFlags: Int, + newBackgroundFlags: Int, + ) { + val oldMode = evaluateModeFromPermissionFlags(oldForegroundFlags, oldBackgroundFlags) + val newMode = evaluateModeFromPermissionFlags(newForegroundFlags, newBackgroundFlags) + + if (oldMode != newMode) { + val uid = UserHandle.getUid(userId, appId) + pendingChanges[Triple(uid, deviceId, appOpCode)] = newMode + } + } + + override fun onStateMutated() { + val listenersLocal = listeners + pendingChanges.forEachIndexed { _, key, mode -> + listenersLocal.forEachIndexed { _, listener -> + val uid = key.first + val deviceId = key.second + val appOpCode = key.third + + listener.onUidModeChanged(uid, appOpCode, mode, deviceId) + } + } + + pendingChanges.clear() + } + } + + companion object { + private val LOG_TAG = AppOpService::class.java.simpleName + } } diff --git a/services/permission/java/com/android/server/permission/access/collection/LongSparseArrayExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/LongSparseArrayExtensions.kt new file mode 100644 index 000000000000..827dd0e5d292 --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/collection/LongSparseArrayExtensions.kt @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2022 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.permission.access.collection + +import android.util.LongSparseArray + +inline fun <T> LongSparseArray<T>.allIndexed(predicate: (Int, Long, T) -> Boolean): Boolean { + forEachIndexed { index, key, value -> + if (!predicate(index, key, value)) { + return false + } + } + return true +} + +inline fun <T> LongSparseArray<T>.anyIndexed(predicate: (Int, Long, T) -> Boolean): Boolean { + forEachIndexed { index, key, value -> + if (predicate(index, key, value)) { + return true + } + } + return false +} + +inline fun <T> LongSparseArray<T>.forEachIndexed(action: (Int, Long, T) -> Unit) { + for (index in 0 until size) { + action(index, keyAt(index), valueAt(index)) + } +} + +inline fun <T> LongSparseArray<T>.forEachReversedIndexed(action: (Int, Long, T) -> Unit) { + for (index in lastIndex downTo 0) { + action(index, keyAt(index), valueAt(index)) + } +} + +inline fun <T> LongSparseArray<T>.getOrPut(key: Long, defaultValue: () -> T): T { + val index = indexOfKey(key) + return if (index >= 0) { + valueAt(index) + } else { + defaultValue().also { put(key, it) } + } +} + +inline val <T> LongSparseArray<T>.lastIndex: Int + get() = size - 1 + +@Suppress("NOTHING_TO_INLINE") +inline operator fun <T> LongSparseArray<T>.minusAssign(key: Long) { + delete(key) +} + +inline fun <T> LongSparseArray<T>.noneIndexed(predicate: (Int, Long, T) -> Boolean): Boolean { + forEachIndexed { index, key, value -> + if (predicate(index, key, value)) { + return false + } + } + return true +} + +inline fun <T> LongSparseArray<T>.removeAllIndexed(predicate: (Int, Long, T) -> Boolean): Boolean { + var isChanged = false + forEachReversedIndexed { index, key, value -> + if (predicate(index, key, value)) { + removeAt(index) + isChanged = true + } + } + return isChanged +} + +inline fun <T> LongSparseArray<T>.retainAllIndexed(predicate: (Int, Long, T) -> Boolean): Boolean { + var isChanged = false + forEachReversedIndexed { index, key, value -> + if (!predicate(index, key, value)) { + removeAt(index) + isChanged = true + } + } + return isChanged +} + +inline val <T> LongSparseArray<T>.size: Int + get() = size() + +@Suppress("NOTHING_TO_INLINE") +inline operator fun <T> LongSparseArray<T>.set(key: Long, value: T) { + put(key, value) +} diff --git a/services/permission/java/com/android/server/permission/access/collection/SparseIntArrayExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/SparseIntArrayExtensions.kt new file mode 100644 index 000000000000..a582431aa83c --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/collection/SparseIntArrayExtensions.kt @@ -0,0 +1,120 @@ +/* + * 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.permission.access.collection + +import android.util.SparseIntArray + +inline fun SparseIntArray.allIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean { + forEachIndexed { index, key, value -> + if (!predicate(index, key, value)) { + return false + } + } + return true +} + +inline fun SparseIntArray.anyIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean { + forEachIndexed { index, key, value -> + if (predicate(index, key, value)) { + return true + } + } + return false +} + +inline fun SparseIntArray.forEachIndexed(action: (Int, Int, Int) -> Unit) { + for (index in 0 until size) { + action(index, keyAt(index), valueAt(index)) + } +} + +inline fun SparseIntArray.forEachReversedIndexed(action: (Int, Int, Int) -> Unit) { + for (index in lastIndex downTo 0) { + action(index, keyAt(index), valueAt(index)) + } +} + +inline fun SparseIntArray.getOrPut(key: Int, defaultValue: () -> Int): Int { + val index = indexOfKey(key) + return if (index >= 0) { + valueAt(index) + } else { + defaultValue().also { put(key, it) } + } +} + +inline val SparseIntArray.lastIndex: Int + get() = size - 1 + +@Suppress("NOTHING_TO_INLINE") +inline operator fun SparseIntArray.minusAssign(key: Int) { + delete(key) +} + +inline fun SparseIntArray.noneIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean { + forEachIndexed { index, key, value -> + if (predicate(index, key, value)) { + return false + } + } + return true +} + +fun SparseIntArray.remove(key: Int) { + delete(key) +} + +fun SparseIntArray.remove(key: Int, defaultValue: Int): Int { + val index = indexOfKey(key) + return if (index >= 0) { + val oldValue = valueAt(index) + removeAt(index) + oldValue + } else { + defaultValue + } +} + +inline fun SparseIntArray.removeAllIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean { + var isChanged = false + forEachReversedIndexed { index, key, value -> + if (predicate(index, key, value)) { + removeAt(index) + isChanged = true + } + } + return isChanged +} + +inline fun SparseIntArray.retainAllIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean { + var isChanged = false + forEachReversedIndexed { index, key, value -> + if (!predicate(index, key, value)) { + removeAt(index) + isChanged = true + } + } + return isChanged +} + +@Suppress("NOTHING_TO_INLINE") +inline operator fun SparseIntArray.set(key: Int, value: Int) { + put(key, value) +} + +inline val SparseIntArray.size: Int + get() = size() diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt index 4b086b3aca17..67df67fdf6c1 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt @@ -227,25 +227,59 @@ class AppIdPermissionPolicy : SchemePolicy() { if (isRequestedBySystemPackage) { return@forEach } - val oldFlags = getPermissionFlags(appId, userId, permissionName) - var newFlags = oldFlags andInv PermissionFlags.UPGRADE_EXEMPT - val isExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT) - newFlags = - if (permission.isHardRestricted && !isExempt) { - newFlags or PermissionFlags.RESTRICTION_REVOKED - } else { - newFlags andInv PermissionFlags.RESTRICTION_REVOKED - } - newFlags = - if (permission.isSoftRestricted && !isExempt) { - newFlags or PermissionFlags.SOFT_RESTRICTED - } else { - newFlags andInv PermissionFlags.SOFT_RESTRICTED - } - setPermissionFlags(appId, userId, permissionName, newFlags) + updatePermissionExemptFlags( + appId, + userId, + permission, + PermissionFlags.UPGRADE_EXEMPT, + 0 + ) } } + fun MutateStateScope.updatePermissionExemptFlags( + appId: Int, + userId: Int, + permission: Permission, + exemptFlagMask: Int, + exemptFlagValues: Int + ) { + val permissionName = permission.name + val oldFlags = getPermissionFlags(appId, userId, permissionName) + var newFlags = (oldFlags andInv exemptFlagMask) or (exemptFlagValues and exemptFlagMask) + if (oldFlags == newFlags) { + return + } + val isExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT) + if (permission.isHardRestricted && !isExempt) { + newFlags = newFlags or PermissionFlags.RESTRICTION_REVOKED + // If the permission was policy fixed as granted but it is no longer on any of the + // allowlists we need to clear the policy fixed flag as allowlisting trumps policy i.e. + // policy cannot grant a non grantable permission. + if (PermissionFlags.isPermissionGranted(oldFlags)) { + newFlags = newFlags andInv PermissionFlags.POLICY_FIXED + } + } else { + newFlags = newFlags andInv PermissionFlags.RESTRICTION_REVOKED + } + newFlags = + if ( + permission.isSoftRestricted && !isExempt && + !anyPackageInAppId(appId) { + permissionName in it.androidPackage!!.requestedPermissions && + isSoftRestrictedPermissionExemptForPackage(it, permissionName) + } + ) { + newFlags or PermissionFlags.SOFT_RESTRICTED + } else { + newFlags andInv PermissionFlags.SOFT_RESTRICTED + } + if (oldFlags == newFlags) { + return + } + setPermissionFlags(appId, userId, permissionName, newFlags) + } + override fun MutateStateScope.onPackageUninstalled( packageName: String, appId: Int, @@ -1118,7 +1152,12 @@ class AppIdPermissionPolicy : SchemePolicy() { newFlags andInv PermissionFlags.RESTRICTION_REVOKED } newFlags = - if (permission.isSoftRestricted && !isExempt) { + if ( + permission.isSoftRestricted && !isExempt && + !requestingPackageStates.anyIndexed { _, it -> + isSoftRestrictedPermissionExemptForPackage(it, permissionName) + } + ) { newFlags or PermissionFlags.SOFT_RESTRICTED } else { newFlags andInv PermissionFlags.SOFT_RESTRICTED @@ -1398,6 +1437,17 @@ class AppIdPermissionPolicy : SchemePolicy() { } } + // See also SoftRestrictedPermissionPolicy.mayGrantPermission() + private fun isSoftRestrictedPermissionExemptForPackage( + packageState: PackageState, + permissionName: String + ): Boolean = + when (permissionName) { + Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE -> + packageState.androidPackage!!.targetSdkVersion >= Build.VERSION_CODES.Q + else -> false + } + private inline fun MutateStateScope.anyPackageInAppId( appId: Int, state: AccessState = newState, diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt index 28889de4bbb1..c5c921dc3515 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt @@ -346,9 +346,18 @@ object PermissionFlags { return flags.hasBits(RUNTIME_GRANTED) } - fun isAppOpGranted(flags: Int): Boolean = - isPermissionGranted(flags) && !flags.hasBits(RESTRICTION_REVOKED) && - !flags.hasBits(APP_OP_REVOKED) + fun isAppOpGranted(flags: Int): Boolean { + if (!isPermissionGranted(flags)) { + return false + } + if (flags.hasAnyBit(MASK_RESTRICTED)) { + return false + } + if (flags.hasBits(APP_OP_REVOKED)) { + return false + } + return true + } fun toApiFlags(flags: Int): Int { var apiFlags = 0 diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt index 0704c8ffca25..0b58543e076d 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt @@ -88,7 +88,6 @@ import com.android.server.pm.PackageInstallerService import com.android.server.pm.PackageManagerLocal import com.android.server.pm.UserManagerInternal import com.android.server.pm.UserManagerService -import com.android.server.pm.parsing.pkg.AndroidPackageUtils import com.android.server.pm.permission.LegacyPermission import com.android.server.pm.permission.LegacyPermissionSettings import com.android.server.pm.permission.LegacyPermissionState @@ -97,7 +96,6 @@ import com.android.server.pm.permission.PermissionManagerServiceInterface import com.android.server.pm.permission.PermissionManagerServiceInternal import com.android.server.pm.pkg.AndroidPackage import com.android.server.pm.pkg.PackageState -import com.android.server.policy.SoftRestrictedPermissionPolicy import java.io.FileDescriptor import java.io.PrintWriter import java.util.concurrent.CompletableFuture @@ -1006,25 +1004,14 @@ class PermissionService(private val service: AccessCheckingService) : } if (isGranted && oldFlags.hasBits(PermissionFlags.SOFT_RESTRICTED)) { - // TODO: Refactor SoftRestrictedPermissionPolicy. - val softRestrictedPermissionPolicy = - SoftRestrictedPermissionPolicy.forPermission( - context, - AndroidPackageUtils.generateAppInfoWithoutState(androidPackage), - androidPackage, - UserHandle.of(userId), - permissionName + if (reportError) { + Slog.e( + LOG_TAG, + "$methodName: Cannot grant soft-restricted non-exempt permission" + + " $permissionName to package $packageName" ) - if (!softRestrictedPermissionPolicy.mayGrantPermission()) { - if (reportError) { - Slog.e( - LOG_TAG, - "$methodName: Cannot grant soft-restricted non-exempt permission" + - " $permissionName to package $packageName" - ) - } - return } + return } val newFlags = PermissionFlags.updateRuntimePermissionGranted(oldFlags, isGranted) @@ -1135,25 +1122,23 @@ class PermissionService(private val service: AccessCheckingService) : return emptyMap() } - val permissionFlagsMap = - service.getState { + service.getState { + val permissionFlags = if (deviceId == VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT) { with(policy) { getAllPermissionFlags(packageState.appId, userId) } } else { with(devicePolicy) { getAllPermissionFlags(packageState.appId, deviceId, userId) } - } + } ?: return emptyMap() + val permissionStates = ArrayMap<String, PermissionState>() + permissionFlags.forEachIndexed { _, permissionName, flags -> + val granted = isPermissionGranted(packageState, userId, permissionName, deviceId) + val apiFlags = PermissionFlags.toApiFlags(flags) + permissionStates[permissionName] = PermissionState(granted, apiFlags) } - ?: return emptyMap() - - val permissionStates = ArrayMap<String, PermissionState>() - permissionFlagsMap.forEachIndexed { _, permissionName, flags -> - val granted = PermissionFlags.isPermissionGranted(flags) - val apiFlags = PermissionFlags.toApiFlags(flags) - permissionStates[permissionName] = PermissionState(granted, apiFlags) + return permissionStates } - return permissionStates } override fun isPermissionRevokedByPolicy( @@ -1852,10 +1837,19 @@ class PermissionService(private val service: AccessCheckingService) : allowlistedFlags: Int, userId: Int ) { + var exemptMask = 0 + if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM)) { + exemptMask = exemptMask or PermissionFlags.SYSTEM_EXEMPT + } + if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE)) { + exemptMask = exemptMask or PermissionFlags.UPGRADE_EXEMPT + } + if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER)) { + exemptMask = exemptMask or PermissionFlags.INSTALLER_EXEMPT + } + service.mutateState { with(policy) { - val permissionsFlags = getUidPermissionFlags(appId, userId) ?: return@mutateState - val permissions = getPermissions() androidPackage.requestedPermissions.forEachIndexed { _, requestedPermission -> val permission = permissions[requestedPermission] @@ -1863,81 +1857,8 @@ class PermissionService(private val service: AccessCheckingService) : return@forEachIndexed } - val oldFlags = permissionsFlags[requestedPermission] ?: 0 - val wasGranted = PermissionFlags.isPermissionGranted(oldFlags) - - var newFlags = oldFlags - var mask = 0 - var allowlistFlagsCopy = allowlistedFlags - while (allowlistFlagsCopy != 0) { - val flag = 1 shl allowlistFlagsCopy.countTrailingZeroBits() - allowlistFlagsCopy = allowlistFlagsCopy and flag.inv() - when (flag) { - PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM -> { - mask = mask or PermissionFlags.SYSTEM_EXEMPT - newFlags = - if (permissionNames.contains(requestedPermission)) { - newFlags or PermissionFlags.SYSTEM_EXEMPT - } else { - newFlags andInv PermissionFlags.SYSTEM_EXEMPT - } - } - PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE -> { - mask = mask or PermissionFlags.UPGRADE_EXEMPT - newFlags = - if (permissionNames.contains(requestedPermission)) { - newFlags or PermissionFlags.UPGRADE_EXEMPT - } else { - newFlags andInv PermissionFlags.UPGRADE_EXEMPT - } - } - PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER -> { - mask = mask or PermissionFlags.INSTALLER_EXEMPT - newFlags = - if (permissionNames.contains(requestedPermission)) { - newFlags or PermissionFlags.INSTALLER_EXEMPT - } else { - newFlags andInv PermissionFlags.INSTALLER_EXEMPT - } - } - } - } - - if (oldFlags == newFlags) { - return@forEachIndexed - } - - val isExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT) - - // If the permission is policy fixed as granted but it is no longer - // on any of the allowlists we need to clear the policy fixed flag - // as allowlisting trumps policy i.e. policy cannot grant a non - // grantable permission. - if (oldFlags.hasBits(PermissionFlags.POLICY_FIXED)) { - if (!isExempt && wasGranted) { - mask = mask or PermissionFlags.POLICY_FIXED - newFlags = newFlags andInv PermissionFlags.POLICY_FIXED - } - } - - newFlags = - if (permission.isHardRestricted && !isExempt) { - newFlags or PermissionFlags.RESTRICTION_REVOKED - } else { - newFlags andInv PermissionFlags.RESTRICTION_REVOKED - } - newFlags = - if (permission.isSoftRestricted && !isExempt) { - newFlags or PermissionFlags.SOFT_RESTRICTED - } else { - newFlags andInv PermissionFlags.SOFT_RESTRICTED - } - mask = - mask or - PermissionFlags.RESTRICTION_REVOKED or - PermissionFlags.SOFT_RESTRICTED - - updatePermissionFlags(appId, userId, requestedPermission, mask, newFlags) + var exemptFlags = if (requestedPermission in permissionNames) exemptMask else 0 + updatePermissionExemptFlags(appId, userId, permission, exemptMask, exemptFlags) } } } @@ -2905,5 +2826,8 @@ class PermissionService(private val service: AccessCheckingService) : } else { emptySet<String>() } + + fun getFullerPermission(permissionName: String): String? = + FULLER_PERMISSIONS[permissionName] } } 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 1c71a6287c79..1d225ba09bbd 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java @@ -30,7 +30,10 @@ import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SH import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME_IMPLICIT; import static org.junit.Assert.assertThrows; -import static org.mockito.Mockito.any; +import static org.mockito.AdditionalMatchers.and; +import static org.mockito.AdditionalMatchers.not; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; @@ -40,6 +43,7 @@ import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.view.Display; +import android.view.inputmethod.ImeTracker; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -56,7 +60,7 @@ import org.junit.runner.RunWith; * Test the behavior of {@link DefaultImeVisibilityApplier} when performing or applying the IME * visibility state. * - * Build/Install/Run: + * <p>Build/Install/Run: * atest FrameworksInputMethodSystemServerTests:DefaultImeVisibilityApplierTest */ @RunWith(AndroidJUnit4.class) @@ -75,7 +79,8 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe public void testPerformShowIme() throws Exception { synchronized (ImfLock.class) { mVisibilityApplier.performShowIme(new Binder() /* showInputToken */, - null /* statsToken */, 0 /* showFlags */, null, SHOW_SOFT_INPUT); + ImeTracker.Token.empty(), 0 /* showFlags */, null /* resultReceiver */, + SHOW_SOFT_INPUT); } verifyShowSoftInput(false, true, 0 /* showFlags */); } @@ -84,46 +89,66 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe public void testPerformHideIme() throws Exception { synchronized (ImfLock.class) { mVisibilityApplier.performHideIme(new Binder() /* hideInputToken */, - null /* statsToken */, null, HIDE_SOFT_INPUT); + ImeTracker.Token.empty(), null /* resultReceiver */, HIDE_SOFT_INPUT); } verifyHideSoftInput(false, true); } @Test public void testApplyImeVisibility_throwForInvalidState() { - assertThrows(IllegalArgumentException.class, - () -> mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_INVALID)); + assertThrows(IllegalArgumentException.class, () -> { + synchronized (ImfLock.class) { + mVisibilityApplier.applyImeVisibility(mWindowToken, ImeTracker.Token.empty(), + STATE_INVALID); + } + }); } @Test public void testApplyImeVisibility_showIme() { - mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_SHOW_IME); - verify(mMockWindowManagerInternal).showImePostLayout(eq(mWindowToken), any()); + final var statsToken = ImeTracker.Token.empty(); + synchronized (ImfLock.class) { + mVisibilityApplier.applyImeVisibility(mWindowToken, statsToken, STATE_SHOW_IME); + } + verify(mMockWindowManagerInternal).showImePostLayout(eq(mWindowToken), eq(statsToken)); } @Test public void testApplyImeVisibility_hideIme() { - mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_HIDE_IME); - verify(mMockWindowManagerInternal).hideIme(eq(mWindowToken), anyInt(), any()); + final var statsToken = ImeTracker.Token.empty(); + synchronized (ImfLock.class) { + mVisibilityApplier.applyImeVisibility(mWindowToken, statsToken, STATE_HIDE_IME); + } + verify(mMockWindowManagerInternal).hideIme(eq(mWindowToken), anyInt() /* displayId */, + eq(statsToken)); } @Test public void testApplyImeVisibility_hideImeExplicit() throws Exception { mInputMethodManagerService.mImeWindowVis = IME_ACTIVE; - mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_HIDE_IME_EXPLICIT); + synchronized (ImfLock.class) { + mVisibilityApplier.applyImeVisibility(mWindowToken, ImeTracker.Token.empty(), + STATE_HIDE_IME_EXPLICIT); + } verifyHideSoftInput(true, true); } @Test public void testApplyImeVisibility_hideNotAlways() throws Exception { mInputMethodManagerService.mImeWindowVis = IME_ACTIVE; - mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_HIDE_IME_NOT_ALWAYS); + synchronized (ImfLock.class) { + mVisibilityApplier.applyImeVisibility(mWindowToken, ImeTracker.Token.empty(), + STATE_HIDE_IME_NOT_ALWAYS); + } verifyHideSoftInput(true, true); } @Test public void testApplyImeVisibility_showImeImplicit() throws Exception { - mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_SHOW_IME_IMPLICIT); + synchronized (ImfLock.class) { + mVisibilityApplier.applyImeVisibility(mWindowToken, ImeTracker.Token.empty(), + STATE_SHOW_IME_IMPLICIT); + } verifyShowSoftInput(true, true, 0 /* showFlags */); } @@ -135,21 +160,21 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe mInputMethodManagerService.setAttachedClientForTesting(null); startInputOrWindowGainedFocus(mWindowToken, SOFT_INPUT_STATE_ALWAYS_VISIBLE); + final var statsToken = ImeTracker.Token.empty(); synchronized (ImfLock.class) { final int displayIdToShowIme = mInputMethodManagerService.getDisplayIdToShowImeLocked(); // Verify hideIme will apply the expected displayId when the default IME // visibility applier app STATE_HIDE_IME. - mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_HIDE_IME); + mVisibilityApplier.applyImeVisibility(mWindowToken, statsToken, STATE_HIDE_IME); verify(mInputMethodManagerService.mWindowManagerInternal).hideIme( - eq(mWindowToken), eq(displayIdToShowIme), eq(null)); + eq(mWindowToken), eq(displayIdToShowIme), eq(statsToken)); } } @Test public void testShowImeScreenshot() { synchronized (ImfLock.class) { - mVisibilityApplier.showImeScreenshot(mWindowToken, Display.DEFAULT_DISPLAY, - null /* statsToken */); + mVisibilityApplier.showImeScreenshot(mWindowToken, Display.DEFAULT_DISPLAY); } verify(mMockImeTargetVisibilityPolicy).showImeScreenshot(eq(mWindowToken), @@ -174,17 +199,20 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe synchronized (ImfLock.class) { // Simulate the system hides the IME when switching IME services in different users. // (e.g. unbinding the IME from the current user to the profile user) + final var statsToken = ImeTracker.Token.empty(); final int displayIdToShowIme = mInputMethodManagerService.getDisplayIdToShowImeLocked(); - mInputMethodManagerService.hideCurrentInputLocked(mWindowToken, null, 0, null, + mInputMethodManagerService.hideCurrentInputLocked(mWindowToken, + statsToken, 0 /* flags */, null /* resultReceiver */, HIDE_SWITCH_USER); mInputMethodManagerService.onUnbindCurrentMethodByReset(); // Expects applyImeVisibility() -> hideIme() will be called to notify WM for syncing // the IME hidden state. - verify(mVisibilityApplier).applyImeVisibility(eq(mWindowToken), any(), - eq(STATE_HIDE_IME)); + // The unbind will cancel the previous stats token, and create a new one internally. + verify(mVisibilityApplier).applyImeVisibility( + eq(mWindowToken), any(), eq(STATE_HIDE_IME)); verify(mInputMethodManagerService.mWindowManagerInternal).hideIme( - eq(mWindowToken), eq(displayIdToShowIme), eq(null)); + eq(mWindowToken), eq(displayIdToShowIme), and(not(eq(statsToken)), notNull())); } } diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java index fae5f86e4007..a22cacbcb5df 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java @@ -39,9 +39,12 @@ import static com.android.server.inputmethod.InputMethodManagerService.ImeDispla import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.notNull; + import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; +import android.view.inputmethod.ImeTracker; import android.view.inputmethod.InputMethodManager; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -58,7 +61,7 @@ import org.mockito.ArgumentCaptor; * Test the behavior of {@link ImeVisibilityStateComputer} and {@link ImeVisibilityApplier} when * requesting the IME visibility. * - * Build/Install/Run: + * <p> Build/Install/Run: * atest FrameworksInputMethodSystemServerTests:ImeVisibilityStateComputerTest */ @RunWith(AndroidJUnit4.class) @@ -91,7 +94,8 @@ public class ImeVisibilityStateComputerTest extends InputMethodManagerServiceTes @Test public void testRequestImeVisibility_showImplicit() { initImeTargetWindowState(mWindowToken); - boolean res = mComputer.onImeShowFlags(null, InputMethodManager.SHOW_IMPLICIT); + boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(), + InputMethodManager.SHOW_IMPLICIT); mComputer.requestImeVisibility(mWindowToken, res); final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken); @@ -106,7 +110,7 @@ public class ImeVisibilityStateComputerTest extends InputMethodManagerServiceTes @Test public void testRequestImeVisibility_showExplicit() { initImeTargetWindowState(mWindowToken); - boolean res = mComputer.onImeShowFlags(null, 0 /* showFlags */); + boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(), 0 /* showFlags */); mComputer.requestImeVisibility(mWindowToken, res); final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken); @@ -125,7 +129,7 @@ public class ImeVisibilityStateComputerTest extends InputMethodManagerServiceTes @Test public void testRequestImeVisibility_showExplicit_thenShowImplicit() { initImeTargetWindowState(mWindowToken); - mComputer.onImeShowFlags(null, 0 /* showFlags */); + mComputer.onImeShowFlags(ImeTracker.Token.empty(), 0 /* showFlags */); assertThat(mComputer.mRequestedShowExplicitly).isTrue(); mComputer.onImeShowFlags(null, InputMethodManager.SHOW_IMPLICIT); @@ -139,10 +143,10 @@ public class ImeVisibilityStateComputerTest extends InputMethodManagerServiceTes @Test public void testRequestImeVisibility_showForced_thenShowExplicit() { initImeTargetWindowState(mWindowToken); - mComputer.onImeShowFlags(null, InputMethodManager.SHOW_FORCED); + mComputer.onImeShowFlags(ImeTracker.Token.empty(), InputMethodManager.SHOW_FORCED); assertThat(mComputer.mShowForced).isTrue(); - mComputer.onImeShowFlags(null, 0 /* showFlags */); + mComputer.onImeShowFlags(ImeTracker.Token.empty(), 0 /* showFlags */); assertThat(mComputer.mShowForced).isTrue(); } @@ -152,7 +156,8 @@ public class ImeVisibilityStateComputerTest extends InputMethodManagerServiceTes mComputer.getImePolicy().setA11yRequestNoSoftKeyboard(SHOW_MODE_HIDDEN); initImeTargetWindowState(mWindowToken); - boolean res = mComputer.onImeShowFlags(null, InputMethodManager.SHOW_IMPLICIT); + boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(), + InputMethodManager.SHOW_IMPLICIT); mComputer.requestImeVisibility(mWindowToken, res); final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken); @@ -170,7 +175,8 @@ public class ImeVisibilityStateComputerTest extends InputMethodManagerServiceTes mComputer.getImePolicy().setImeHiddenByDisplayPolicy(true); initImeTargetWindowState(mWindowToken); - boolean res = mComputer.onImeShowFlags(null, InputMethodManager.SHOW_IMPLICIT); + boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(), + InputMethodManager.SHOW_IMPLICIT); mComputer.requestImeVisibility(mWindowToken, res); final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken); @@ -188,7 +194,8 @@ public class ImeVisibilityStateComputerTest extends InputMethodManagerServiceTes mComputer.setInputShown(true); initImeTargetWindowState(mWindowToken); - assertThat(mComputer.canHideIme(null, InputMethodManager.HIDE_NOT_ALWAYS)).isTrue(); + assertThat(mComputer.canHideIme(ImeTracker.Token.empty(), + InputMethodManager.HIDE_NOT_ALWAYS)).isTrue(); mComputer.requestImeVisibility(mWindowToken, false); final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken); @@ -281,7 +288,7 @@ public class ImeVisibilityStateComputerTest extends InputMethodManagerServiceTes final ArgumentCaptor<ImeVisibilityResult> resultCaptor = ArgumentCaptor.forClass( ImeVisibilityResult.class); verify(mInputMethodManagerService).onApplyImeVisibilityFromComputer(targetCaptor.capture(), - resultCaptor.capture()); + notNull() /* statsToken */, resultCaptor.capture()); final IBinder imeInputTarget = targetCaptor.getValue(); final ImeVisibilityResult result = resultCaptor.getValue(); diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java index a1be00aab340..f4d95afaacf4 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java @@ -27,6 +27,7 @@ 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.ArgumentMatchers.notNull; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -277,8 +278,9 @@ public class InputMethodManagerServiceTestBase { .setCurrentMethodVisible(); } verify(mMockInputMethod, times(showSoftInput ? 1 : 0)) - .showSoftInput(any(), any(), - showFlags != NO_VERIFY_SHOW_FLAGS ? eq(showFlags) : anyInt(), any()); + .showSoftInput(any() /* showInputToken */ , notNull() /* statsToken */, + showFlags != NO_VERIFY_SHOW_FLAGS ? eq(showFlags) : anyInt() /* flags*/, + any() /* resultReceiver */); } protected void verifyHideSoftInput(boolean setNotVisible, boolean hideSoftInput) @@ -288,6 +290,7 @@ public class InputMethodManagerServiceTestBase { .setCurrentMethodNotVisible(); } verify(mMockInputMethod, times(hideSoftInput ? 1 : 0)) - .hideSoftInput(any(), any(), anyInt(), any()); + .hideSoftInput(any() /* hideInputToken */, notNull() /* statsToken */, + anyInt() /* flags */, any() /* resultReceiver */); } } diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt index cde46abafe95..96753b6d2bcc 100644 --- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt +++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt @@ -233,24 +233,6 @@ class AppIdPermissionPolicyTest : BasePermissionPolicyTest() { .isEqualTo(expectedNewFlags) } - @Test - fun testOnPackageInstalled_restrictedPermissionsIsExempted_clearsRestrictionFlags() { - val oldFlags = PermissionFlags.SOFT_RESTRICTED or PermissionFlags.INSTALLER_EXEMPT - testOnPackageInstalled( - oldFlags, - permissionInfoFlags = PermissionInfo.FLAG_SOFT_RESTRICTED - ) {} - val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) - val expectedNewFlags = PermissionFlags.INSTALLER_EXEMPT - assertWithMessage( - "After onPackageInstalled() is called for a non-system app that requests a runtime" + - " soft restricted permission that is exempted. The actual permission flags" + - " $actualFlags should match the expected flags $expectedNewFlags" - ) - .that(actualFlags) - .isEqualTo(expectedNewFlags) - } - private fun testOnPackageInstalled( oldFlags: Int, permissionInfoFlags: Int = 0, diff --git a/services/tests/VpnTests/java/android/net/Ikev2VpnProfileTest.java b/services/tests/VpnTests/java/android/net/Ikev2VpnProfileTest.java new file mode 100644 index 000000000000..e12e961fd60f --- /dev/null +++ b/services/tests/VpnTests/java/android/net/Ikev2VpnProfileTest.java @@ -0,0 +1,585 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import static android.net.cts.util.IkeSessionTestUtils.CHILD_PARAMS; +import static android.net.cts.util.IkeSessionTestUtils.IKE_PARAMS_V6; +import static android.net.cts.util.IkeSessionTestUtils.getTestIkeSessionParams; + +import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.net.ipsec.ike.IkeKeyIdIdentification; +import android.net.ipsec.ike.IkeTunnelConnectionParams; +import android.os.Build; +import android.test.mock.MockContext; + +import androidx.test.filters.SmallTest; + +import com.android.internal.net.VpnProfile; +import com.android.internal.org.bouncycastle.x509.X509V1CertificateGenerator; +import com.android.net.module.util.ProxyUtils; +import com.android.testutils.DevSdkIgnoreRule; +import com.android.testutils.DevSdkIgnoreRunner; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import javax.security.auth.x500.X500Principal; + +/** Unit tests for {@link Ikev2VpnProfile.Builder}. */ +@SmallTest +@RunWith(DevSdkIgnoreRunner.class) +@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) +public class Ikev2VpnProfileTest { + private static final String SERVER_ADDR_STRING = "1.2.3.4"; + private static final String IDENTITY_STRING = "Identity"; + private static final String USERNAME_STRING = "username"; + private static final String PASSWORD_STRING = "pa55w0rd"; + private static final String EXCL_LIST = "exclList"; + private static final byte[] PSK_BYTES = "preSharedKey".getBytes(); + private static final int TEST_MTU = 1300; + + @Rule + public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule(); + + private final MockContext mMockContext = + new MockContext() { + @Override + public String getOpPackageName() { + return "fooPackage"; + } + }; + private final ProxyInfo mProxy = ProxyInfo.buildDirectProxy( + SERVER_ADDR_STRING, -1, ProxyUtils.exclusionStringAsList(EXCL_LIST)); + + private X509Certificate mUserCert; + private X509Certificate mServerRootCa; + private PrivateKey mPrivateKey; + + @Before + public void setUp() throws Exception { + mServerRootCa = generateRandomCertAndKeyPair().cert; + + final CertificateAndKey userCertKey = generateRandomCertAndKeyPair(); + mUserCert = userCertKey.cert; + mPrivateKey = userCertKey.key; + } + + private Ikev2VpnProfile.Builder getBuilderWithDefaultOptions() { + final Ikev2VpnProfile.Builder builder = + new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING); + + builder.setBypassable(true); + builder.setProxy(mProxy); + builder.setMaxMtu(TEST_MTU); + builder.setMetered(true); + + return builder; + } + + @Test + public void testBuildValidProfileWithOptions() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa); + final Ikev2VpnProfile profile = builder.build(); + assertNotNull(profile); + + // Check non-auth parameters correctly stored + assertEquals(SERVER_ADDR_STRING, profile.getServerAddr()); + assertEquals(IDENTITY_STRING, profile.getUserIdentity()); + assertEquals(mProxy, profile.getProxyInfo()); + assertTrue(profile.isBypassable()); + assertTrue(profile.isMetered()); + assertEquals(TEST_MTU, profile.getMaxMtu()); + assertEquals(Ikev2VpnProfile.DEFAULT_ALGORITHMS, profile.getAllowedAlgorithms()); + } + + @Test + public void testBuildUsernamePasswordProfile() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa); + final Ikev2VpnProfile profile = builder.build(); + assertNotNull(profile); + + assertEquals(USERNAME_STRING, profile.getUsername()); + assertEquals(PASSWORD_STRING, profile.getPassword()); + assertEquals(mServerRootCa, profile.getServerRootCaCert()); + + assertNull(profile.getPresharedKey()); + assertNull(profile.getRsaPrivateKey()); + assertNull(profile.getUserCert()); + } + + @Test + public void testBuildDigitalSignatureProfile() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa); + final Ikev2VpnProfile profile = builder.build(); + assertNotNull(profile); + + assertEquals(profile.getUserCert(), mUserCert); + assertEquals(mPrivateKey, profile.getRsaPrivateKey()); + assertEquals(profile.getServerRootCaCert(), mServerRootCa); + + assertNull(profile.getPresharedKey()); + assertNull(profile.getUsername()); + assertNull(profile.getPassword()); + } + + @Test + public void testBuildPresharedKeyProfile() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthPsk(PSK_BYTES); + final Ikev2VpnProfile profile = builder.build(); + assertNotNull(profile); + + assertArrayEquals(PSK_BYTES, profile.getPresharedKey()); + + assertNull(profile.getServerRootCaCert()); + assertNull(profile.getUsername()); + assertNull(profile.getPassword()); + assertNull(profile.getRsaPrivateKey()); + assertNull(profile.getUserCert()); + } + + @Test + public void testBuildWithAllowedAlgorithmsAead() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + builder.setAuthPsk(PSK_BYTES); + + List<String> allowedAlgorithms = + Arrays.asList( + IpSecAlgorithm.AUTH_CRYPT_AES_GCM, + IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305); + builder.setAllowedAlgorithms(allowedAlgorithms); + + final Ikev2VpnProfile profile = builder.build(); + assertEquals(allowedAlgorithms, profile.getAllowedAlgorithms()); + } + + @Test + public void testBuildWithAllowedAlgorithmsNormal() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + builder.setAuthPsk(PSK_BYTES); + + List<String> allowedAlgorithms = + Arrays.asList( + IpSecAlgorithm.AUTH_HMAC_SHA512, + IpSecAlgorithm.AUTH_AES_XCBC, + IpSecAlgorithm.AUTH_AES_CMAC, + IpSecAlgorithm.CRYPT_AES_CBC, + IpSecAlgorithm.CRYPT_AES_CTR); + builder.setAllowedAlgorithms(allowedAlgorithms); + + final Ikev2VpnProfile profile = builder.build(); + assertEquals(allowedAlgorithms, profile.getAllowedAlgorithms()); + } + + @Test + public void testSetAllowedAlgorithmsEmptyList() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + try { + builder.setAllowedAlgorithms(new ArrayList<>()); + fail("Expected exception due to no valid algorithm set"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testSetAllowedAlgorithmsInvalidList() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + List<String> allowedAlgorithms = new ArrayList<>(); + + try { + builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_SHA256)); + fail("Expected exception due to missing encryption"); + } catch (IllegalArgumentException expected) { + } + + try { + builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.CRYPT_AES_CBC)); + fail("Expected exception due to missing authentication"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testSetAllowedAlgorithmsInsecureAlgorithm() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + List<String> allowedAlgorithms = new ArrayList<>(); + + try { + builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_MD5)); + fail("Expected exception due to insecure algorithm"); + } catch (IllegalArgumentException expected) { + } + + try { + builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_SHA1)); + fail("Expected exception due to insecure algorithm"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testBuildNoAuthMethodSet() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + try { + builder.build(); + fail("Expected exception due to lack of auth method"); + } catch (IllegalArgumentException expected) { + } + } + + + // TODO: Refer to Build.VERSION_CODES.SC_V2 when it's available in AOSP and mainline branch + @DevSdkIgnoreRule.IgnoreUpTo(SC_V2) + @Test + public void testBuildExcludeLocalRoutesSet() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + builder.setAuthPsk(PSK_BYTES); + builder.setLocalRoutesExcluded(true); + + final Ikev2VpnProfile profile = builder.build(); + assertNotNull(profile); + assertTrue(profile.areLocalRoutesExcluded()); + + builder.setBypassable(false); + try { + builder.build(); + fail("Expected exception because excludeLocalRoutes should be set only" + + " on the bypassable VPN"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testBuildInvalidMtu() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + try { + builder.setMaxMtu(500); + fail("Expected exception due to too-small MTU"); + } catch (IllegalArgumentException expected) { + } + } + + private void verifyVpnProfileCommon(VpnProfile profile) { + assertEquals(SERVER_ADDR_STRING, profile.server); + assertEquals(IDENTITY_STRING, profile.ipsecIdentifier); + assertEquals(mProxy, profile.proxy); + assertTrue(profile.isBypassable); + assertTrue(profile.isMetered); + assertEquals(TEST_MTU, profile.maxMtu); + } + + @Test + public void testPskConvertToVpnProfile() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthPsk(PSK_BYTES); + final VpnProfile profile = builder.build().toVpnProfile(); + + verifyVpnProfileCommon(profile); + assertEquals(Ikev2VpnProfile.encodeForIpsecSecret(PSK_BYTES), profile.ipsecSecret); + + // Check nothing else is set + assertEquals("", profile.username); + assertEquals("", profile.password); + assertEquals("", profile.ipsecUserCert); + assertEquals("", profile.ipsecCaCert); + } + + @Test + public void testUsernamePasswordConvertToVpnProfile() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa); + final VpnProfile profile = builder.build().toVpnProfile(); + + verifyVpnProfileCommon(profile); + assertEquals(USERNAME_STRING, profile.username); + assertEquals(PASSWORD_STRING, profile.password); + assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert); + + // Check nothing else is set + assertEquals("", profile.ipsecUserCert); + assertEquals("", profile.ipsecSecret); + } + + @Test + public void testRsaConvertToVpnProfile() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa); + final VpnProfile profile = builder.build().toVpnProfile(); + + final String expectedSecret = Ikev2VpnProfile.PREFIX_INLINE + + Ikev2VpnProfile.encodeForIpsecSecret(mPrivateKey.getEncoded()); + verifyVpnProfileCommon(profile); + assertEquals(Ikev2VpnProfile.certificateToPemString(mUserCert), profile.ipsecUserCert); + assertEquals( + expectedSecret, + profile.ipsecSecret); + assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert); + + // Check nothing else is set + assertEquals("", profile.username); + assertEquals("", profile.password); + } + + @Test + public void testPskFromVpnProfileDiscardsIrrelevantValues() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthPsk(PSK_BYTES); + final VpnProfile profile = builder.build().toVpnProfile(); + profile.username = USERNAME_STRING; + profile.password = PASSWORD_STRING; + profile.ipsecCaCert = Ikev2VpnProfile.certificateToPemString(mServerRootCa); + profile.ipsecUserCert = Ikev2VpnProfile.certificateToPemString(mUserCert); + + final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile); + assertNull(result.getUsername()); + assertNull(result.getPassword()); + assertNull(result.getUserCert()); + assertNull(result.getRsaPrivateKey()); + assertNull(result.getServerRootCaCert()); + } + + @Test + public void testUsernamePasswordFromVpnProfileDiscardsIrrelevantValues() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa); + final VpnProfile profile = builder.build().toVpnProfile(); + profile.ipsecSecret = new String(PSK_BYTES); + profile.ipsecUserCert = Ikev2VpnProfile.certificateToPemString(mUserCert); + + final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile); + assertNull(result.getPresharedKey()); + assertNull(result.getUserCert()); + assertNull(result.getRsaPrivateKey()); + } + + @Test + public void testRsaFromVpnProfileDiscardsIrrelevantValues() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa); + final VpnProfile profile = builder.build().toVpnProfile(); + profile.username = USERNAME_STRING; + profile.password = PASSWORD_STRING; + + final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile); + assertNull(result.getUsername()); + assertNull(result.getPassword()); + assertNull(result.getPresharedKey()); + } + + @Test + public void testPskConversionIsLossless() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthPsk(PSK_BYTES); + final Ikev2VpnProfile ikeProfile = builder.build(); + + assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile())); + } + + @Test + public void testUsernamePasswordConversionIsLossless() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa); + final Ikev2VpnProfile ikeProfile = builder.build(); + + assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile())); + } + + @Test + public void testRsaConversionIsLossless() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa); + final Ikev2VpnProfile ikeProfile = builder.build(); + + assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile())); + } + + @Test + public void testBuildWithIkeTunConnParamsConvertToVpnProfile() throws Exception { + // Special keyId that contains delimiter character of VpnProfile + final byte[] keyId = "foo\0bar".getBytes(); + final IkeTunnelConnectionParams tunnelParams = new IkeTunnelConnectionParams( + getTestIkeSessionParams(true /* testIpv6 */, new IkeKeyIdIdentification(keyId)), + CHILD_PARAMS); + final Ikev2VpnProfile ikev2VpnProfile = new Ikev2VpnProfile.Builder(tunnelParams).build(); + final VpnProfile vpnProfile = ikev2VpnProfile.toVpnProfile(); + + assertEquals(VpnProfile.TYPE_IKEV2_FROM_IKE_TUN_CONN_PARAMS, vpnProfile.type); + + // Username, password, server, ipsecIdentifier, ipsecCaCert, ipsecSecret, ipsecUserCert and + // getAllowedAlgorithms should not be set if IkeTunnelConnectionParams is set. + assertEquals("", vpnProfile.server); + assertEquals("", vpnProfile.ipsecIdentifier); + assertEquals("", vpnProfile.username); + assertEquals("", vpnProfile.password); + assertEquals("", vpnProfile.ipsecCaCert); + assertEquals("", vpnProfile.ipsecSecret); + assertEquals("", vpnProfile.ipsecUserCert); + assertEquals(0, vpnProfile.getAllowedAlgorithms().size()); + + // IkeTunnelConnectionParams should stay the same. + assertEquals(tunnelParams, vpnProfile.ikeTunConnParams); + + // Convert to disk-stable format and then back to Ikev2VpnProfile should be the same. + final VpnProfile decodedVpnProfile = + VpnProfile.decode(vpnProfile.key, vpnProfile.encode()); + final Ikev2VpnProfile convertedIkev2VpnProfile = + Ikev2VpnProfile.fromVpnProfile(decodedVpnProfile); + assertEquals(ikev2VpnProfile, convertedIkev2VpnProfile); + } + + @Test + public void testConversionIsLosslessWithIkeTunConnParams() throws Exception { + final IkeTunnelConnectionParams tunnelParams = + new IkeTunnelConnectionParams(IKE_PARAMS_V6, CHILD_PARAMS); + // Config authentication related fields is not required while building with + // IkeTunnelConnectionParams. + final Ikev2VpnProfile ikeProfile = new Ikev2VpnProfile.Builder(tunnelParams).build(); + assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile())); + } + + @Test + public void testAutomaticNattAndIpVersionConversionIsLossless() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + builder.setAutomaticNattKeepaliveTimerEnabled(true); + builder.setAutomaticIpVersionSelectionEnabled(true); + + builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa); + final Ikev2VpnProfile ikeProfile = builder.build(); + + assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile())); + } + + @Test + public void testAutomaticNattAndIpVersionDefaults() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa); + final Ikev2VpnProfile ikeProfile = builder.build(); + + assertEquals(false, ikeProfile.isAutomaticNattKeepaliveTimerEnabled()); + assertEquals(false, ikeProfile.isAutomaticIpVersionSelectionEnabled()); + } + + @Test + public void testEquals() throws Exception { + // Verify building without IkeTunnelConnectionParams + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa); + assertEquals(builder.build(), builder.build()); + + // Verify building with IkeTunnelConnectionParams + final IkeTunnelConnectionParams tunnelParams = + new IkeTunnelConnectionParams(IKE_PARAMS_V6, CHILD_PARAMS); + final IkeTunnelConnectionParams tunnelParams2 = + new IkeTunnelConnectionParams(IKE_PARAMS_V6, CHILD_PARAMS); + assertEquals(new Ikev2VpnProfile.Builder(tunnelParams).build(), + new Ikev2VpnProfile.Builder(tunnelParams2).build()); + } + + @Test + public void testBuildProfileWithNullProxy() throws Exception { + final Ikev2VpnProfile ikev2VpnProfile = + new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING) + .setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa) + .build(); + + // ProxyInfo should be null for the profile without setting ProxyInfo. + assertNull(ikev2VpnProfile.getProxyInfo()); + + // ProxyInfo should stay null after performing toVpnProfile() and fromVpnProfile() + final VpnProfile vpnProfile = ikev2VpnProfile.toVpnProfile(); + assertNull(vpnProfile.proxy); + + final Ikev2VpnProfile convertedIkev2VpnProfile = Ikev2VpnProfile.fromVpnProfile(vpnProfile); + assertNull(convertedIkev2VpnProfile.getProxyInfo()); + } + + private static class CertificateAndKey { + public final X509Certificate cert; + public final PrivateKey key; + + CertificateAndKey(X509Certificate cert, PrivateKey key) { + this.cert = cert; + this.key = key; + } + } + + private static CertificateAndKey generateRandomCertAndKeyPair() throws Exception { + final Date validityBeginDate = + new Date(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1L)); + final Date validityEndDate = + new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1L)); + + // Generate a keypair + final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(512); + final KeyPair keyPair = keyPairGenerator.generateKeyPair(); + + final X500Principal dnName = new X500Principal("CN=test.android.com"); + final X509V1CertificateGenerator certGen = new X509V1CertificateGenerator(); + certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis())); + certGen.setSubjectDN(dnName); + certGen.setIssuerDN(dnName); + certGen.setNotBefore(validityBeginDate); + certGen.setNotAfter(validityEndDate); + certGen.setPublicKey(keyPair.getPublic()); + certGen.setSignatureAlgorithm("SHA256WithRSAEncryption"); + + final X509Certificate cert = certGen.generate(keyPair.getPrivate(), "AndroidOpenSSL"); + return new CertificateAndKey(cert, keyPair.getPrivate()); + } +} diff --git a/services/tests/VpnTests/java/android/net/VpnManagerTest.java b/services/tests/VpnTests/java/android/net/VpnManagerTest.java new file mode 100644 index 000000000000..365b4d1c25f8 --- /dev/null +++ b/services/tests/VpnTests/java/android/net/VpnManagerTest.java @@ -0,0 +1,148 @@ +/* + * 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.net; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assume.assumeFalse; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Build; +import android.test.mock.MockContext; +import android.util.SparseArray; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; + +import com.android.internal.net.VpnProfile; +import com.android.internal.util.MessageUtils; +import com.android.testutils.DevSdkIgnoreRule; +import com.android.testutils.DevSdkIgnoreRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Unit tests for {@link VpnManager}. */ +@SmallTest +@RunWith(DevSdkIgnoreRunner.class) +@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) +public class VpnManagerTest { + + private static final String PKG_NAME = "fooPackage"; + + private static final String SESSION_NAME_STRING = "testSession"; + private static final String SERVER_ADDR_STRING = "1.2.3.4"; + private static final String IDENTITY_STRING = "Identity"; + private static final byte[] PSK_BYTES = "preSharedKey".getBytes(); + + private IVpnManager mMockService; + private VpnManager mVpnManager; + private final MockContext mMockContext = + new MockContext() { + @Override + public String getOpPackageName() { + return PKG_NAME; + } + }; + + @Before + public void setUp() throws Exception { + assumeFalse("Skipping test because watches don't support VPN", + InstrumentationRegistry.getContext().getPackageManager().hasSystemFeature( + PackageManager.FEATURE_WATCH)); + mMockService = mock(IVpnManager.class); + mVpnManager = new VpnManager(mMockContext, mMockService); + } + + @Test + public void testProvisionVpnProfilePreconsented() throws Exception { + final PlatformVpnProfile profile = getPlatformVpnProfile(); + when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME))) + .thenReturn(true); + + // Expect there to be no intent returned, as consent has already been granted. + assertNull(mVpnManager.provisionVpnProfile(profile)); + verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME)); + } + + @Test + public void testProvisionVpnProfileNeedsConsent() throws Exception { + final PlatformVpnProfile profile = getPlatformVpnProfile(); + when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME))) + .thenReturn(false); + + // Expect intent to be returned, as consent has not already been granted. + final Intent intent = mVpnManager.provisionVpnProfile(profile); + assertNotNull(intent); + + final ComponentName expectedComponentName = + ComponentName.unflattenFromString( + "com.android.vpndialogs/com.android.vpndialogs.PlatformVpnConfirmDialog"); + assertEquals(expectedComponentName, intent.getComponent()); + verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME)); + } + + @Test + public void testDeleteProvisionedVpnProfile() throws Exception { + mVpnManager.deleteProvisionedVpnProfile(); + verify(mMockService).deleteVpnProfile(eq(PKG_NAME)); + } + + @Test + public void testStartProvisionedVpnProfile() throws Exception { + mVpnManager.startProvisionedVpnProfile(); + verify(mMockService).startVpnProfile(eq(PKG_NAME)); + } + + @Test + public void testStopProvisionedVpnProfile() throws Exception { + mVpnManager.stopProvisionedVpnProfile(); + verify(mMockService).stopVpnProfile(eq(PKG_NAME)); + } + + private Ikev2VpnProfile getPlatformVpnProfile() throws Exception { + return new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING) + .setBypassable(true) + .setMaxMtu(1300) + .setMetered(true) + .setAuthPsk(PSK_BYTES) + .build(); + } + + @Test + public void testVpnTypesEqual() throws Exception { + SparseArray<String> vmVpnTypes = MessageUtils.findMessageNames( + new Class[] { VpnManager.class }, new String[]{ "TYPE_VPN_" }); + SparseArray<String> nativeVpnType = MessageUtils.findMessageNames( + new Class[] { NativeVpnType.class }, new String[]{ "" }); + + // TYPE_VPN_NONE = -1 is only defined in VpnManager. + assertEquals(vmVpnTypes.size() - 1, nativeVpnType.size()); + for (int i = VpnManager.TYPE_VPN_SERVICE; i < vmVpnTypes.size(); i++) { + assertEquals(vmVpnTypes.get(i), "TYPE_VPN_" + nativeVpnType.get(i)); + } + } +} diff --git a/services/tests/VpnTests/java/com/android/internal/net/VpnProfileTest.java b/services/tests/VpnTests/java/com/android/internal/net/VpnProfileTest.java new file mode 100644 index 000000000000..acae7d25cdb6 --- /dev/null +++ b/services/tests/VpnTests/java/com/android/internal/net/VpnProfileTest.java @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net; + +import static android.net.cts.util.IkeSessionTestUtils.CHILD_PARAMS; +import static android.net.cts.util.IkeSessionTestUtils.IKE_PARAMS_V4; + +import static com.android.modules.utils.build.SdkLevel.isAtLeastT; +import static com.android.modules.utils.build.SdkLevel.isAtLeastU; +import static com.android.testutils.ParcelUtils.assertParcelSane; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.net.IpSecAlgorithm; +import android.net.ipsec.ike.IkeTunnelConnectionParams; +import android.os.Build; + +import androidx.test.filters.SmallTest; + +import com.android.testutils.DevSdkIgnoreRule; +import com.android.testutils.DevSdkIgnoreRunner; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** Unit tests for {@link VpnProfile}. */ +@SmallTest +@RunWith(DevSdkIgnoreRunner.class) +@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) +public class VpnProfileTest { + private static final String DUMMY_PROFILE_KEY = "Test"; + + private static final int ENCODED_INDEX_AUTH_PARAMS_INLINE = 23; + private static final int ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS = 24; + private static final int ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE = 25; + private static final int ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION = 26; + private static final int ENCODED_INDEX_IKE_TUN_CONN_PARAMS = 27; + private static final int ENCODED_INDEX_AUTOMATIC_NATT_KEEPALIVE_TIMER_ENABLED = 28; + private static final int ENCODED_INDEX_AUTOMATIC_IP_VERSION_SELECTION_ENABLED = 29; + + @Test + public void testDefaults() throws Exception { + final VpnProfile p = new VpnProfile(DUMMY_PROFILE_KEY); + + assertEquals(DUMMY_PROFILE_KEY, p.key); + assertEquals("", p.name); + assertEquals(VpnProfile.TYPE_PPTP, p.type); + assertEquals("", p.server); + assertEquals("", p.username); + assertEquals("", p.password); + assertEquals("", p.dnsServers); + assertEquals("", p.searchDomains); + assertEquals("", p.routes); + assertTrue(p.mppe); + assertEquals("", p.l2tpSecret); + assertEquals("", p.ipsecIdentifier); + assertEquals("", p.ipsecSecret); + assertEquals("", p.ipsecUserCert); + assertEquals("", p.ipsecCaCert); + assertEquals("", p.ipsecServerCert); + assertEquals(null, p.proxy); + assertTrue(p.getAllowedAlgorithms() != null && p.getAllowedAlgorithms().isEmpty()); + assertFalse(p.isBypassable); + assertFalse(p.isMetered); + assertEquals(1360, p.maxMtu); + assertFalse(p.areAuthParamsInline); + assertFalse(p.isRestrictedToTestNetworks); + assertFalse(p.excludeLocalRoutes); + assertFalse(p.requiresInternetValidation); + assertFalse(p.automaticNattKeepaliveTimerEnabled); + assertFalse(p.automaticIpVersionSelectionEnabled); + } + + private VpnProfile getSampleIkev2Profile(String key) { + final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */, + false /* excludesLocalRoutes */, true /* requiresPlatformValidation */, + null /* ikeTunConnParams */, true /* mAutomaticNattKeepaliveTimerEnabled */, + true /* automaticIpVersionSelectionEnabled */); + + p.name = "foo"; + p.type = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS; + p.server = "bar"; + p.username = "baz"; + p.password = "qux"; + p.dnsServers = "8.8.8.8"; + p.searchDomains = ""; + p.routes = "0.0.0.0/0"; + p.mppe = false; + p.l2tpSecret = ""; + p.ipsecIdentifier = "quux"; + p.ipsecSecret = "quuz"; + p.ipsecUserCert = "corge"; + p.ipsecCaCert = "grault"; + p.ipsecServerCert = "garply"; + p.proxy = null; + p.setAllowedAlgorithms( + Arrays.asList( + IpSecAlgorithm.AUTH_CRYPT_AES_GCM, + IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305, + IpSecAlgorithm.AUTH_HMAC_SHA512, + IpSecAlgorithm.CRYPT_AES_CBC)); + p.isBypassable = true; + p.isMetered = true; + p.maxMtu = 1350; + p.areAuthParamsInline = true; + + // Not saved, but also not compared. + p.saveLogin = true; + + return p; + } + + private VpnProfile getSampleIkev2ProfileWithIkeTunConnParams(String key) { + final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */, + false /* excludesLocalRoutes */, true /* requiresPlatformValidation */, + new IkeTunnelConnectionParams(IKE_PARAMS_V4, CHILD_PARAMS), + true /* mAutomaticNattKeepaliveTimerEnabled */, + true /* automaticIpVersionSelectionEnabled */); + + p.name = "foo"; + p.server = "bar"; + p.dnsServers = "8.8.8.8"; + p.searchDomains = ""; + p.routes = "0.0.0.0/0"; + p.mppe = false; + p.proxy = null; + p.setAllowedAlgorithms( + Arrays.asList( + IpSecAlgorithm.AUTH_CRYPT_AES_GCM, + IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305, + IpSecAlgorithm.AUTH_HMAC_SHA512, + IpSecAlgorithm.CRYPT_AES_CBC)); + p.isBypassable = true; + p.isMetered = true; + p.maxMtu = 1350; + p.areAuthParamsInline = true; + + // Not saved, but also not compared. + p.saveLogin = true; + + return p; + } + + @Test + public void testEquals() { + assertEquals( + getSampleIkev2Profile(DUMMY_PROFILE_KEY), getSampleIkev2Profile(DUMMY_PROFILE_KEY)); + + final VpnProfile modified = getSampleIkev2Profile(DUMMY_PROFILE_KEY); + modified.maxMtu--; + assertNotEquals(getSampleIkev2Profile(DUMMY_PROFILE_KEY), modified); + } + + @Test + public void testParcelUnparcel() { + if (isAtLeastU()) { + // automaticNattKeepaliveTimerEnabled, automaticIpVersionSelectionEnabled added in U. + assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 28); + assertParcelSane(getSampleIkev2ProfileWithIkeTunConnParams(DUMMY_PROFILE_KEY), 28); + } else if (isAtLeastT()) { + // excludeLocalRoutes, requiresPlatformValidation were added in T. + assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 26); + assertParcelSane(getSampleIkev2ProfileWithIkeTunConnParams(DUMMY_PROFILE_KEY), 26); + } else { + assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 23); + } + } + + @Test + public void testEncodeDecodeWithIkeTunConnParams() { + final VpnProfile profile = getSampleIkev2ProfileWithIkeTunConnParams(DUMMY_PROFILE_KEY); + final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode()); + assertEquals(profile, decoded); + } + + @Test + public void testEncodeDecode() { + final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY); + final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode()); + assertEquals(profile, decoded); + } + + @Test + public void testEncodeDecodeTooManyValues() { + final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY); + final byte[] tooManyValues = + (new String(profile.encode()) + VpnProfile.VALUE_DELIMITER + "invalid").getBytes(); + + assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooManyValues)); + } + + private String getEncodedDecodedIkev2ProfileMissingValues(int... missingIndices) { + // Sort to ensure when we remove, we can do it from greatest first. + Arrays.sort(missingIndices); + + final String encoded = new String(getSampleIkev2Profile(DUMMY_PROFILE_KEY).encode()); + final List<String> parts = + new ArrayList<>(Arrays.asList(encoded.split(VpnProfile.VALUE_DELIMITER))); + + // Remove from back first to ensure indexing is consistent. + for (int i = missingIndices.length - 1; i >= 0; i--) { + parts.remove(missingIndices[i]); + } + + return String.join(VpnProfile.VALUE_DELIMITER, parts.toArray(new String[0])); + } + + @Test + public void testEncodeDecodeInvalidNumberOfValues() { + final String tooFewValues = + getEncodedDecodedIkev2ProfileMissingValues( + ENCODED_INDEX_AUTH_PARAMS_INLINE, + ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS, + ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE, + ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION, + ENCODED_INDEX_IKE_TUN_CONN_PARAMS, + ENCODED_INDEX_AUTOMATIC_NATT_KEEPALIVE_TIMER_ENABLED, + ENCODED_INDEX_AUTOMATIC_IP_VERSION_SELECTION_ENABLED + /* missingIndices */); + + assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes())); + } + + private String getEncodedDecodedIkev2ProfileWithtooFewValues() { + return getEncodedDecodedIkev2ProfileMissingValues( + ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS, + ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE, + ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION, + ENCODED_INDEX_IKE_TUN_CONN_PARAMS, + ENCODED_INDEX_AUTOMATIC_NATT_KEEPALIVE_TIMER_ENABLED, + ENCODED_INDEX_AUTOMATIC_IP_VERSION_SELECTION_ENABLED /* missingIndices */); + } + + @Test + public void testEncodeDecodeMissingIsRestrictedToTestNetworks() { + final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues(); + + // Verify decoding without isRestrictedToTestNetworks defaults to false + final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes()); + assertFalse(decoded.isRestrictedToTestNetworks); + } + + @Test + public void testEncodeDecodeMissingExcludeLocalRoutes() { + final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues(); + + // Verify decoding without excludeLocalRoutes defaults to false + final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes()); + assertFalse(decoded.excludeLocalRoutes); + } + + @Test + public void testEncodeDecodeMissingRequiresValidation() { + final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues(); + + // Verify decoding without requiresValidation defaults to false + final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes()); + assertFalse(decoded.requiresInternetValidation); + } + + @Test + public void testEncodeDecodeMissingAutomaticNattKeepaliveTimerEnabled() { + final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues(); + + // Verify decoding without automaticNattKeepaliveTimerEnabled defaults to false + final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes()); + assertFalse(decoded.automaticNattKeepaliveTimerEnabled); + } + + @Test + public void testEncodeDecodeMissingAutomaticIpVersionSelectionEnabled() { + final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues(); + + // Verify decoding without automaticIpVersionSelectionEnabled defaults to false + final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes()); + assertFalse(decoded.automaticIpVersionSelectionEnabled); + } + + @Test + public void testEncodeDecodeLoginsNotSaved() { + final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY); + profile.saveLogin = false; + + final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode()); + assertNotEquals(profile, decoded); + + // Add the username/password back, everything else must be equal. + decoded.username = profile.username; + decoded.password = profile.password; + assertEquals(profile, decoded); + } + + @Test + public void testClone() { + final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY); + final VpnProfile clone = profile.clone(); + assertEquals(profile, clone); + assertNotSame(profile, clone); + } +} diff --git a/services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java b/services/tests/VpnTests/java/com/android/server/net/LockdownVpnTrackerTest.java index 0e881efd4cdf..0e881efd4cdf 100644 --- a/services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java +++ b/services/tests/VpnTests/java/com/android/server/net/LockdownVpnTrackerTest.java diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java index fcee70fd0702..e9315c8ed8e6 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -1727,6 +1727,27 @@ public final class DisplayPowerControllerTest { /* ignoreAnimationLimits= */ anyBoolean()); } + @Test + public void testDefaultDozeBrightness() { + float brightness = 0.121f; + when(mPowerManagerMock.getBrightnessConstraint( + PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE)).thenReturn(brightness); + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); + when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f); + when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness( + any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT); + when(mHolder.hbmController.getCurrentBrightnessMax()) + .thenReturn(PowerManager.BRIGHTNESS_MAX); + + DisplayPowerRequest dpr = new DisplayPowerRequest(); + dpr.policy = DisplayPowerRequest.POLICY_DOZE; + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + verify(mHolder.animator).animateTo(eq(brightness), /* linearSecondTarget= */ anyFloat(), + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); + } + /** * Creates a mock and registers it to {@link LocalServices}. */ diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java index 37958faed1ca..1c681ce21f02 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java @@ -27,6 +27,7 @@ import android.content.Context; import android.content.ContextWrapper; import android.content.res.Resources; import android.hardware.display.DisplayManagerInternal; +import android.os.PowerManager; import android.view.Display; import androidx.test.core.app.ApplicationProvider; @@ -155,6 +156,7 @@ public final class DisplayBrightnessStrategySelectorTest { DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock( DisplayManagerInternal.DisplayPowerRequest.class); displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE; + displayPowerRequest.dozeScreenBrightness = 0.2f; when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn( DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest, @@ -162,6 +164,18 @@ public final class DisplayBrightnessStrategySelectorTest { } @Test + public void selectStrategyDoesNotSelectDozeStrategyWhenInvalidBrightness() { + DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock( + DisplayManagerInternal.DisplayPowerRequest.class); + displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE; + displayPowerRequest.dozeScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; + when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn( + DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING); + assertNotEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest, + Display.STATE_DOZE), mDozeBrightnessModeStrategy); + } + + @Test public void selectStrategySelectsScreenOffStrategyWhenValid() { DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock( DisplayManagerInternal.DisplayPowerRequest.class); diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java index 886780655de2..ba462e363b4e 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java @@ -215,7 +215,7 @@ public class AutomaticBrightnessStrategyTest { mAutomaticBrightnessStrategy.setUseAutoBrightness(true); int targetDisplayState = Display.STATE_DOZE; boolean allowAutoBrightnessWhileDozing = true; - int brightnessReason = BrightnessReason.REASON_DOZE; + int brightnessReason = BrightnessReason.REASON_UNKNOWN; int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE; float lastUserSetBrightness = 0.2f; boolean userSetBrightnessChanged = true; diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java index 3f6117be6143..e141faf599fa 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -2001,6 +2001,59 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { } @Test + public void testReplacePendingToCachedProcess_withDeferrableBroadcast() throws Exception { + // Legacy stack doesn't support deferral + Assume.assumeTrue(mImpl == Impl.MODERN); + + final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); + final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN); + final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE); + final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW); + + setProcessFreezable(receiverGreenApp, true, false); + mQueue.onProcessFreezableChangedLocked(receiverGreenApp); + waitForIdle(); + + final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK) + .addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + final BroadcastOptions opts = BroadcastOptions.makeBasic() + .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE); + + final BroadcastFilter receiverGreen = makeRegisteredReceiver(receiverGreenApp, 10); + final BroadcastFilter receiverBlue = makeRegisteredReceiver(receiverBlueApp, 5); + final BroadcastFilter receiverYellow = makeRegisteredReceiver(receiverYellowApp, 0); + enqueueBroadcast(makeBroadcastRecord(timeTick, callerApp, opts, List.of( + receiverGreen, receiverBlue, receiverYellow))); + + // Enqueue the broadcast again to replace the earlier one + enqueueBroadcast(makeBroadcastRecord(timeTick, callerApp, opts, List.of( + receiverGreen, receiverBlue, receiverYellow))); + + waitForIdle(); + // Green should still be in the cached state and shouldn't receive the broadcast + verifyScheduleRegisteredReceiver(never(), receiverGreenApp, timeTick); + + final IApplicationThread blueThread = receiverBlueApp.getThread(); + final IApplicationThread yellowThread = receiverYellowApp.getThread(); + final InOrder inOrder = inOrder(blueThread, yellowThread); + inOrder.verify(blueThread).scheduleRegisteredReceiver( + any(), argThat(filterEqualsIgnoringComponent(timeTick)), + anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyBoolean(), + eq(UserHandle.USER_SYSTEM), anyInt(), anyInt(), any()); + inOrder.verify(yellowThread).scheduleRegisteredReceiver( + any(), argThat(filterEqualsIgnoringComponent(timeTick)), + anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyBoolean(), + eq(UserHandle.USER_SYSTEM), anyInt(), anyInt(), any()); + + setProcessFreezable(receiverGreenApp, false, false); + mQueue.onProcessFreezableChangedLocked(receiverGreenApp); + waitForIdle(); + + // Confirm that green receives the broadcast once it comes out of the cached state + verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, timeTick); + } + + @Test public void testIdleAndBarrier() throws Exception { final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN); diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp b/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp new file mode 100644 index 000000000000..e94b8ad0a9ac --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp @@ -0,0 +1,56 @@ +// Copyright (C) 2024 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "RollbackPackageHealthObserverTests", + + srcs: [ + "*.java", + ], + + static_libs: [ + "androidx.test.runner", + "mockito-target-extended-minus-junit4", + "services.core", + "truth", + "flag-junit", + ], + + libs: [ + "android.test.mock", + "android.test.base", + "android.test.runner", + ], + + jni_libs: [ + "libdexmakerjvmtiagent", + "libstaticjvmtiagent", + ], + + certificate: "platform", + platform_apis: true, + test_suites: [ + "device-tests", + "automotive-tests", + ], +} diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidManifest.xml b/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidManifest.xml new file mode 100644 index 000000000000..c52dbdee4b4b --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidManifest.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.rollback"> + + <uses-sdk android:targetSdkVersion="35" /> + + <application android:testOnly="true" + android:debuggable="true"> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.server.rollback" + android:label="Frameworks Rollback Package Health Observer test" /> +</manifest> diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidTest.xml b/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidTest.xml new file mode 100644 index 000000000000..635183c553bf --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidTest.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<configuration description="Runs Rollback Package Health Observer Tests."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-instrumentation" /> + + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="install-arg" value="-t" /> + <option name="test-file-name" value="RollbackPackageHealthObserverTests.apk" /> + </target_preparer> + + <option name="test-tag" value="RollbackPackageHealthObserverTests" /> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.server.rollback" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration> diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING b/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING index e42bdad97730..6ac56bfc254a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING +++ b/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING @@ -1,7 +1,7 @@ { "postsubmit": [ { - "name": "FrameworksMockingServicesTests", + "name": "RollbackPackageHealthObserverTests", "options": [ { "include-filter": "com.android.server.rollback" diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java new file mode 100644 index 000000000000..7ecc7fd1b94b --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java @@ -0,0 +1,640 @@ +/* + * 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.wallpaper; + +import static android.app.WallpaperManager.LANDSCAPE; +import static android.app.WallpaperManager.ORIENTATION_UNKNOWN; +import static android.app.WallpaperManager.PORTRAIT; +import static android.app.WallpaperManager.SQUARE_LANDSCAPE; +import static android.app.WallpaperManager.SQUARE_PORTRAIT; +import static android.app.WallpaperManager.getOrientation; +import static android.app.WallpaperManager.getRotatedOrientation; + +import static com.android.window.flags.Flags.FLAG_MULTI_CROP; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; + +import android.graphics.Point; +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.util.SparseArray; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; + +import java.util.Comparator; +import java.util.List; + +/** + * Unit tests for the most important helpers of {@link WallpaperCropper}, in particular + * {@link WallpaperCropper#getCrop(Point, Point, SparseArray, boolean)}. + */ +@Presubmit +@RunWith(AndroidJUnit4.class) +@RequiresFlagsEnabled(FLAG_MULTI_CROP) +public class WallpaperCropperTest { + + @Mock + private WallpaperDisplayHelper mWallpaperDisplayHelper; + private WallpaperCropper mWallpaperCropper; + + private static final Point PORTRAIT_ONE = new Point(500, 800); + private static final Point PORTRAIT_TWO = new Point(400, 1000); + private static final Point PORTRAIT_THREE = new Point(2000, 800); + private static final Point PORTRAIT_FOUR = new Point(1600, 1000); + + private static final Point SQUARE_PORTRAIT_ONE = new Point(1000, 800); + private static final Point SQUARE_LANDSCAPE_ONE = new Point(800, 1000); + + /** + * Common device: a single screen of portrait/landscape orientation + */ + private static final List<Point> STANDARD_DISPLAY = List.of(PORTRAIT_ONE); + + /** 1: folded: portrait, unfolded: square with w < h */ + private static final List<Point> FOLDABLE_ONE = List.of(PORTRAIT_ONE, SQUARE_PORTRAIT_ONE); + + /** 2: folded: portrait, unfolded: square with w > h */ + private static final List<Point> FOLDABLE_TWO = List.of(PORTRAIT_TWO, SQUARE_LANDSCAPE_ONE); + + /** 3: folded: square with w < h, unfolded: portrait */ + private static final List<Point> FOLDABLE_THREE = List.of(SQUARE_PORTRAIT_ONE, PORTRAIT_THREE); + + /** 4: folded: square with w > h, unfolded: portrait */ + private static final List<Point> FOLDABLE_FOUR = List.of(SQUARE_LANDSCAPE_ONE, PORTRAIT_FOUR); + + /** + * List of different sets of displays for foldable devices. Foldable devices have two displays: + * a folded (smaller) unfolded (larger). + */ + private static final List<List<Point>> ALL_FOLDABLE_DISPLAYS = List.of( + FOLDABLE_ONE, FOLDABLE_TWO, FOLDABLE_THREE, FOLDABLE_FOUR); + + private SparseArray<Point> mDisplaySizes = new SparseArray<>(); + private int mFolded = ORIENTATION_UNKNOWN; + private int mFoldedRotated = ORIENTATION_UNKNOWN; + private int mUnfolded = ORIENTATION_UNKNOWN; + private int mUnfoldedRotated = ORIENTATION_UNKNOWN; + + private static final List<Integer> ALL_MODES = List.of( + WallpaperCropper.ADD, WallpaperCropper.REMOVE, WallpaperCropper.BALANCE); + + @Before + public void setUp() { + initMocks(this); + mWallpaperCropper = new WallpaperCropper(mWallpaperDisplayHelper); + } + + private void setUpWithDisplays(List<Point> displaySizes) { + mDisplaySizes = new SparseArray<>(); + displaySizes.forEach(size -> { + mDisplaySizes.put(getOrientation(size), size); + Point rotated = new Point(size.y, size.x); + mDisplaySizes.put(getOrientation(rotated), rotated); + }); + when(mWallpaperDisplayHelper.getDefaultDisplaySizes()).thenReturn(mDisplaySizes); + if (displaySizes.size() == 2) { + Point largestDisplay = displaySizes.stream().max( + Comparator.comparingInt(p -> p.x * p.y)).get(); + Point smallestDisplay = displaySizes.stream().min( + Comparator.comparingInt(p -> p.x * p.y)).get(); + mUnfolded = getOrientation(largestDisplay); + mFolded = getOrientation(smallestDisplay); + mUnfoldedRotated = getRotatedOrientation(mUnfolded); + mFoldedRotated = getRotatedOrientation(mFolded); + } + doAnswer(invocation -> getFoldedOrientation(invocation.getArgument(0))) + .when(mWallpaperDisplayHelper).getFoldedOrientation(anyInt()); + doAnswer(invocation -> getUnfoldedOrientation(invocation.getArgument(0))) + .when(mWallpaperDisplayHelper).getUnfoldedOrientation(anyInt()); + } + + private int getFoldedOrientation(int orientation) { + if (orientation == ORIENTATION_UNKNOWN) return ORIENTATION_UNKNOWN; + if (orientation == mUnfolded) return mFolded; + if (orientation == mUnfoldedRotated) return mFoldedRotated; + return ORIENTATION_UNKNOWN; + } + + private int getUnfoldedOrientation(int orientation) { + if (orientation == ORIENTATION_UNKNOWN) return ORIENTATION_UNKNOWN; + if (orientation == mFolded) return mUnfolded; + if (orientation == mFoldedRotated) return mUnfoldedRotated; + return ORIENTATION_UNKNOWN; + } + + /** + * Test that {@link WallpaperCropper#noParallax} successfully removes the parallax in a simple + * case, removing the right or left part depending on the "rtl" argument. + */ + @Test + public void testNoParallax_noScale() { + Point displaySize = new Point(1000, 1000); + Point bitmapSize = new Point(1200, 1000); + Point expectedCropSize = new Point(1000, 1000); + Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y); + assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ false)) + .isEqualTo(leftOf(crop, expectedCropSize)); + assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ true)) + .isEqualTo(rightOf(crop, expectedCropSize)); + } + + /** + * Test that {@link WallpaperCropper#noParallax} correctly takes zooming into account. + */ + @Test + public void testNoParallax_withScale() { + Point displaySize = new Point(1000, 1000); + Point bitmapSize = new Point(600, 500); + Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y); + Point expectedCropSize = new Point(500, 500); + assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ false)) + .isEqualTo(leftOf(crop, expectedCropSize)); + assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ true)) + .isEqualTo(rightOf(crop, expectedCropSize)); + } + + /** + * Test that {@link WallpaperCropper#noParallax} correctly removes parallax when the image is + * cropped, i.e. when the crop rectangle is not the full bitmap. + */ + @Test + public void testNoParallax_withScaleAndCrop() { + Point displaySize = new Point(1000, 1000); + Point bitmapSize = new Point(2000, 2000); + Rect crop = new Rect(300, 1000, 900, 1500); + Point expectedCropSize = new Point(500, 500); + assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ false)) + .isEqualTo(leftOf(crop, expectedCropSize)); + assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ true)) + .isEqualTo(rightOf(crop, expectedCropSize)); + } + + /** + * Test that {@link WallpaperCropper#getAdjustedCrop} does nothing when the crop has the same + * width/height ratio than the screen. + */ + @Test + public void testGetAdjustedCrop_noOp() { + Point displaySize = new Point(1000, 1000); + + for (Point bitmapSize: List.of( + new Point(1000, 1000), + new Point(2000, 2000), + new Point(500, 500))) { + for (Rect crop: List.of( + new Rect(0, 0, bitmapSize.x, bitmapSize.y), + new Rect(100, 200, bitmapSize.x - 100, bitmapSize.y))) { + for (int mode: ALL_MODES) { + for (boolean rtl: List.of(true, false)) { + for (boolean parallax: List.of(true, false)) { + assertThat(WallpaperCropper.getAdjustedCrop( + crop, bitmapSize, displaySize, parallax, rtl, mode)) + .isEqualTo(crop); + } + } + } + } + } + } + + /** + * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with parallax = true, + * does not keep more width than needed for {@link WallpaperCropper#MAX_PARALLAX}. + */ + @Test + public void testGetAdjustedCrop_tooMuchParallax() { + Point displaySize = new Point(1000, 1000); + int tooLargeWidth = (int) (displaySize.x * (1 + 2 * WallpaperCropper.MAX_PARALLAX)); + Point bitmapSize = new Point(tooLargeWidth, 1000); + Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y); + int expectedWidth = (int) (displaySize.x * (1 + WallpaperCropper.MAX_PARALLAX)); + Point expectedCropSize = new Point(expectedWidth, 1000); + for (int mode: ALL_MODES) { + assertThat(WallpaperCropper.getAdjustedCrop( + crop, bitmapSize, displaySize, true, false, mode)) + .isEqualTo(leftOf(crop, expectedCropSize)); + assertThat(WallpaperCropper.getAdjustedCrop( + crop, bitmapSize, displaySize, true, true, mode)) + .isEqualTo(rightOf(crop, expectedCropSize)); + } + } + + /** + * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with parallax = true, + * does not remove parallax if the parallax is below {@link WallpaperCropper#MAX_PARALLAX}. + */ + @Test + public void testGetAdjustedCrop_acceptableParallax() { + Point displaySize = new Point(1000, 1000); + List<Integer> acceptableWidths = List.of(displaySize.x, + (int) (displaySize.x * (1 + 0.5 * WallpaperCropper.MAX_PARALLAX)), + (int) (displaySize.x * (1 + 0.9 * WallpaperCropper.MAX_PARALLAX)), + (int) (displaySize.x * (1 + 1.0 * WallpaperCropper.MAX_PARALLAX))); + for (int acceptableWidth: acceptableWidths) { + Point bitmapSize = new Point(acceptableWidth, 1000); + Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y); + for (int mode : ALL_MODES) { + for (boolean rtl : List.of(false, true)) { + assertThat(WallpaperCropper.getAdjustedCrop( + crop, bitmapSize, displaySize, true, rtl, mode)) + .isEqualTo(crop); + } + } + } + } + + /** + * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with + * {@link WallpaperCropper#ADD}, correctly enlarges the crop to match the display dimensions, + * and adds content to the crop by an equal amount on both sides when possible. + */ + @Test + public void testGetAdjustedCrop_add() { + Point displaySize = new Point(1000, 1000); + Point bitmapSize = new Point(1000, 1000); + + List<Rect> crops = List.of( + new Rect(0, 0, 900, 1000), + new Rect(0, 0, 1000, 900), + new Rect(0, 0, 400, 500), + new Rect(500, 600, 1000, 1000)); + + List<Rect> expectedAdjustedCrops = List.of( + new Rect(0, 0, 1000, 1000), + new Rect(0, 0, 1000, 1000), + new Rect(0, 0, 500, 500), + new Rect(500, 500, 1000, 1000)); + + for (int i = 0; i < crops.size(); i++) { + Rect crop = crops.get(i); + Rect expectedCrop = expectedAdjustedCrops.get(i); + for (boolean rtl: List.of(false, true)) { + assertThat(WallpaperCropper.getAdjustedCrop( + crop, bitmapSize, displaySize, false, rtl, WallpaperCropper.ADD)) + .isEqualTo(expectedCrop); + } + } + } + + /** + * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with + * {@link WallpaperCropper#REMOVE}, correctly shrinks the crop to match the display dimensions, + * and removes content by an equal amount on both sides. + */ + @Test + public void testGetAdjustedCrop_remove() { + Point displaySize = new Point(1000, 1000); + Point bitmapSize = new Point(1500, 1500); + + List<Rect> crops = List.of( + new Rect(50, 0, 1150, 1000), + new Rect(0, 50, 1000, 1150)); + + Point expectedCropSize = new Point(1000, 1000); + + for (Rect crop: crops) { + for (boolean rtl : List.of(false, true)) { + assertThat(WallpaperCropper.getAdjustedCrop( + crop, bitmapSize, displaySize, false, rtl, WallpaperCropper.REMOVE)) + .isEqualTo(centerOf(crop, expectedCropSize)); + } + } + } + + /** + * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with + * {@link WallpaperCropper#BALANCE}, gives an adjusted crop with the same center and same number + * of pixels when possible. + */ + @Test + public void testGetAdjustedCrop_balance() { + Point displaySize = new Point(500, 1000); + Point transposedDisplaySize = new Point(1000, 500); + Point bitmapSize = new Point(1000, 1000); + + List<Rect> crops = List.of( + new Rect(0, 250, 1000, 750), + new Rect(100, 0, 300, 100)); + + List<Rect> expectedAdjustedCrops = List.of( + new Rect(250, 0, 750, 1000), + new Rect(150, 0, 250, 200)); + + for (int i = 0; i < crops.size(); i++) { + Rect crop = crops.get(i); + Rect expected = expectedAdjustedCrops.get(i); + assertThat(WallpaperCropper.getAdjustedCrop( + crop, bitmapSize, displaySize, false, false, WallpaperCropper.BALANCE)) + .isEqualTo(expected); + + Rect transposedCrop = new Rect(crop.top, crop.left, crop.bottom, crop.right); + Rect expectedTransposed = new Rect( + expected.top, expected.left, expected.bottom, expected.right); + assertThat(WallpaperCropper.getAdjustedCrop(transposedCrop, bitmapSize, + transposedDisplaySize, false, false, WallpaperCropper.BALANCE)) + .isEqualTo(expectedTransposed); + } + } + + /** + * Test that {@link WallpaperCropper#getCrop} follows a simple centre-align strategy when + * no suggested crops are provided. + */ + @Test + public void testGetCrop_noSuggestedCrops_centersWallpaper() { + setUpWithDisplays(STANDARD_DISPLAY); + Point bitmapSize = new Point(800, 1000); + Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y); + SparseArray<Rect> suggestedCrops = new SparseArray<>(); + + List<Point> displaySizes = List.of( + new Point(500, 1000), + new Point(1000, 500)); + List<Point> expectedCropSizes = List.of( + new Point(500, 1000), + new Point(800, 400)); + + for (int i = 0; i < displaySizes.size(); i++) { + Point displaySize = displaySizes.get(i); + Point expectedCropSize = expectedCropSizes.get(i); + for (boolean rtl : List.of(false, true)) { + assertThat(mWallpaperCropper.getCrop( + displaySize, bitmapSize, suggestedCrops, rtl)) + .isEqualTo(centerOf(bitmapRect, expectedCropSize)); + } + } + } + + /** + * Test that {@link WallpaperCropper#getCrop} reuses a suggested crop of the same orientation + * as the display if possible, and does not remove additional width for parallax, + * but adds width if necessary. + */ + @Test + public void testGetCrop_hasSuggestedCrop() { + setUpWithDisplays(STANDARD_DISPLAY); + Point bitmapSize = new Point(800, 1000); + SparseArray<Rect> suggestedCrops = new SparseArray<>(); + suggestedCrops.put(PORTRAIT, new Rect(0, 0, 400, 800)); + for (int otherOrientation: List.of(LANDSCAPE, SQUARE_LANDSCAPE, SQUARE_PORTRAIT)) { + suggestedCrops.put(otherOrientation, new Rect(0, 0, 10, 10)); + } + + for (boolean rtl : List.of(false, true)) { + assertThat(mWallpaperCropper.getCrop( + new Point(300, 800), bitmapSize, suggestedCrops, rtl)) + .isEqualTo(suggestedCrops.get(PORTRAIT)); + assertThat(mWallpaperCropper.getCrop( + new Point(500, 800), bitmapSize, suggestedCrops, rtl)) + .isEqualTo(new Rect(0, 0, 500, 800)); + } + } + + /** + * Test that {@link WallpaperCropper#getCrop}, if there is no suggested crop of the same + * orientation as the display, reuses a suggested crop of the rotated orientation if possible, + * and preserves the center and number of pixels of the crop if possible. + * <p> + * To simplify, in this test case all crops have the same size as the display (no zoom) + * and are at the center of the image. Also the image is large enough to preserver the number + * of pixels (no additional zoom required). + */ + @Test + public void testGetCrop_hasRotatedSuggestedCrop() { + setUpWithDisplays(STANDARD_DISPLAY); + Point bitmapSize = new Point(2000, 1800); + Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y); + SparseArray<Rect> suggestedCrops = new SparseArray<>(); + Point portrait = PORTRAIT_ONE; + Point landscape = new Point(PORTRAIT_ONE.y, PORTRAIT_ONE.x); + Point squarePortrait = SQUARE_PORTRAIT_ONE; + Point squareLandscape = new Point(SQUARE_PORTRAIT_ONE.y, SQUARE_PORTRAIT_ONE.y); + suggestedCrops.put(PORTRAIT, centerOf(bitmapRect, portrait)); + suggestedCrops.put(SQUARE_LANDSCAPE, centerOf(bitmapRect, squareLandscape)); + for (boolean rtl : List.of(false, true)) { + assertThat(mWallpaperCropper.getCrop( + landscape, bitmapSize, suggestedCrops, rtl)) + .isEqualTo(centerOf(bitmapRect, landscape)); + + assertThat(mWallpaperCropper.getCrop( + squarePortrait, bitmapSize, suggestedCrops, rtl)) + .isEqualTo(centerOf(bitmapRect, squarePortrait)); + } + } + + /** + * Test that {@link WallpaperCropper#getCrop}, when asked for a folded crop with a suggested + * crop only for the relative unfolded orientation, creates the folded crop at the center of the + * unfolded crop, by removing content on two sides to match the folded screen dimensions. + * <p> + * To simplify, in this test case all crops have the same size as the display (no zoom) + * and are at the center of the image. + */ + @Test + public void testGetCrop_hasUnfoldedSuggestedCrop() { + for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) { + setUpWithDisplays(displaySizes); + Point bitmapSize = new Point(2000, 2400); + Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y); + + Point largestDisplay = displaySizes.stream().max( + Comparator.comparingInt(a -> a.x * a.y)).orElseThrow(); + int unfoldedOne = getOrientation(largestDisplay); + int unfoldedTwo = getRotatedOrientation(unfoldedOne); + Rect unfoldedCropOne = centerOf(bitmapRect, mDisplaySizes.get(unfoldedOne)); + Rect unfoldedCropTwo = centerOf(bitmapRect, mDisplaySizes.get(unfoldedTwo)); + SparseArray<Rect> suggestedCrops = new SparseArray<>(); + suggestedCrops.put(unfoldedOne, unfoldedCropOne); + suggestedCrops.put(unfoldedTwo, unfoldedCropTwo); + + int foldedOne = getFoldedOrientation(unfoldedOne); + int foldedTwo = getFoldedOrientation(unfoldedTwo); + Point foldedDisplayOne = mDisplaySizes.get(foldedOne); + Point foldedDisplayTwo = mDisplaySizes.get(foldedTwo); + + for (boolean rtl : List.of(false, true)) { + assertThat(mWallpaperCropper.getCrop( + foldedDisplayOne, bitmapSize, suggestedCrops, rtl)) + .isEqualTo(centerOf(unfoldedCropOne, foldedDisplayOne)); + + assertThat(mWallpaperCropper.getCrop( + foldedDisplayTwo, bitmapSize, suggestedCrops, rtl)) + .isEqualTo(centerOf(unfoldedCropTwo, foldedDisplayTwo)); + } + } + } + + /** + * Test that {@link WallpaperCropper#getCrop}, when asked for an unfolded crop with a suggested + * crop only for the relative folded orientation, creates the unfolded crop with the same center + * as the folded crop, by adding content on two sides to match the unfolded screen dimensions. + * <p> + * To simplify, in this test case all crops have the same size as the display (no zoom) and are + * at the center of the image. Also the image is large enough to add content. + */ + @Test + public void testGetCrop_hasFoldedSuggestedCrop() { + for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) { + setUpWithDisplays(displaySizes); + Point bitmapSize = new Point(2000, 2000); + Rect bitmapRect = new Rect(0, 0, 2000, 2000); + + Point smallestDisplay = displaySizes.stream().min( + Comparator.comparingInt(a -> a.x * a.y)).orElseThrow(); + int foldedOne = getOrientation(smallestDisplay); + int foldedTwo = getRotatedOrientation(foldedOne); + Point foldedDisplayOne = mDisplaySizes.get(foldedOne); + Point foldedDisplayTwo = mDisplaySizes.get(foldedTwo); + Rect foldedCropOne = centerOf(bitmapRect, foldedDisplayOne); + Rect foldedCropTwo = centerOf(bitmapRect, foldedDisplayTwo); + SparseArray<Rect> suggestedCrops = new SparseArray<>(); + suggestedCrops.put(foldedOne, foldedCropOne); + suggestedCrops.put(foldedTwo, foldedCropTwo); + + int unfoldedOne = getUnfoldedOrientation(foldedOne); + int unfoldedTwo = getUnfoldedOrientation(foldedTwo); + Point unfoldedDisplayOne = mDisplaySizes.get(unfoldedOne); + Point unfoldedDisplayTwo = mDisplaySizes.get(unfoldedTwo); + + for (boolean rtl : List.of(false, true)) { + assertThat(centerOf(mWallpaperCropper.getCrop( + unfoldedDisplayOne, bitmapSize, suggestedCrops, rtl), foldedDisplayOne)) + .isEqualTo(foldedCropOne); + + assertThat(centerOf(mWallpaperCropper.getCrop( + unfoldedDisplayTwo, bitmapSize, suggestedCrops, rtl), foldedDisplayTwo)) + .isEqualTo(foldedCropTwo); + } + } + } + + /** + * Test that {@link WallpaperCropper#getCrop}, when asked for an folded crop with a suggested + * crop only for the rotated unfolded orientation, creates the folded crop from that crop by + * combining a rotate + fold operation. The folded crop should have less pixels than the + * unfolded crop due to the fold operation which removes content on both sides of the image. + * <p> + * To simplify, in this test case all crops have the same size as the display (no zoom) + * and are at the center of the image. + */ + @Test + public void testGetCrop_hasRotatedUnfoldedSuggestedCrop() { + for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) { + setUpWithDisplays(displaySizes); + Point bitmapSize = new Point(2000, 2000); + Rect bitmapRect = new Rect(0, 0, 2000, 2000); + Point largestDisplay = displaySizes.stream().max( + Comparator.comparingInt(a -> a.x * a.y)).orElseThrow(); + int unfoldedOne = getOrientation(largestDisplay); + int unfoldedTwo = getRotatedOrientation(unfoldedOne); + for (int unfolded: List.of(unfoldedOne, unfoldedTwo)) { + Rect unfoldedCrop = centerOf(bitmapRect, mDisplaySizes.get(unfolded)); + int rotatedUnfolded = getRotatedOrientation(unfolded); + Rect rotatedUnfoldedCrop = centerOf(bitmapRect, mDisplaySizes.get(rotatedUnfolded)); + SparseArray<Rect> suggestedCrops = new SparseArray<>(); + suggestedCrops.put(unfolded, unfoldedCrop); + int rotatedFolded = getFoldedOrientation(rotatedUnfolded); + Point rotatedFoldedDisplay = mDisplaySizes.get(rotatedFolded); + + for (boolean rtl : List.of(false, true)) { + assertThat(mWallpaperCropper.getCrop( + rotatedFoldedDisplay, bitmapSize, suggestedCrops, rtl)) + .isEqualTo(centerOf(rotatedUnfoldedCrop, rotatedFoldedDisplay)); + } + } + } + } + + /** + * Test that {@link WallpaperCropper#getCrop}, when asked for an unfolded crop with a suggested + * crop only for the rotated folded orientation, creates the unfolded crop from that crop by + * combining a rotate + unfold operation. The unfolded crop should have more pixels than the + * folded crop due to the unfold operation which adds content on two sides of the image. + * <p> + * To simplify, in this test case all crops have the same size as the display (no zoom) + * and are centered inside the image. Also the image is large enough to add content. + */ + @Test + public void testGetCrop_hasRotatedFoldedSuggestedCrop() { + for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) { + setUpWithDisplays(displaySizes); + Point bitmapSize = new Point(2000, 2000); + Rect bitmapRect = new Rect(0, 0, 2000, 2000); + + Point smallestDisplay = displaySizes.stream().min( + Comparator.comparingInt(a -> a.x * a.y)).orElseThrow(); + int foldedOne = getOrientation(smallestDisplay); + int foldedTwo = getRotatedOrientation(foldedOne); + for (int folded: List.of(foldedOne, foldedTwo)) { + Rect foldedCrop = centerOf(bitmapRect, mDisplaySizes.get(folded)); + SparseArray<Rect> suggestedCrops = new SparseArray<>(); + suggestedCrops.put(folded, foldedCrop); + int rotatedFolded = getRotatedOrientation(folded); + int rotatedUnfolded = getUnfoldedOrientation(rotatedFolded); + Point rotatedFoldedDisplay = mDisplaySizes.get(rotatedFolded); + Rect rotatedFoldedCrop = centerOf(bitmapRect, rotatedFoldedDisplay); + Point rotatedUnfoldedDisplay = mDisplaySizes.get(rotatedUnfolded); + + for (boolean rtl : List.of(false, true)) { + Rect rotatedUnfoldedCrop = mWallpaperCropper.getCrop( + rotatedUnfoldedDisplay, bitmapSize, suggestedCrops, rtl); + assertThat(centerOf(rotatedUnfoldedCrop, rotatedFoldedDisplay)) + .isEqualTo(rotatedFoldedCrop); + } + } + } + } + + private static Rect centerOf(Rect container, Point point) { + checkSubset(container, point); + int diffWidth = container.width() - point.x; + int diffHeight = container.height() - point.y; + int startX = container.left + diffWidth / 2; + int startY = container.top + diffHeight / 2; + return new Rect(startX, startY, startX + point.x, startY + point.y); + } + + private static Rect leftOf(Rect container, Point point) { + Rect result = centerOf(container, point); + result.offset(container.left - result.left, 0); + return result; + } + + private static Rect rightOf(Rect container, Point point) { + checkSubset(container, point); + Rect result = centerOf(container, point); + result.offset(container.right - result.right, 0); + return result; + } + + private static void checkSubset(Rect container, Point point) { + if (container.width() < point.x || container.height() < point.y) { + throw new IllegalArgumentException(); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java index bb0063427339..fa1fd90e10c9 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java @@ -344,6 +344,21 @@ public class ALSProbeTest { verifyNoMoreInteractions(mSensorManager); } + @Test + public void testAwaitLuxWhenNoLightSensor() { + when(mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)).thenReturn(null); + mProbe = new ALSProbe(mSensorManager, new Handler(mLooper.getLooper()), TIMEOUT_MS - 1); + + AtomicInteger lux = new AtomicInteger(-5); + mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */); + + // Verify that no light sensor will be registered. + verify(mSensorManager, times(0)).registerListener( + mSensorEventListenerCaptor.capture(), any(), anyInt()); + + assertThat(lux.get()).isEqualTo(-1); + } + private void moveTimeBy(long millis) { mLooper.moveTimeForward(millis); mLooper.processAllMessages(); diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java index ea84eb2fbf73..a6f2196cf05b 100644 --- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java @@ -16,8 +16,6 @@ package com.android.server.os; -import android.app.admin.flags.Flags; - import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity; import static com.google.common.truth.Truth.assertThat; @@ -27,15 +25,21 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; +import android.app.admin.DevicePolicyManager; +import android.app.admin.flags.Flags; import android.app.role.RoleManager; import android.content.Context; import android.content.pm.PackageManager; +import android.content.pm.UserInfo; import android.os.Binder; import android.os.BugreportManager.BugreportCallback; +import android.os.BugreportParams; import android.os.IBinder; import android.os.IDumpstateListener; import android.os.Process; import android.os.RemoteException; +import android.os.UserHandle; +import android.os.UserManager; import android.platform.test.annotations.RequiresFlagsDisabled; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; @@ -65,6 +69,9 @@ import java.util.function.Consumer; @RunWith(AndroidJUnit4.class) public class BugreportManagerServiceImplTest { + private static final UserInfo ADMIN_USER_INFO = + new UserInfo(/* id= */ 5678, "adminUser", UserInfo.FLAG_ADMIN); + @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); @@ -75,6 +82,12 @@ public class BugreportManagerServiceImplTest { @Mock private PackageManager mPackageManager; + @Mock + private UserManager mMockUserManager; + @Mock + private DevicePolicyManager mMockDevicePolicyManager; + + private TestInjector mInjector; private int mCallingUid = 1234; private String mCallingPackage = "test.package"; @@ -90,11 +103,13 @@ public class BugreportManagerServiceImplTest { mMappingFile = new AtomicFile(mContext.getFilesDir(), "bugreport-mapping.xml"); ArraySet<String> mAllowlistedPackages = new ArraySet<>(); mAllowlistedPackages.add(mContext.getPackageName()); - mService = new BugreportManagerServiceImpl( - new BugreportManagerServiceImpl.Injector(mContext, mAllowlistedPackages, - mMappingFile)); + mInjector = new TestInjector(mContext, mAllowlistedPackages, mMappingFile, + mMockUserManager, mMockDevicePolicyManager); + mService = new BugreportManagerServiceImpl(mInjector); mBugreportFileManager = new BugreportManagerServiceImpl.BugreportFileManager(mMappingFile); when(mPackageManager.getPackageUidAsUser(anyString(), anyInt())).thenReturn(mCallingUid); + // The calling user is an admin user by default. + when(mMockUserManager.isUserAdmin(anyInt())).thenReturn(true); } @After @@ -182,6 +197,63 @@ public class BugreportManagerServiceImplTest { } @Test + public void testStartBugreport() throws Exception { + mService.startBugreport(mCallingUid, mContext.getPackageName(), + new FileDescriptor(), /* screenshotFd= */ null, + BugreportParams.BUGREPORT_MODE_FULL, + /* flags= */ 0, new Listener(new CountDownLatch(1)), + /* isScreenshotRequested= */ false); + + assertThat(mInjector.isBugreportStarted()).isTrue(); + } + + @Test + public void testStartBugreport_nonAdminProfileOfAdminCurrentUser() throws Exception { + int callingUid = Binder.getCallingUid(); + int callingUserId = UserHandle.getUserId(callingUid); + when(mMockUserManager.isUserAdmin(callingUserId)).thenReturn(false); + when(mMockUserManager.getProfileParent(callingUserId)).thenReturn(ADMIN_USER_INFO); + + mService.startBugreport(mCallingUid, mContext.getPackageName(), + new FileDescriptor(), /* screenshotFd= */ null, + BugreportParams.BUGREPORT_MODE_FULL, + /* flags= */ 0, new Listener(new CountDownLatch(1)), + /* isScreenshotRequested= */ false); + + assertThat(mInjector.isBugreportStarted()).isTrue(); + } + + @Test + public void testStartBugreport_throwsForNonAdminUser() throws Exception { + when(mMockUserManager.isUserAdmin(anyInt())).thenReturn(false); + + Exception thrown = assertThrows(IllegalArgumentException.class, + () -> mService.startBugreport(mCallingUid, mContext.getPackageName(), + new FileDescriptor(), /* screenshotFd= */ null, + BugreportParams.BUGREPORT_MODE_FULL, + /* flags= */ 0, new Listener(new CountDownLatch(1)), + /* isScreenshotRequested= */ false)); + + assertThat(thrown.getMessage()).contains("not an admin user"); + } + + @Test + public void testStartBugreport_throwsForNotAffiliatedUser() throws Exception { + when(mMockUserManager.isUserAdmin(anyInt())).thenReturn(false); + when(mMockDevicePolicyManager.getDeviceOwnerUserId()).thenReturn(-1); + when(mMockDevicePolicyManager.isAffiliatedUser(anyInt())).thenReturn(false); + + Exception thrown = assertThrows(IllegalArgumentException.class, + () -> mService.startBugreport(mCallingUid, mContext.getPackageName(), + new FileDescriptor(), /* screenshotFd= */ null, + BugreportParams.BUGREPORT_MODE_REMOTE, + /* flags= */ 0, new Listener(new CountDownLatch(1)), + /* isScreenshotRequested= */ false)); + + assertThat(thrown.getMessage()).contains("not affiliated to the device owner"); + } + + @Test public void testRetrieveBugreportWithoutFilesForCaller() throws Exception { CountDownLatch latch = new CountDownLatch(1); Listener listener = new Listener(latch); @@ -224,7 +296,8 @@ public class BugreportManagerServiceImplTest { private void clearAllowlist() { mService = new BugreportManagerServiceImpl( - new BugreportManagerServiceImpl.Injector(mContext, new ArraySet<>(), mMappingFile)); + new TestInjector(mContext, new ArraySet<>(), mMappingFile, + mMockUserManager, mMockDevicePolicyManager)); } private static class Listener implements IDumpstateListener { @@ -275,4 +348,46 @@ public class BugreportManagerServiceImplTest { complete(successful); } } + + private static class TestInjector extends BugreportManagerServiceImpl.Injector { + + private static final String SYSTEM_PROPERTY_BUGREPORT_START = "ctl.start"; + private static final String SYSTEM_PROPERTY_BUGREPORT_STOP = "ctl.stop"; + + private final UserManager mUserManager; + private final DevicePolicyManager mDevicePolicyManager; + private boolean mBugreportStarted = false; + + TestInjector(Context context, ArraySet<String> allowlistedPackages, AtomicFile mappingFile, + UserManager um, DevicePolicyManager dpm) { + super(context, allowlistedPackages, mappingFile); + mUserManager = um; + mDevicePolicyManager = dpm; + } + + @Override + public UserManager getUserManager() { + return mUserManager; + } + + @Override + public DevicePolicyManager getDevicePolicyManager() { + return mDevicePolicyManager; + } + + @Override + public void setSystemProperty(String key, String value) { + // Calling SystemProperties.set() will throw a RuntimeException due to permission error. + // Instead, we are just marking a flag to store the state for testing. + if (SYSTEM_PROPERTY_BUGREPORT_START.equals(key)) { + mBugreportStarted = true; + } else if (SYSTEM_PROPERTY_BUGREPORT_STOP.equals(key)) { + mBugreportStarted = false; + } + } + + public boolean isBugreportStarted() { + return mBugreportStarted; + } + } } diff --git a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java index 2039f93b9c40..54d11387752c 100644 --- a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java +++ b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java @@ -96,6 +96,9 @@ public class TestSystemImpl implements SystemInterface { return; } PackageInfo packageInfo = userPackages.get(userId); + if (packageInfo == null) { + return; + } packageInfo.applicationInfo.enabled = enable; setPackageInfoForUser(userId, packageInfo); } @@ -106,6 +109,9 @@ public class TestSystemImpl implements SystemInterface { return; } PackageInfo packageInfo = userPackages.get(userId); + if (packageInfo == null) { + return; + } packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_INSTALLED; packageInfo.applicationInfo.privateFlags &= (~ApplicationInfo.PRIVATE_FLAG_HIDDEN); setPackageInfoForUser(userId, packageInfo); diff --git a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java index c535ec5026db..e181a513b637 100644 --- a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java @@ -1551,7 +1551,7 @@ public class WebViewUpdateServiceTest { @Test @RequiresFlagsEnabled("android.webkit.update_service_v2") - public void testDefaultWebViewPackageInstalling() { + public void testDefaultWebViewPackageInstallingDuringStartUp() { String testPackage = "testDefault"; WebViewProviderInfo[] packages = new WebViewProviderInfo[] { @@ -1574,6 +1574,68 @@ public class WebViewUpdateServiceTest { Matchers.anyObject(), Mockito.eq(testPackage)); } + @Test + @RequiresFlagsEnabled("android.webkit.update_service_v2") + public void testDefaultWebViewPackageInstallingAfterStartUp() { + String testPackage = "testDefault"; + WebViewProviderInfo[] packages = + new WebViewProviderInfo[] { + new WebViewProviderInfo( + testPackage, + "", + true /* default available */, + false /* fallback */, + null) + }; + checkCertainPackageUsedAfterWebViewBootPreparation(testPackage, packages); + + // uninstall the default package. + mTestSystemImpl.setPackageInfo( + createPackageInfo( + testPackage, true /* enabled */, true /* valid */, false /* installed */)); + mWebViewUpdateServiceImpl.packageStateChanged(testPackage, + WebViewUpdateService.PACKAGE_REMOVED, 0); + + // Check that we try to re-install the default package. + Mockito.verify(mTestSystemImpl) + .installExistingPackageForAllUsers( + Matchers.anyObject(), Mockito.eq(testPackage)); + } + + /** + * Ensures that adding a new user for which the current WebView package is uninstalled triggers + * the repair logic. + */ + @Test + @RequiresFlagsEnabled("android.webkit.update_service_v2") + public void testAddingNewUserWithDefaultdPackageNotInstalled() { + String testPackage = "testDefault"; + WebViewProviderInfo[] packages = + new WebViewProviderInfo[] { + new WebViewProviderInfo( + testPackage, + "", + true /* default available */, + false /* fallback */, + null) + }; + checkCertainPackageUsedAfterWebViewBootPreparation(testPackage, packages); + + // Add new user with the default package not installed. + int newUser = 100; + mTestSystemImpl.addUser(newUser); + mTestSystemImpl.setPackageInfoForUser(newUser, + createPackageInfo(testPackage, true /* enabled */, true /* valid */, + false /* installed */)); + + mWebViewUpdateServiceImpl.handleNewUser(newUser); + + // Check that we try to re-install the default package for all users. + Mockito.verify(mTestSystemImpl) + .installExistingPackageForAllUsers( + Matchers.anyObject(), Mockito.eq(testPackage)); + } + private void testDefaultPackageChosen(PackageInfo packageInfo) { WebViewProviderInfo[] packages = new WebViewProviderInfo[] { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java index 33ca5c2bbe16..a45b102278ef 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java @@ -20,6 +20,7 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.fail; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; @@ -32,6 +33,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.ActivityManager; import android.app.INotificationManager; import android.content.ComponentName; import android.content.Context; @@ -47,9 +49,12 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.IntArray; import android.util.Xml; +import android.Manifest; +import com.android.internal.util.CollectionUtils; import com.android.internal.util.function.TriPredicate; import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; import com.android.server.UiServiceTestCase; import com.android.server.notification.NotificationManagerService.NotificationAssistants; @@ -59,7 +64,9 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -89,11 +96,15 @@ public class NotificationAssistantsTest extends UiServiceTestCase { UserInfo mZero = new UserInfo(0, "zero", 0); UserInfo mTen = new UserInfo(10, "ten", 0); + ComponentName mCn = new ComponentName("a", "b"); + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mContext.setMockPackageManager(mPm); mContext.addMockSystemService(Context.USER_SERVICE, mUm); + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.string.config_defaultAssistantAccessComponent, "a/a"); mAssistants = spy(mNm.new NotificationAssistants(mContext, mLock, mUserProfiles, miPm)); when(mNm.getBinderService()).thenReturn(mINm); mContext.ensureTestableResources(); @@ -102,8 +113,9 @@ public class NotificationAssistantsTest extends UiServiceTestCase { ResolveInfo resolve = new ResolveInfo(); approved.add(resolve); ServiceInfo info = new ServiceInfo(); - info.packageName = "a"; - info.name="a"; + info.packageName = mCn.getPackageName(); + info.name = mCn.getClassName(); + info.permission = Manifest.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE; resolve.serviceInfo = info; when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())) .thenReturn(approved); @@ -137,6 +149,51 @@ public class NotificationAssistantsTest extends UiServiceTestCase { } @Test + public void testWriteXml_userTurnedOffNAS() throws Exception { + int userId = ActivityManager.getCurrentUser(); + + mAssistants.loadDefaultsFromConfig(true); + + mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true, + true, true); + + ComponentName current = CollectionUtils.firstOrNull( + mAssistants.getAllowedComponents(userId)); + assertNotNull(current); + mAssistants.setUserSet(userId, true); + mAssistants.setPackageOrComponentEnabled(current.flattenToString(), userId, true, false, + true); + + TypedXmlSerializer serializer = Xml.newFastSerializer(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + serializer.setOutput(new BufferedOutputStream(baos), "utf-8"); + serializer.startDocument(null, true); + mAssistants.writeXml(serializer, true, userId); + serializer.endDocument(); + serializer.flush(); + + //fail(baos.toString("UTF-8")); + + final TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(baos.toByteArray())), null); + TriPredicate<String, Integer, String> allowedManagedServicePackages = + mNm::canUseManagedServices; + + parser.nextTag(); + mAssistants = spy(mNm.new NotificationAssistants(mContext, mLock, mUserProfiles, miPm)); + mAssistants.readXml(parser, allowedManagedServicePackages, false, UserHandle.USER_ALL); + + ArrayMap<Boolean, ArraySet<String>> approved = mAssistants.mApproved.get(0); + // approved should not be null + assertNotNull(approved); + assertEquals(new ArraySet<>(), approved.get(true)); + + // user set is maintained + assertTrue(mAssistants.mIsUserChanged.get(ActivityManager.getCurrentUser())); + } + + @Test public void testReadXml_userDisabled() throws Exception { String xml = "<enabled_assistants version=\"4\" defaults=\"b/b\">" + "<service_listing approved=\"\" user=\"0\" primary=\"true\"" @@ -160,6 +217,33 @@ public class NotificationAssistantsTest extends UiServiceTestCase { } @Test + public void testReadXml_userDisabled_restore() throws Exception { + String xml = "<enabled_assistants version=\"4\" defaults=\"b/b\">" + + "<service_listing approved=\"\" user=\"0\" primary=\"true\"" + + "user_changed=\"true\"/>" + + "</enabled_assistants>"; + + final TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(xml.toString().getBytes())), null); + TriPredicate<String, Integer, String> allowedManagedServicePackages = + mNm::canUseManagedServices; + + parser.nextTag(); + mAssistants.readXml(parser, allowedManagedServicePackages, true, + ActivityManager.getCurrentUser()); + + ArrayMap<Boolean, ArraySet<String>> approved = mAssistants.mApproved.get(0); + + // approved should not be null + assertNotNull(approved); + assertEquals(new ArraySet<>(), approved.get(true)); + + // user set is maintained + assertTrue(mAssistants.mIsUserChanged.get(ActivityManager.getCurrentUser())); + } + + @Test public void testReadXml_upgradeUserSet() throws Exception { String xml = "<enabled_assistants version=\"3\" defaults=\"b/b\">" + "<service_listing approved=\"\" user=\"0\" primary=\"true\"" diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java index 3499a12f5954..bf8cfa5c0561 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java @@ -20,8 +20,12 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; import static junit.framework.TestCase.assertFalse; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -70,10 +74,11 @@ public class NotificationHistoryJobServiceTest extends UiServiceTestCase { @Before public void setUp() throws Exception { - mJobService = new NotificationHistoryJobService(); + mJobService = spy(new NotificationHistoryJobService()); mJobService.attachBaseContext(mContext); mJobService.onCreate(); mJobService.onBind(/* intent= */ null); // Create JobServiceEngine within JobService. + doNothing().when(mJobService).jobFinished(any(), eq(false)); mContext.addMockSystemService(JobScheduler.class, mMockJobScheduler); 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 99d5a6d9118a..75552bc433c5 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java @@ -16,7 +16,7 @@ package com.android.server.notification; -import static com.android.server.notification.ZenAdapters.notificationPolicyToZenPolicy; +import static android.service.notification.ZenAdapters.notificationPolicyToZenPolicy; import static com.google.common.truth.Truth.assertThat; 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 495e01a640de..7c1adbc39033 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -136,6 +136,7 @@ import android.provider.Settings; import android.provider.Settings.Global; import android.service.notification.Condition; import android.service.notification.DeviceEffectsApplier; +import android.service.notification.ZenAdapters; import android.service.notification.ZenDeviceEffects; import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeConfig.ConfigChangeOrigin; diff --git a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java index 20bb549301aa..faa6d97ce0e3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java @@ -27,6 +27,7 @@ import static org.junit.Assert.assertTrue; import android.graphics.PixelFormat; import android.platform.test.annotations.Presubmit; import android.view.InsetsSource; +import android.view.inputmethod.ImeTracker; import androidx.test.filters.SmallTest; @@ -34,6 +35,12 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +/** + * Tests for the {@link ImeInsetsSourceProvider} class. + * + * <p> Build/Install/Run: + * atest WmTests:ImeInsetsSourceProviderTest + */ @SmallTest @Presubmit @RunWith(WindowTestRunner.class) @@ -56,7 +63,7 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase { mDisplayContent.setImeControlTarget(popup); mDisplayContent.setImeLayeringTarget(appWin); popup.mAttrs.format = PixelFormat.TRANSPARENT; - mImeProvider.scheduleShowImePostLayout(appWin, null /* statsToken */); + mImeProvider.scheduleShowImePostLayout(appWin, ImeTracker.Token.empty()); assertTrue(mImeProvider.isReadyToShowIme()); } @@ -65,7 +72,7 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase { WindowState target = createWindow(null, TYPE_APPLICATION, "app"); mDisplayContent.setImeLayeringTarget(target); mDisplayContent.updateImeInputAndControlTarget(target); - mImeProvider.scheduleShowImePostLayout(target, null /* statsToken */); + mImeProvider.scheduleShowImePostLayout(target, ImeTracker.Token.empty()); assertTrue(mImeProvider.isReadyToShowIme()); } @@ -79,7 +86,7 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase { mDisplayContent.setImeLayeringTarget(target); mDisplayContent.setImeControlTarget(target); - mImeProvider.scheduleShowImePostLayout(target, null /* statsToken */); + mImeProvider.scheduleShowImePostLayout(target, ImeTracker.Token.empty()); assertFalse(mImeProvider.isImeShowing()); mImeProvider.checkShowImePostLayout(); assertTrue(mImeProvider.isImeShowing()); 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 60130635108c..da11e6ad613a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java @@ -554,7 +554,7 @@ public class RootWindowContainerTests extends WindowTestsBase { mRootWindowContainer.applySleepTokens(true); // The display orientation should be changed by the activity so there is no relaunch. - verify(activity, never()).relaunchActivityLocked(anyBoolean()); + verify(activity, never()).relaunchActivityLocked(anyBoolean(), anyInt()); assertEquals(rotatedConfig.orientation, display.getConfiguration().orientation); } 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 cd3ce9192509..c8ad4bd47880 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -104,6 +104,7 @@ import android.view.SurfaceControl; import android.view.View; import android.view.WindowInsets; import android.view.WindowManager; +import android.view.inputmethod.ImeTracker; import android.window.ClientWindowFrames; import android.window.ITaskFragmentOrganizer; import android.window.TaskFragmentOrganizer; @@ -126,7 +127,7 @@ import java.util.List; /** * Tests for the {@link WindowState} class. * - * Build/Install/Run: + * <p> Build/Install/Run: * atest WmTests:WindowStateTests */ @SmallTest @@ -1099,7 +1100,7 @@ public class WindowStateTests extends WindowTestsBase { mDisplayContent.setImeInputTarget(app); app.setRequestedVisibleTypes(ime(), ime()); assertTrue(mDisplayContent.shouldImeAttachedToApp()); - controller.getImeSourceProvider().scheduleShowImePostLayout(app, null /* statsToken */); + controller.getImeSourceProvider().scheduleShowImePostLayout(app, ImeTracker.Token.empty()); controller.getImeSourceProvider().getSource().setVisible(true); controller.updateAboveInsetsState(false); @@ -1137,7 +1138,7 @@ public class WindowStateTests extends WindowTestsBase { mDisplayContent.setImeInputTarget(app); app.setRequestedVisibleTypes(ime(), ime()); assertTrue(mDisplayContent.shouldImeAttachedToApp()); - controller.getImeSourceProvider().scheduleShowImePostLayout(app, null /* statsToken */); + controller.getImeSourceProvider().scheduleShowImePostLayout(app, ImeTracker.Token.empty()); controller.getImeSourceProvider().getSource().setVisible(true); controller.updateAboveInsetsState(false); diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java index 44f4068fb424..883c702ddb79 100644 --- a/services/usage/java/com/android/server/usage/StorageStatsService.java +++ b/services/usage/java/com/android/server/usage/StorageStatsService.java @@ -383,7 +383,7 @@ public class StorageStatsService extends IStorageStatsManager.Stub { return queryStatsForUid(volumeUuid, appInfo.uid, callingPackage); } else { // Multiple packages means we need to go manual - final int appId = UserHandle.getUserId(appInfo.uid); + final int appId = UserHandle.getAppId(appInfo.uid); final String[] packageNames = new String[] { packageName }; final long[] ceDataInodes = new long[1]; String[] codePaths = new String[0]; diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 1e1dd00b8df5..5a5296836089 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -66,7 +66,6 @@ import android.os.Handler; import android.os.IBinder; import android.os.Parcel; import android.os.ParcelFileDescriptor; -import android.os.PermissionEnforcer; import android.os.PersistableBundle; import android.os.RemoteCallback; import android.os.RemoteCallbackList; @@ -76,7 +75,6 @@ import android.os.SharedMemory; import android.os.ShellCallback; import android.os.Trace; import android.os.UserHandle; -import android.permission.flags.Flags; import android.provider.Settings; import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback; import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback; @@ -1407,17 +1405,6 @@ public class VoiceInteractionManagerService extends SystemService { } } - // Enforce permissions that are flag controlled. The flag value decides if the permission - // should be enforced. - private void initAndVerifyDetector_enforcePermissionWithFlags() { - PermissionEnforcer enforcer = mContext.getSystemService(PermissionEnforcer.class); - if (Flags.voiceActivationPermissionApis()) { - enforcer.enforcePermission( - android.Manifest.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO, - getCallingPid(), getCallingUid()); - } - } - @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) @Override public void initAndVerifyDetector( @@ -1427,13 +1414,7 @@ public class VoiceInteractionManagerService extends SystemService { @NonNull IBinder token, IHotwordRecognitionStatusCallback callback, int detectorType) { - // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the - // {@link #initAndVerifyDetector(Identity, PersistableBundle, ShareMemory, IBinder, - // IHotwordRecognitionStatusCallback, int)} - // and replace with the permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully - // launched. super.initAndVerifyDetector_enforcePermission(); - initAndVerifyDetector_enforcePermissionWithFlags(); synchronized (this) { enforceIsCurrentVoiceInteractionService(); diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java index 2fc6a22261b6..8b503f2b97e4 100644 --- a/telecomm/java/android/telecom/RemoteConnectionService.java +++ b/telecomm/java/android/telecom/RemoteConnectionService.java @@ -28,6 +28,8 @@ import com.android.internal.telecom.IConnectionService; import com.android.internal.telecom.IConnectionServiceAdapter; import com.android.internal.telecom.IVideoProvider; import com.android.internal.telecom.RemoteServiceCallback; +import com.android.server.telecom.flags.FeatureFlags; +import com.android.server.telecom.flags.FeatureFlagsImpl; import java.util.ArrayList; import java.util.HashMap; @@ -550,6 +552,9 @@ final class RemoteConnectionService { private final Map<String, RemoteConference> mConferenceById = new HashMap<>(); private final Set<RemoteConnection> mPendingConnections = new HashSet<>(); + /** Telecom feature flags **/ + private final FeatureFlags mTelecomFeatureFlags = new FeatureFlagsImpl(); + RemoteConnectionService( IConnectionService outgoingConnectionServiceRpc, ConnectionService ourConnectionServiceImpl) throws RemoteException { @@ -578,6 +583,14 @@ final class RemoteConnectionService { extras.putString(Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME, mOurConnectionServiceImpl.getApplicationContext().getOpPackageName()); + // Defaulted ConnectionRequest params + String telecomCallId = ""; + boolean shouldShowIncomingUI = false; + if (mTelecomFeatureFlags.setRemoteConnectionCallId()) { + telecomCallId = id; + shouldShowIncomingUI = request.shouldShowIncomingCallUi(); + } + final ConnectionRequest newRequest = new ConnectionRequest.Builder() .setAccountHandle(request.getAccountHandle()) .setAddress(request.getAddress()) @@ -585,6 +598,9 @@ final class RemoteConnectionService { .setVideoState(request.getVideoState()) .setRttPipeFromInCall(request.getRttPipeFromInCall()) .setRttPipeToInCall(request.getRttPipeToInCall()) + // Flagged changes + .setTelecomCallId(telecomCallId) + .setShouldShowIncomingCallUi(shouldShowIncomingUI) .build(); try { if (mConnectionById.isEmpty()) { @@ -626,10 +642,28 @@ final class RemoteConnectionService { mOutgoingConnectionServiceRpc.addConnectionServiceAdapter(mServant.getStub(), null /*Session.Info*/); } + + // Set telecom call id to what's being tracked by base ConnectionService. + String telecomCallId = mTelecomFeatureFlags.setRemoteConnectionCallId() + ? id : request.getTelecomCallId(); + + final ConnectionRequest newRequest = new ConnectionRequest.Builder() + .setAccountHandle(request.getAccountHandle()) + .setAddress(request.getAddress()) + .setExtras(request.getExtras()) + .setVideoState(request.getVideoState()) + .setShouldShowIncomingCallUi(request.shouldShowIncomingCallUi()) + .setRttPipeFromInCall(request.getRttPipeFromInCall()) + .setRttPipeToInCall(request.getRttPipeToInCall()) + .setParticipants(request.getParticipants()) + .setIsAdhocConferenceCall(request.isAdhocConferenceCall()) + .setTelecomCallId(telecomCallId) + .build(); + RemoteConference conference = new RemoteConference(id, mOutgoingConnectionServiceRpc); mOutgoingConnectionServiceRpc.createConference(connectionManagerPhoneAccount, id, - request, + newRequest, isIncoming, false /* isUnknownCall */, null /*Session.info*/); @@ -640,7 +674,7 @@ final class RemoteConnectionService { maybeDisconnectAdapter(); } }); - conference.putExtras(request.getExtras()); + conference.putExtras(newRequest.getExtras()); return conference; } catch (RemoteException e) { return RemoteConference.failure( diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index 9792cdd80a00..048b1b290dde 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -1434,11 +1434,14 @@ public class TelecomManager { } /** - * This API will return all {@link PhoneAccount}s registered via - * {@link TelecomManager#registerPhoneAccount(PhoneAccount)}. If a {@link PhoneAccount} appears - * to be missing from the list, Telecom has either unregistered the {@link PhoneAccount} - * or the caller registered the {@link PhoneAccount} under a different user and does not - * have the {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission. + * This API will return all {@link PhoneAccount}s the caller registered via + * {@link TelecomManager#registerPhoneAccount(PhoneAccount)}. If a {@link PhoneAccount} appears + * to be missing from the list, Telecom has either unregistered the {@link PhoneAccount} (for + * cleanup purposes) or the caller registered the {@link PhoneAccount} under a different user + * and does not have the {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission. + * <b>Note:</b> This API will only return {@link PhoneAccount}s registered by the same app. For + * system Dialers that need all the {@link PhoneAccount}s registered by every application, see + * {@link TelecomManager#getAllPhoneAccounts()}. * * @return all the {@link PhoneAccount}s registered by the caller. */ diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 5d99acd87dd3..2150b5deff52 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -5307,6 +5307,19 @@ public class CarrierConfigManager { KEY_PREFIX + "enable_presence_group_subscribe_bool"; /** + * SIP SUBSCRIBE retry duration used when device doesn't receive a response to SIP + * SUBSCRIBE request. + * If this value is not defined or defined as negative value, the device does not retry + * the SIP SUBSCRIBE. + * If the value is 0 then device retries immediately upon timeout. + * If the value is > 0 then device waits for configured duration and retries after timeout + * is detected + * @hide + */ + public static final String KEY_SUBSCRIBE_RETRY_DURATION_MILLIS_LONG = + KEY_PREFIX + "subscribe_retry_duration_millis_long"; + + /** * Flag indicating whether or not to use SIP URI when send a presence subscribe. * When {@code true}, the device sets the To and Contact header to be SIP URI using * the TelephonyManager#getIsimDomain" API. @@ -5982,6 +5995,7 @@ public class CarrierConfigManager { defaults.putBoolean(KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL, false); defaults.putBoolean(KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL, false); defaults.putBoolean(KEY_ENABLE_PRESENCE_GROUP_SUBSCRIBE_BOOL, false); + defaults.putInt(KEY_SUBSCRIBE_RETRY_DURATION_MILLIS_LONG, -1); defaults.putBoolean(KEY_USE_SIP_URI_FOR_PRESENCE_SUBSCRIBE_BOOL, false); defaults.putInt(KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT, 30 * 24 * 60 * 60); defaults.putBoolean(KEY_RCS_REQUEST_FORBIDDEN_BY_SIP_489_BOOL, false); diff --git a/telephony/java/android/telephony/TelephonyFrameworkInitializer.java b/telephony/java/android/telephony/TelephonyFrameworkInitializer.java index c2f5b8f3e7da..901daf813e86 100644 --- a/telephony/java/android/telephony/TelephonyFrameworkInitializer.java +++ b/telephony/java/android/telephony/TelephonyFrameworkInitializer.java @@ -19,12 +19,14 @@ package android.telephony; import android.annotation.NonNull; import android.app.SystemServiceRegistry; import android.content.Context; +import android.content.pm.PackageManager; import android.os.TelephonyServiceManager; import android.telephony.euicc.EuiccCardManager; import android.telephony.euicc.EuiccManager; import android.telephony.ims.ImsManager; import android.telephony.satellite.SatelliteManager; +import com.android.internal.telephony.flags.Flags; import com.android.internal.util.Preconditions; @@ -55,6 +57,11 @@ public class TelephonyFrameworkInitializer { sTelephonyServiceManager = Preconditions.checkNotNull(telephonyServiceManager); } + private static boolean hasSystemFeature(Context context, String feature) { + if (!Flags.minimalTelephonyManagersConditionalOnFeatures()) return true; + return context.getPackageManager().hasSystemFeature(feature); + } + /** * Called by {@link SystemServiceRegistry}'s static initializer and registers all telephony * services to {@link Context}, so that {@link Context#getSystemService} can return them. @@ -76,33 +83,39 @@ public class TelephonyFrameworkInitializer { SystemServiceRegistry.registerContextAwareService( Context.CARRIER_CONFIG_SERVICE, CarrierConfigManager.class, - context -> new CarrierConfigManager(context) + context -> hasSystemFeature(context, PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION) + ? new CarrierConfigManager(context) : null ); SystemServiceRegistry.registerContextAwareService( Context.EUICC_SERVICE, EuiccManager.class, - context -> new EuiccManager(context) + context -> hasSystemFeature(context, PackageManager.FEATURE_TELEPHONY_EUICC) + ? new EuiccManager(context) : null ); SystemServiceRegistry.registerContextAwareService( Context.EUICC_CARD_SERVICE, EuiccCardManager.class, - context -> new EuiccCardManager(context) + context -> hasSystemFeature(context, PackageManager.FEATURE_TELEPHONY_EUICC) + ? new EuiccCardManager(context) : null ); SystemServiceRegistry.registerContextAwareService( Context.TELEPHONY_IMS_SERVICE, ImsManager.class, - context -> new ImsManager(context) + context -> hasSystemFeature(context, PackageManager.FEATURE_TELEPHONY_IMS) + ? new ImsManager(context) : null ); SystemServiceRegistry.registerContextAwareService( Context.SMS_SERVICE, SmsManager.class, - context -> SmsManager.getSmsManagerForContextAndSubscriptionId(context, - SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) + context -> hasSystemFeature(context, PackageManager.FEATURE_TELEPHONY_MESSAGING) + ? SmsManager.getSmsManagerForContextAndSubscriptionId(context, + SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) : null ); SystemServiceRegistry.registerContextAwareService( Context.SATELLITE_SERVICE, SatelliteManager.class, - context -> new SatelliteManager(context) + context -> hasSystemFeature(context, PackageManager.FEATURE_TELEPHONY_SATELLITE) + ? new SatelliteManager(context) : null ); } diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt index d47329ea97b4..57da05f13bbb 100644 --- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt +++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt @@ -52,6 +52,7 @@ class OpenAppFromIntentColdAfterCameraTest(flicker: LegacyFlickerTest) : // Can't use TAPL due to Recents not showing in 3 Button Nav in full screen mode device.pressHome() tapl.getWorkspace() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() } teardown { testApp.exit(wmHelper) } transitions { testApp.launchViaIntent(wmHelper) } diff --git a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt index 7343ba1c1ce7..e60764f137af 100644 --- a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt @@ -24,6 +24,7 @@ import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.content.pm.ResolveInfo import android.content.pm.ServiceInfo +import android.hardware.input.KeyboardLayoutSelectionResult import android.hardware.input.IInputManager import android.hardware.input.InputManager import android.hardware.input.InputManagerGlobal @@ -525,13 +526,13 @@ class KeyboardLayoutManagerTests { keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype, ENGLISH_UK_LAYOUT_DESCRIPTOR ) - val keyboardLayout = + assertEquals( + "Default UI: getKeyboardLayoutForInputDevice API should always return " + + "KeyboardLayoutSelectionResult.FAILED", + KeyboardLayoutSelectionResult.FAILED, keyboardLayoutManager.getKeyboardLayoutForInputDevice( keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype ) - assertNull( - "Default UI: getKeyboardLayoutForInputDevice API should always return null", - keyboardLayout ) } } @@ -545,12 +546,14 @@ class KeyboardLayoutManagerTests { keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype, ENGLISH_UK_LAYOUT_DESCRIPTOR ) - assertEquals( - "New UI: getKeyboardLayoutForInputDevice API should return the set layout", - ENGLISH_UK_LAYOUT_DESCRIPTOR, + var result = keyboardLayoutManager.getKeyboardLayoutForInputDevice( keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype ) + assertEquals( + "New UI: getKeyboardLayoutForInputDevice API should return the set layout", + ENGLISH_UK_LAYOUT_DESCRIPTOR, + result.layoutDescriptor ) // This should replace previously set layout @@ -558,12 +561,14 @@ class KeyboardLayoutManagerTests { keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype, ENGLISH_US_LAYOUT_DESCRIPTOR ) - assertEquals( - "New UI: getKeyboardLayoutForInputDevice API should return the last set layout", - ENGLISH_US_LAYOUT_DESCRIPTOR, + result = keyboardLayoutManager.getKeyboardLayoutForInputDevice( keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype ) + assertEquals( + "New UI: getKeyboardLayoutForInputDevice API should return the last set layout", + ENGLISH_US_LAYOUT_DESCRIPTOR, + result.layoutDescriptor ) } } @@ -734,17 +739,20 @@ class KeyboardLayoutManagerTests { createImeSubtypeForLanguageTag("ru"), createLayoutDescriptor("keyboard_layout_russian") ) - assertNull( - "New UI: getDefaultKeyboardLayoutForInputDevice should return null when no " + - "layout available", + assertEquals( + "New UI: getDefaultKeyboardLayoutForInputDevice should return " + + "KeyboardLayoutSelectionResult.FAILED when no layout available", + KeyboardLayoutSelectionResult.FAILED, keyboardLayoutManager.getKeyboardLayoutForInputDevice( keyboardDevice.identifier, USER_ID, imeInfo, createImeSubtypeForLanguageTag("it") ) ) - assertNull( - "New UI: getDefaultKeyboardLayoutForInputDevice should return null when no " + - "layout for script code is available", + assertEquals( + "New UI: getDefaultKeyboardLayoutForInputDevice should return " + + "KeyboardLayoutSelectionResult.FAILED when no layout for script code is" + + "available", + KeyboardLayoutSelectionResult.FAILED, keyboardLayoutManager.getKeyboardLayoutForInputDevice( keyboardDevice.identifier, USER_ID, imeInfo, createImeSubtypeForLanguageTag("en-Deva") @@ -811,8 +819,10 @@ class KeyboardLayoutManagerTests { createImeSubtypeForLanguageTagAndLayoutType("ru", ""), createLayoutDescriptor("keyboard_layout_russian") ) - assertNull("New UI: getDefaultKeyboardLayoutForInputDevice should return null when " + - "no layout for script code is available", + assertEquals("New UI: getDefaultKeyboardLayoutForInputDevice should return " + + "KeyboardLayoutSelectionResult.FAILED when no layout for script code is" + + "available", + KeyboardLayoutSelectionResult.FAILED, keyboardLayoutManager.getKeyboardLayoutForInputDevice( keyboardDevice.identifier, USER_ID, imeInfo, createImeSubtypeForLanguageTagAndLayoutType("en-Deva-US", "") @@ -865,14 +875,16 @@ class KeyboardLayoutManagerTests { ArgumentMatchers.anyBoolean(), ArgumentMatchers.eq(keyboardDevice.vendorId), ArgumentMatchers.eq(keyboardDevice.productId), - ArgumentMatchers.eq(createByteArray( + ArgumentMatchers.eq( + createByteArray( KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG, LAYOUT_TYPE_DEFAULT, GERMAN_LAYOUT_NAME, - KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD, + KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD, "de-Latn", - LAYOUT_TYPE_QWERTZ), + LAYOUT_TYPE_QWERTZ ), + ), ArgumentMatchers.eq(keyboardDevice.deviceBus), ) } @@ -893,13 +905,16 @@ class KeyboardLayoutManagerTests { ArgumentMatchers.anyBoolean(), ArgumentMatchers.eq(englishQwertyKeyboardDevice.vendorId), ArgumentMatchers.eq(englishQwertyKeyboardDevice.productId), - ArgumentMatchers.eq(createByteArray( + ArgumentMatchers.eq( + createByteArray( "en", LAYOUT_TYPE_QWERTY, ENGLISH_US_LAYOUT_NAME, - KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE, + KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE, "de-Latn", - LAYOUT_TYPE_QWERTZ)), + LAYOUT_TYPE_QWERTZ + ) + ), ArgumentMatchers.eq(keyboardDevice.deviceBus), ) } @@ -918,14 +933,16 @@ class KeyboardLayoutManagerTests { ArgumentMatchers.anyBoolean(), ArgumentMatchers.eq(keyboardDevice.vendorId), ArgumentMatchers.eq(keyboardDevice.productId), - ArgumentMatchers.eq(createByteArray( + ArgumentMatchers.eq( + createByteArray( KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG, LAYOUT_TYPE_DEFAULT, "Default", - KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEFAULT, + KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT, KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG, - LAYOUT_TYPE_DEFAULT), + LAYOUT_TYPE_DEFAULT ), + ), ArgumentMatchers.eq(keyboardDevice.deviceBus), ) } @@ -998,12 +1015,13 @@ class KeyboardLayoutManagerTests { imeSubtype: InputMethodSubtype, expectedLayout: String ) { + val result = keyboardLayoutManager.getKeyboardLayoutForInputDevice( + device.identifier, USER_ID, imeInfo, imeSubtype + ) assertEquals( "New UI: getDefaultKeyboardLayoutForInputDevice should return $expectedLayout", expectedLayout, - keyboardLayoutManager.getKeyboardLayoutForInputDevice( - device.identifier, USER_ID, imeInfo, imeSubtype - ) + result.layoutDescriptor ) } diff --git a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt index 89a47b9b736a..0615941eda09 100644 --- a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt +++ b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt @@ -17,6 +17,7 @@ package com.android.server.input import android.hardware.input.KeyboardLayout +import android.hardware.input.KeyboardLayoutSelectionResult import android.icu.util.ULocale import android.platform.test.annotations.Presubmit import android.view.InputDevice @@ -120,15 +121,15 @@ class KeyboardMetricsCollectorTests { val event = builder.addLayoutSelection( createImeSubtype(1, ULocale.forLanguageTag("en-US"), "qwerty"), "English(US)(Qwerty)", - KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD + KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD ).addLayoutSelection( createImeSubtype(2, ULocale.forLanguageTag("en-US"), "azerty"), null, // Default layout type - KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER + KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_USER ).addLayoutSelection( createImeSubtype(3, ULocale.forLanguageTag("en-US"), "qwerty"), "German", - KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE + KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE ).setIsFirstTimeConfiguration(true).build() assertEquals( @@ -158,7 +159,7 @@ class KeyboardMetricsCollectorTests { "de-CH", KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"), "English(US)(Qwerty)", - KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD, + KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD, "en-US", KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"), ) @@ -167,7 +168,7 @@ class KeyboardMetricsCollectorTests { "de-CH", KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"), KeyboardMetricsCollector.DEFAULT_LAYOUT_NAME, - KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER, + KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_USER, "en-US", KeyboardLayout.LayoutType.getLayoutTypeEnumValue("azerty"), ) @@ -176,7 +177,7 @@ class KeyboardMetricsCollectorTests { "de-CH", KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"), "German", - KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE, + KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE, "en-US", KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"), ) @@ -197,7 +198,7 @@ class KeyboardMetricsCollectorTests { val event = builder.addLayoutSelection( createImeSubtype(4, null, "qwerty"), // Default language tag "German", - KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE + KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE ).build() assertExpectedLayoutConfiguration( @@ -205,7 +206,7 @@ class KeyboardMetricsCollectorTests { KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG, KeyboardLayout.LayoutType.getLayoutTypeEnumValue("azerty"), "German", - KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE, + KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE, KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG, KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"), ) diff --git a/tests/graphics/HwAccelerationTest/jni/native-lib.cpp b/tests/graphics/HwAccelerationTest/jni/native-lib.cpp index 407d4bf76336..2977c2157261 100644 --- a/tests/graphics/HwAccelerationTest/jni/native-lib.cpp +++ b/tests/graphics/HwAccelerationTest/jni/native-lib.cpp @@ -30,7 +30,7 @@ struct MyWrapper { void setBuffer(AHardwareBuffer* buffer) { ASurfaceTransaction* transaction = ASurfaceTransaction_create(); - ASurfaceTransaction_setBuffer(transaction, surfaceControl, buffer); + ASurfaceTransaction_setBuffer(transaction, surfaceControl, buffer, -1); ASurfaceTransaction_setVisibility(transaction, surfaceControl, ASURFACE_TRANSACTION_VISIBILITY_SHOW); ASurfaceTransaction_apply(transaction); diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp index b054a57e85af..b4e2758f4abe 100644 --- a/tools/aapt2/Android.bp +++ b/tools/aapt2/Android.bp @@ -15,12 +15,7 @@ // package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], + default_applicable_licenses: ["Android-Apache-2.0"], } toolSources = [ diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk deleted file mode 100644 index 15ae2baa73df..000000000000 --- a/tools/aapt2/Android.mk +++ /dev/null @@ -1,4 +0,0 @@ -include $(CLEAR_VARS) -aapt2_results := ./out/soong/.intermediates/frameworks/base/tools/aapt2/aapt2_results -$(call declare-1p-target,$(aapt2_results)) -aapt2_results := diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java index 58638e8e1af4..45ab9863ff73 100644 --- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java +++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java @@ -718,6 +718,9 @@ public class WifiNl80211Manager { } catch (RemoteException e1) { Log.e(TAG, "Failed to get IClientInterface due to remote exception"); return false; + } catch (NullPointerException e2) { + Log.e(TAG, "setupInterfaceForClientMode NullPointerException"); + return false; } if (clientInterface == null) { @@ -785,6 +788,9 @@ public class WifiNl80211Manager { } catch (RemoteException e1) { Log.e(TAG, "Failed to teardown client interface due to remote exception"); return false; + } catch (NullPointerException e2) { + Log.e(TAG, "tearDownClientInterface NullPointerException"); + return false; } if (!success) { Log.e(TAG, "Failed to teardown client interface"); @@ -816,6 +822,9 @@ public class WifiNl80211Manager { } catch (RemoteException e1) { Log.e(TAG, "Failed to get IApInterface due to remote exception"); return false; + } catch (NullPointerException e2) { + Log.e(TAG, "setupInterfaceForSoftApMode NullPointerException"); + return false; } if (apInterface == null) { @@ -854,6 +863,9 @@ public class WifiNl80211Manager { } catch (RemoteException e1) { Log.e(TAG, "Failed to teardown AP interface due to remote exception"); return false; + } catch (NullPointerException e2) { + Log.e(TAG, "tearDownSoftApInterface NullPointerException"); + return false; } if (!success) { Log.e(TAG, "Failed to teardown AP interface"); @@ -1328,6 +1340,8 @@ public class WifiNl80211Manager { } } catch (RemoteException e1) { Log.e(TAG, "Failed to request getChannelsForBand due to remote exception"); + } catch (NullPointerException e2) { + Log.e(TAG, "getChannelsMhzForBand NullPointerException"); } if (result == null) { result = new int[0]; @@ -1352,7 +1366,8 @@ public class WifiNl80211Manager { */ @Nullable public DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String ifaceName) { if (mWificond == null) { - Log.e(TAG, "getDeviceWiphyCapabilities: mWificond binder is null! Did wificond die?"); + Log.e(TAG, "getDeviceWiphyCapabilities: mWificond binder is null! " + + "Did wificond die?"); return null; } @@ -1360,6 +1375,9 @@ public class WifiNl80211Manager { return mWificond.getDeviceWiphyCapabilities(ifaceName); } catch (RemoteException e) { return null; + } catch (NullPointerException e2) { + Log.e(TAG, "getDeviceWiphyCapabilities NullPointerException"); + return null; } } @@ -1409,6 +1427,8 @@ public class WifiNl80211Manager { Log.i(TAG, "Receive country code change to " + newCountryCode); } catch (RemoteException re) { re.rethrowFromSystemServer(); + } catch (NullPointerException e) { + new RemoteException("Wificond service doesn't exist!").rethrowFromSystemServer(); } } |