diff options
239 files changed, 5322 insertions, 1794 deletions
diff --git a/boot/preloaded-classes b/boot/preloaded-classes index a696e03d5bdf..afd9984cb124 100644 --- a/boot/preloaded-classes +++ b/boot/preloaded-classes @@ -6469,6 +6469,7 @@ android.os.connectivity.WifiActivityEnergyInfo$1 android.os.connectivity.WifiActivityEnergyInfo android.os.connectivity.WifiBatteryStats$1 android.os.connectivity.WifiBatteryStats +android.os.flagging.AconfigPackage android.os.health.HealthKeys$Constant android.os.health.HealthKeys$Constants android.os.health.HealthKeys$SortedIntArray diff --git a/config/preloaded-classes b/config/preloaded-classes index ed402767ee64..343de0bf3b98 100644 --- a/config/preloaded-classes +++ b/config/preloaded-classes @@ -6473,6 +6473,7 @@ android.os.connectivity.WifiActivityEnergyInfo$1 android.os.connectivity.WifiActivityEnergyInfo android.os.connectivity.WifiBatteryStats$1 android.os.connectivity.WifiBatteryStats +android.os.flagging.AconfigPackage android.os.health.HealthKeys$Constant android.os.health.HealthKeys$Constants android.os.health.HealthKeys$SortedIntArray diff --git a/core/api/current.txt b/core/api/current.txt index 5efa74779c06..a9febc3db62b 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -29956,7 +29956,7 @@ package android.net.http { public class X509TrustManagerExtensions { ctor public X509TrustManagerExtensions(javax.net.ssl.X509TrustManager) throws java.lang.IllegalArgumentException; method public java.util.List<java.security.cert.X509Certificate> checkServerTrusted(java.security.cert.X509Certificate[], String, String) throws java.security.cert.CertificateException; - method @FlaggedApi("android.net.platform.flags.x509_extensions_certificate_transparency") @NonNull public java.util.List<java.security.cert.X509Certificate> checkServerTrusted(@NonNull java.security.cert.X509Certificate[], @Nullable byte[], @Nullable byte[], @NonNull String, @NonNull String) throws java.security.cert.CertificateException; + method @FlaggedApi("android.security.certificate_transparency_configuration") @NonNull public java.util.List<java.security.cert.X509Certificate> checkServerTrusted(@NonNull java.security.cert.X509Certificate[], @Nullable byte[], @Nullable byte[], @NonNull String, @NonNull String) throws java.security.cert.CertificateException; method public boolean isSameTrustConfiguration(String, String); method public boolean isUserAddedCertificate(java.security.cert.X509Certificate); } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 6230a59a62c0..8594caee32c5 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1402,9 +1402,9 @@ package android.credentials.selection { method @NonNull public android.credentials.selection.GetCredentialProviderData.Builder setRemoteEntry(@Nullable android.credentials.selection.Entry); } - @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public class IntentFactory { - 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); + @FlaggedApi("android.credentials.flags.propagate_user_context_for_intent_creation") public class IntentFactory { + method @NonNull public static android.content.Intent createCancelUiIntent(@NonNull android.content.Context, @NonNull android.os.IBinder, boolean, @NonNull String, int); + 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, int); } @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public abstract class ProviderData implements android.os.Parcelable { diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java index ed088fed41c2..a731e5085466 100644 --- a/core/java/android/app/appfunctions/AppFunctionManager.java +++ b/core/java/android/app/appfunctions/AppFunctionManager.java @@ -34,6 +34,7 @@ import android.os.ICancellationSignal; import android.os.OutcomeReceiver; import android.os.ParcelableException; import android.os.RemoteException; +import android.os.SystemClock; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -179,7 +180,8 @@ public final class AppFunctionManager { ExecuteAppFunctionAidlRequest aidlRequest = new ExecuteAppFunctionAidlRequest( - request, mContext.getUser(), mContext.getPackageName()); + request, mContext.getUser(), mContext.getPackageName(), + /* requestTime= */ SystemClock.elapsedRealtime()); try { ICancellationSignal cancellationTransport = diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionAidlRequest.java b/core/java/android/app/appfunctions/ExecuteAppFunctionAidlRequest.java index e623fa10f474..707d1fc0473e 100644 --- a/core/java/android/app/appfunctions/ExecuteAppFunctionAidlRequest.java +++ b/core/java/android/app/appfunctions/ExecuteAppFunctionAidlRequest.java @@ -41,8 +41,9 @@ public final class ExecuteAppFunctionAidlRequest implements Parcelable { ExecuteAppFunctionRequest.CREATOR.createFromParcel(in); UserHandle userHandle = UserHandle.CREATOR.createFromParcel(in); String callingPackage = in.readString8(); + long requestTime = in.readLong(); return new ExecuteAppFunctionAidlRequest( - clientRequest, userHandle, callingPackage); + clientRequest, userHandle, callingPackage, requestTime); } @Override @@ -60,11 +61,15 @@ public final class ExecuteAppFunctionAidlRequest implements Parcelable { /** The package name of the app that is requesting to execute the app function. */ private final String mCallingPackage; - public ExecuteAppFunctionAidlRequest( - ExecuteAppFunctionRequest clientRequest, UserHandle userHandle, String callingPackage) { + /** The time of calling executeAppFunction(). */ + private final long mRequestTime; + + public ExecuteAppFunctionAidlRequest(ExecuteAppFunctionRequest clientRequest, + UserHandle userHandle, String callingPackage, long requestTime) { this.mClientRequest = Objects.requireNonNull(clientRequest); this.mUserHandle = Objects.requireNonNull(userHandle); this.mCallingPackage = Objects.requireNonNull(callingPackage); + this.mRequestTime = requestTime; } @Override @@ -77,6 +82,7 @@ public final class ExecuteAppFunctionAidlRequest implements Parcelable { mClientRequest.writeToParcel(dest, flags); mUserHandle.writeToParcel(dest, flags); dest.writeString8(mCallingPackage); + dest.writeLong(mRequestTime); } /** Returns the client request to execute an app function. */ @@ -96,4 +102,9 @@ public final class ExecuteAppFunctionAidlRequest implements Parcelable { public String getCallingPackage() { return mCallingPackage; } + + /** Returns the time of calling executeAppFunction(). */ + public long getRequestTime() { + return mRequestTime; + } } diff --git a/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java b/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java index 2426daf5c9f2..e290169bdea8 100644 --- a/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java +++ b/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java @@ -17,6 +17,7 @@ package android.app.appfunctions; import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.RemoteException; import android.util.Log; @@ -37,8 +38,16 @@ public class SafeOneTimeExecuteAppFunctionCallback { @NonNull private final IExecuteAppFunctionCallback mCallback; + @Nullable CompletionCallback mCompletionCallback; + public SafeOneTimeExecuteAppFunctionCallback(@NonNull IExecuteAppFunctionCallback callback) { + this(callback, /* completionCallback= */ null); + } + + public SafeOneTimeExecuteAppFunctionCallback(@NonNull IExecuteAppFunctionCallback callback, + @Nullable CompletionCallback completionCallback) { mCallback = Objects.requireNonNull(callback); + mCompletionCallback = completionCallback; } /** Invoke wrapped callback with the result. */ @@ -49,6 +58,9 @@ public class SafeOneTimeExecuteAppFunctionCallback { } try { mCallback.onSuccess(result); + if (mCompletionCallback != null) { + mCompletionCallback.finalizeOnSuccess(result); + } } catch (RemoteException ex) { // Failed to notify the other end. Ignore. Log.w(TAG, "Failed to invoke the callback", ex); @@ -63,6 +75,9 @@ public class SafeOneTimeExecuteAppFunctionCallback { } try { mCallback.onError(error); + if (mCompletionCallback != null) { + mCompletionCallback.finalizeOnError(error); + } } catch (RemoteException ex) { // Failed to notify the other end. Ignore. Log.w(TAG, "Failed to invoke the callback", ex); @@ -76,4 +91,16 @@ public class SafeOneTimeExecuteAppFunctionCallback { public void disable() { mOnResultCalled.set(true); } + + /** + * Provides a hook to execute additional actions after the {@link IExecuteAppFunctionCallback} + * has been invoked. + */ + public interface CompletionCallback { + /** Called after {@link IExecuteAppFunctionCallback#onSuccess}. */ + void finalizeOnSuccess(@NonNull ExecuteAppFunctionResponse result); + + /** Called after {@link IExecuteAppFunctionCallback#onError}. */ + void finalizeOnError(@NonNull AppFunctionException error); + } } diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index b3f09a98d623..ed2fd99c55c5 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -1290,7 +1290,11 @@ public final class VirtualDeviceManager { @NonNull UserHandle user) {} /** - * Called when a window with a secure surface is no longer shown on the device. + * Called when there is no longer any window with a secure surface shown on the device. + * + * <p>This is only called once there are no more secure windows shown on the device. If + * there are multiple secure windows shown on the device, this callback will be called only + * once all of them are hidden.</p> * * @param displayId The display ID on which the window was shown before. * diff --git a/core/java/android/content/EventLogTags.logtags b/core/java/android/content/EventLogTags.logtags index 21ea90ad2e1e..861a5b72c86c 100644 --- a/core/java/android/content/EventLogTags.logtags +++ b/core/java/android/content/EventLogTags.logtags @@ -1,4 +1,4 @@ -# See system/core/logcat/event.logtags for a description of the format of this file. +# See system/logging/logcat/event.logtags for a description of the format of this file. option java_package android.content; diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index a6492d36cf8f..3d75423edfa9 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -12291,7 +12291,6 @@ public class Intent implements Parcelable, Cloneable { private IBinder mCreatorToken; // Stores all extra keys whose values are intents for a top level intent. private ArraySet<NestedIntentKey> mNestedIntentKeys; - } /** @@ -12353,6 +12352,7 @@ public class Intent implements Parcelable, Cloneable { public int hashCode() { return Objects.hash(mType, mKey, mIndex); } + } private @Nullable CreatorTokenInfo mCreatorTokenInfo; @@ -12416,7 +12416,7 @@ public class Intent implements Parcelable, Cloneable { // removeLaunchSecurityProtection() is called before it is launched. value = null; } - if (value instanceof Intent intent && !visited.contains(intent)) { + if (value instanceof Intent intent) { handleNestedIntent(intent, visited, new NestedIntentKey( NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL, key, 0)); } else if (value instanceof Parcelable[] parcelables) { @@ -12439,7 +12439,6 @@ public class Intent implements Parcelable, Cloneable { } private void handleNestedIntent(Intent intent, Set<Intent> visited, NestedIntentKey key) { - visited.add(intent); if (mCreatorTokenInfo == null) { mCreatorTokenInfo = new CreatorTokenInfo(); } @@ -12447,7 +12446,10 @@ public class Intent implements Parcelable, Cloneable { mCreatorTokenInfo.mNestedIntentKeys = new ArraySet<>(); } mCreatorTokenInfo.mNestedIntentKeys.add(key); - intent.collectNestedIntentKeysRecur(visited); + if (!visited.contains(intent)) { + visited.add(intent); + intent.collectNestedIntentKeysRecur(visited); + } } private void handleParcelableArray(Parcelable[] parcelables, String key, Set<Intent> visited) { diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig index 6fc7d90a8237..ecb4bb1394b6 100644 --- a/core/java/android/content/res/flags.aconfig +++ b/core/java/android/content/res/flags.aconfig @@ -114,3 +114,11 @@ flag { bug: "373535266" is_fixed_read_only: true } + +flag { + name: "self_targeting_android_resource_frro" + is_exported: true + namespace: "customization_picker" + description: "Fixes bug in Launcher preview by enabling overlays targeting 'android'" + bug: "377545987" +}
\ No newline at end of file diff --git a/core/java/android/content/res/loader/ResourcesProvider.java b/core/java/android/content/res/loader/ResourcesProvider.java index 830b7e0fa2d0..7eba1819d148 100644 --- a/core/java/android/content/res/loader/ResourcesProvider.java +++ b/core/java/android/content/res/loader/ResourcesProvider.java @@ -25,6 +25,7 @@ import android.content.om.OverlayManager; import android.content.pm.ApplicationInfo; import android.content.res.ApkAssets; import android.content.res.AssetFileDescriptor; +import android.content.res.Flags; import android.os.ParcelFileDescriptor; import android.util.Log; @@ -90,6 +91,10 @@ public class ResourcesProvider implements AutoCloseable, Closeable { throws IOException { Objects.requireNonNull(overlayInfo); Preconditions.checkArgument(overlayInfo.isFabricated(), "Not accepted overlay"); + if (!Flags.selfTargetingAndroidResourceFrro()) { + Preconditions.checkStringNotEmpty( + overlayInfo.getTargetOverlayableName(), "Without overlayable name"); + } final String overlayName = OverlayManagerImpl.checkOverlayNameValid(overlayInfo.getOverlayName()); final String path = diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig index d8d4e161006c..9c811fb84da3 100644 --- a/core/java/android/credentials/flags.aconfig +++ b/core/java/android/credentials/flags.aconfig @@ -124,3 +124,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + namespace: "credential_manager" + name: "settings_w_fixes" + description: "Settings improvements for credential manager" + bug: "373711451" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/credentials/selection/IntentFactory.java b/core/java/android/credentials/selection/IntentFactory.java index c521b96fd8ee..59539c40d636 100644 --- a/core/java/android/credentials/selection/IntentFactory.java +++ b/core/java/android/credentials/selection/IntentFactory.java @@ -16,7 +16,7 @@ package android.credentials.selection; -import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED; +import static android.credentials.flags.Flags.FLAG_PROPAGATE_USER_CONTEXT_FOR_INTENT_CREATION; import static android.credentials.flags.Flags.configurableSelectorUiEnabled; import android.annotation.FlaggedApi; @@ -24,6 +24,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.TestApi; +import android.annotation.UserIdInt; +import android.app.AppGlobals; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -32,6 +34,7 @@ import android.content.pm.PackageManager; import android.content.res.Resources; import android.os.IBinder; import android.os.Parcel; +import android.os.RemoteException; import android.os.ResultReceiver; import android.text.TextUtils; import android.util.Slog; @@ -46,7 +49,7 @@ import java.util.ArrayList; * @hide */ @TestApi -@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED) +@FlaggedApi(FLAG_PROPAGATE_USER_CONTEXT_FOR_INTENT_CREATION) public class IntentFactory { /** @@ -65,9 +68,10 @@ public class IntentFactory { @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling. @NonNull ArrayList<DisabledProviderData> disabledProviderDataList, - @NonNull ResultReceiver resultReceiver) { + @NonNull ResultReceiver resultReceiver, + @UserIdInt int userId) { return createCredentialSelectorIntentInternal(context, requestInfo, - disabledProviderDataList, resultReceiver); + disabledProviderDataList, resultReceiver, userId); } /** @@ -96,9 +100,10 @@ public class IntentFactory { @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling. @NonNull ArrayList<DisabledProviderData> disabledProviderDataList, - @NonNull ResultReceiver resultReceiver) { + @NonNull ResultReceiver resultReceiver, + @UserIdInt int userId) { IntentCreationResult result = createCredentialSelectorIntentInternal(context, requestInfo, - disabledProviderDataList, resultReceiver); + disabledProviderDataList, resultReceiver, userId); result.getIntent().putParcelableArrayListExtra( ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, enabledProviderDataList); return result; @@ -130,9 +135,10 @@ public class IntentFactory { @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling. @NonNull ArrayList<DisabledProviderData> disabledProviderDataList, - @NonNull ResultReceiver resultReceiver) { + @NonNull ResultReceiver resultReceiver, @UserIdInt int userId) { return createCredentialSelectorIntentForCredMan(context, requestInfo, - enabledProviderDataList, disabledProviderDataList, resultReceiver).getIntent(); + enabledProviderDataList, disabledProviderDataList, resultReceiver, + userId).getIntent(); } /** @@ -142,10 +148,10 @@ public class IntentFactory { @NonNull public static Intent createCancelUiIntent(@NonNull Context context, @NonNull IBinder requestToken, boolean shouldShowCancellationUi, - @NonNull String appPackageName) { + @NonNull String appPackageName, @UserIdInt int userId) { Intent intent = new Intent(); IntentCreationResult.Builder intentResultBuilder = new IntentCreationResult.Builder(intent); - setCredentialSelectorUiComponentName(context, intent, intentResultBuilder); + setCredentialSelectorUiComponentName(context, intent, intentResultBuilder, userId); intent.putExtra(CancelSelectionRequest.EXTRA_CANCEL_UI_REQUEST, new CancelSelectionRequest(new RequestToken(requestToken), shouldShowCancellationUi, appPackageName)); @@ -162,10 +168,10 @@ public class IntentFactory { @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling. @NonNull ArrayList<DisabledProviderData> disabledProviderDataList, - @NonNull ResultReceiver resultReceiver) { + @NonNull ResultReceiver resultReceiver, @UserIdInt int userId) { Intent intent = new Intent(); IntentCreationResult.Builder intentResultBuilder = new IntentCreationResult.Builder(intent); - setCredentialSelectorUiComponentName(context, intent, intentResultBuilder); + setCredentialSelectorUiComponentName(context, intent, intentResultBuilder, userId); intent.putParcelableArrayListExtra( ProviderData.EXTRA_DISABLED_PROVIDER_DATA_LIST, disabledProviderDataList); intent.putExtra(RequestInfo.EXTRA_REQUEST_INFO, requestInfo); @@ -175,9 +181,11 @@ public class IntentFactory { } private static void setCredentialSelectorUiComponentName(@NonNull Context context, - @NonNull Intent intent, @NonNull IntentCreationResult.Builder intentResultBuilder) { + @NonNull Intent intent, @NonNull IntentCreationResult.Builder intentResultBuilder, + @UserIdInt int userId) { if (configurableSelectorUiEnabled()) { - ComponentName componentName = getOemOverrideComponentName(context, intentResultBuilder); + ComponentName componentName = getOemOverrideComponentName(context, + intentResultBuilder, userId); ComponentName fallbackUiComponentName = null; try { @@ -210,7 +218,7 @@ public class IntentFactory { */ @Nullable private static ComponentName getOemOverrideComponentName(@NonNull Context context, - @NonNull IntentCreationResult.Builder intentResultBuilder) { + @NonNull IntentCreationResult.Builder intentResultBuilder, @UserIdInt int userId) { ComponentName result = null; String oemComponentString = Resources.getSystem() @@ -228,35 +236,43 @@ public class IntentFactory { if (oemComponentName != null) { try { intentResultBuilder.setOemUiPackageName(oemComponentName.getPackageName()); - ActivityInfo info = context.getPackageManager().getActivityInfo( - oemComponentName, - PackageManager.ComponentInfoFlags.of( - PackageManager.MATCH_SYSTEM_ONLY)); - boolean oemComponentEnabled = info.enabled; - int runtimeComponentEnabledState = context.getPackageManager() + ActivityInfo info; + if (android.credentials.flags.Flags.propagateUserContextForIntentCreation()) { + info = context.getPackageManager().getActivityInfo(oemComponentName, + PackageManager.ComponentInfoFlags.of( + PackageManager.MATCH_SYSTEM_ONLY)); + } else { + info = AppGlobals.getPackageManager().getActivityInfo( + oemComponentName, 0, userId); + } + boolean oemComponentEnabled = false; + if (info != null) { + oemComponentEnabled = info.enabled; + int runtimeComponentEnabledState = context.getPackageManager() .getComponentEnabledSetting(oemComponentName); - if (runtimeComponentEnabledState == PackageManager + if (runtimeComponentEnabledState == PackageManager .COMPONENT_ENABLED_STATE_ENABLED) { - oemComponentEnabled = true; - } else if (runtimeComponentEnabledState == PackageManager + oemComponentEnabled = true; + } else if (runtimeComponentEnabledState == PackageManager .COMPONENT_ENABLED_STATE_DISABLED) { oemComponentEnabled = false; - } - if (oemComponentEnabled && info.exported) { + } + if (oemComponentEnabled && info.exported) { intentResultBuilder.setOemUiUsageStatus(IntentCreationResult - .OemUiUsageStatus.SUCCESS); + .OemUiUsageStatus.SUCCESS); Slog.i(TAG, - "Found enabled oem CredMan UI component." - + oemComponentString); + "Found enabled oem CredMan UI component." + + oemComponentString); result = oemComponentName; - } else { - intentResultBuilder.setOemUiUsageStatus(IntentCreationResult - .OemUiUsageStatus.OEM_UI_CONFIG_SPECIFIED_FOUND_BUT_NOT_ENABLED); - Slog.i(TAG, - "Found enabled oem CredMan UI component but it was not " - + "enabled."); + } else { + intentResultBuilder.setOemUiUsageStatus(IntentCreationResult + .OemUiUsageStatus.OEM_UI_CONFIG_SPECIFIED_FOUND_BUT_NOT_ENABLED); + Slog.i(TAG, + "Found enabled oem CredMan UI component but it was not " + + "enabled."); + } } - } catch (PackageManager.NameNotFoundException e) { + } catch (RemoteException | PackageManager.NameNotFoundException e) { intentResultBuilder.setOemUiUsageStatus(IntentCreationResult.OemUiUsageStatus .OEM_UI_CONFIG_SPECIFIED_BUT_NOT_FOUND); Slog.i(TAG, "Unable to find oem CredMan UI component: " diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 5f3c15d1842e..4c9e73c8b21f 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -3202,7 +3202,8 @@ public class InputMethodService extends AbstractInputMethodService { */ @FlaggedApi(Flags.FLAG_ADAPTIVE_HANDWRITING_BOUNDS) public final void setStylusHandwritingRegion(@NonNull Region handwritingRegion) { - if (handwritingRegion.equals(mLastHandwritingRegion)) { + final Region immutableHandwritingRegion = new Region(handwritingRegion); + if (immutableHandwritingRegion.equals(mLastHandwritingRegion)) { Log.v(TAG, "Failed to set setStylusHandwritingRegion():" + " same region set twice."); return; @@ -3210,10 +3211,10 @@ public class InputMethodService extends AbstractInputMethodService { if (DEBUG) { Log.d(TAG, "Setting new handwriting region for stylus handwriting " - + handwritingRegion + " from last " + mLastHandwritingRegion); + + immutableHandwritingRegion + " from last " + mLastHandwritingRegion); } - mPrivOps.setHandwritingTouchableRegion(handwritingRegion); - mLastHandwritingRegion = handwritingRegion; + mPrivOps.setHandwritingTouchableRegion(immutableHandwritingRegion); + mLastHandwritingRegion = immutableHandwritingRegion; } /** diff --git a/core/java/android/net/http/X509TrustManagerExtensions.java b/core/java/android/net/http/X509TrustManagerExtensions.java index b44f75a585d5..3425b77be954 100644 --- a/core/java/android/net/http/X509TrustManagerExtensions.java +++ b/core/java/android/net/http/X509TrustManagerExtensions.java @@ -22,7 +22,7 @@ import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; -import android.net.platform.flags.Flags; +import android.security.Flags; import android.security.net.config.UserCertificateSource; import com.android.org.conscrypt.TrustManagerImpl; @@ -152,7 +152,7 @@ public class X509TrustManagerExtensions { * @throws IllegalArgumentException if the TrustManager is not compatible. * @return the properly ordered chain used for verification as a list of X509Certificates. */ - @FlaggedApi(Flags.FLAG_X509_EXTENSIONS_CERTIFICATE_TRANSPARENCY) + @FlaggedApi(Flags.FLAG_CERTIFICATE_TRANSPARENCY_CONFIGURATION) @NonNull public List<X509Certificate> checkServerTrusted( @SuppressLint("ArrayReturn") @NonNull X509Certificate[] chain, diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java index b509c7a441d3..230fa3fec930 100644 --- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java +++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java @@ -1355,14 +1355,21 @@ public final class MessageQueue { /** * @return true if we are blocked on a sync barrier + * + * Calls to this method must not be allowed to race with `next`. + * Specifically, the Looper thread must be paused before calling this method, + * and may not be resumed until after returning from this method. */ boolean isBlockedOnSyncBarrier() { throwIfNotTest(); if (mUseConcurrent) { + // Call nextMessage to get the stack drained into our priority queues + nextMessage(true); + Iterator<MessageNode> queueIter = mPriorityQueue.iterator(); MessageNode queueNode = iterateNext(queueIter); - if (queueNode.isBarrier()) { + if (queueNode != null && queueNode.isBarrier()) { long now = SystemClock.uptimeMillis(); /* Look for a deliverable async node. If one exists we are not blocked. */ @@ -1375,14 +1382,12 @@ public final class MessageQueue { * Look for a deliverable sync node. In this case, if one exists we are blocked * since the barrier prevents delivery of the Message. */ - while (queueNode.isBarrier()) { + while (queueNode != null && queueNode.isBarrier()) { queueNode = iterateNext(queueIter); } if (queueNode != null && now >= queueNode.getWhen()) { return true; } - - return false; } } else { Message msg = mMessages; @@ -1409,10 +1414,8 @@ public final class MessageQueue { if (iter != null && now >= iter.when) { return true; } - return false; } } - /* No barrier was found. */ return false; } diff --git a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java index de0259eb1e36..d7d8e4199b33 100644 --- a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java +++ b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java @@ -19,6 +19,7 @@ package android.os; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.TestApi; import android.app.ActivityThread; import android.app.Instrumentation; @@ -784,7 +785,7 @@ public final class MessageQueue { mMessageDirectlyQueued = false; nativePollOnce(ptr, mNextPollTimeoutMillis); - Message msg = nextMessage(); + Message msg = nextMessage(false); if (msg != null) { msg.markInUse(); return msg; @@ -1087,7 +1088,6 @@ public final class MessageQueue { * * Caller must ensure that this doesn't race 'next' from the Looper thread. */ - @SuppressLint("VisiblySynchronized") // Legacy MessageQueue synchronizes on this Long peekWhenForTest() { throwIfNotTest(); Message ret = nextMessage(true); @@ -1100,7 +1100,6 @@ public final class MessageQueue { * * Caller must ensure that this doesn't race 'next' from the Looper thread. */ - @SuppressLint("VisiblySynchronized") // Legacy MessageQueue synchronizes on this @Nullable Message pollForTest() { throwIfNotTest(); @@ -1109,13 +1108,21 @@ public final class MessageQueue { /** * @return true if we are blocked on a sync barrier + * + * Calls to this method must not be allowed to race with `next`. + * Specifically, the Looper thread must be paused before calling this method, + * and may not be resumed until after returning from this method. */ boolean isBlockedOnSyncBarrier() { throwIfNotTest(); + + // Call nextMessage to get the stack drained into our priority queues + nextMessage(true); + Iterator<MessageNode> queueIter = mPriorityQueue.iterator(); MessageNode queueNode = iterateNext(queueIter); - if (queueNode.isBarrier()) { + if (queueNode != null && queueNode.isBarrier()) { long now = SystemClock.uptimeMillis(); /* Look for a deliverable async node. If one exists we are not blocked. */ @@ -1128,15 +1135,14 @@ public final class MessageQueue { * Look for a deliverable sync node. In this case, if one exists we are blocked * since the barrier prevents delivery of the Message. */ - while (queueNode.isBarrier()) { + while (queueNode != null && queueNode.isBarrier()) { queueNode = iterateNext(queueIter); } if (queueNode != null && now >= queueNode.getWhen()) { return true; } - - return false; } + return false; } private StateNode getStateNode(StackNode node) { @@ -1193,7 +1199,7 @@ public final class MessageQueue { MessageNode p = (MessageNode) top; while (true) { - if (compare.compareMessage(p.mMessage, h, what, object, r, when)) { + if (compare.compareMessage(p, h, what, object, r, when)) { found = true; if (DEBUG) { Log.w(TAG, "stackHasMessages node matches"); @@ -1238,7 +1244,7 @@ public final class MessageQueue { while (iterator.hasNext()) { MessageNode msg = iterator.next(); - if (compare.compareMessage(msg.mMessage, h, what, object, r, when)) { + if (compare.compareMessage(msg, h, what, object, r, when)) { if (removeMatches) { found = true; if (queue.remove(msg)) { diff --git a/core/java/android/os/EventLogTags.logtags b/core/java/android/os/EventLogTags.logtags index b143a7443066..f57aad00e591 100644 --- a/core/java/android/os/EventLogTags.logtags +++ b/core/java/android/os/EventLogTags.logtags @@ -1,4 +1,4 @@ -# See system/core/logcat/event.logtags for a description of the format of this file. +# See system/logging/logcat/event.logtags for a description of the format of this file. option java_package android.os diff --git a/core/java/android/os/LegacyMessageQueue/MessageQueue.java b/core/java/android/os/LegacyMessageQueue/MessageQueue.java index 5e1e1fdca5c8..c0333e914b4d 100644 --- a/core/java/android/os/LegacyMessageQueue/MessageQueue.java +++ b/core/java/android/os/LegacyMessageQueue/MessageQueue.java @@ -814,6 +814,10 @@ public final class MessageQueue { /** * @return true if we are blocked on a sync barrier + * + * Calls to this method must not be allowed to race with `next`. + * Specifically, the Looper thread must be paused before calling this method, + * and may not be resumed until after returning from this method. */ boolean isBlockedOnSyncBarrier() { throwIfNotTest(); diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 7e73a5d04866..b9f2cfcd8ca8 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -6501,7 +6501,11 @@ public class UserManager { * @hide */ public static final void invalidateCacheOnUserDataChanged() { - if (android.multiuser.Flags.cacheProfilesReadOnly()) { + if (android.multiuser.Flags.cacheProfilesReadOnly() + || android.multiuser.Flags.cacheUserInfoReadOnly()) { + // TODO(b/383175685): Rename the invalidation call to make it clearer that it + // invalidates the caches for both getProfiles and getUserInfo (since they both use the + // same user_manager_user_data CachedProperty.api). UserManagerCache.invalidateProfiles(); } } diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index aacc6e2a3156..af96ccfee787 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -57,7 +57,7 @@ flag { is_exported: true is_fixed_read_only: true namespace: "permissions" - description: "enable enhanced confirmation incall apis" + description: "DEPRECATED, does not gate any apis" bug: "364535720" } diff --git a/core/java/android/view/EventLogTags.logtags b/core/java/android/view/EventLogTags.logtags index f3792930647a..95894fa32d6b 100644 --- a/core/java/android/view/EventLogTags.logtags +++ b/core/java/android/view/EventLogTags.logtags @@ -1,4 +1,4 @@ -# See system/core/logcat/event.logtags for a description of the format of this file. +# See system/logging/logcat/event.logtags for a description of the format of this file. option java_package android.view @@ -35,7 +35,7 @@ option java_package android.view # 6: Percent # Default value for data of type int/long is 2 (bytes). # -# See system/core/logcat/event.logtags for the master copy of the tags. +# See system/logging/logcat/event.logtags for the master copy of the tags. # 32000 - 32999 reserved for input method framework # IME animation is started. diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index d15b0f518f83..d13f0e21bf80 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -28277,28 +28277,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags |= PFLAG_FORCE_LAYOUT; mPrivateFlags |= PFLAG_INVALIDATED; - if (mParent != null) { - if (!mParent.isLayoutRequested()) { - mParent.requestLayout(); - } else { - clearMeasureCacheOfAncestors(); - } + if (mParent != null && !mParent.isLayoutRequested()) { + mParent.requestLayout(); } if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) { mAttachInfo.mViewRequestingLayout = null; } } - private void clearMeasureCacheOfAncestors() { - ViewParent parent = mParent; - while (parent instanceof View view) { - if (view.mMeasureCache != null) { - view.mMeasureCache.clear(); - } - parent = view.mParent; - } - } - /** * Forces this view to be laid out during the next layout pass. * This method does not call requestLayout() or forceLayout() @@ -28654,10 +28640,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ @RemotableViewMethod public void setMinimumHeight(int minHeight) { - if (mMinHeight != minHeight) { - mMinHeight = minHeight; - requestLayout(); - } + mMinHeight = minHeight; + requestLayout(); } /** @@ -28687,10 +28671,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ @RemotableViewMethod public void setMinimumWidth(int minWidth) { - if (mMinWidth != minWidth) { - mMinWidth = minWidth; - requestLayout(); - } + mMinWidth = minWidth; + requestLayout(); } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 1596b85bb461..c1b92ee3f74e 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -17,6 +17,7 @@ package android.view; import static android.adpf.Flags.adpfViewrootimplActionDownBoost; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.content.pm.ActivityInfo.OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS; import static android.graphics.HardwareRenderer.SYNC_CONTEXT_IS_STOPPED; @@ -7977,8 +7978,9 @@ public final class ViewRootImpl implements ViewParent, } private boolean moveFocusToAdjacentWindow(@FocusDirection int direction) { - if (getConfiguration().windowConfiguration.getWindowingMode() - != WINDOWING_MODE_MULTI_WINDOW) { + final int windowingMode = getConfiguration().windowConfiguration.getWindowingMode(); + if (windowingMode != WINDOWING_MODE_MULTI_WINDOW + && windowingMode != WINDOWING_MODE_FREEFORM) { return false; } try { diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 6d89f3d89077..f82e5f984f5d 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -948,7 +948,7 @@ public final class InputMethodManager { // requestedVisibleTypes of WindowInsetsController by hiding the IME final var statsToken = ImeTracker.forLogging().onStart( ImeTracker.TYPE_HIDE, ImeTracker.ORIGIN_CLIENT, - SoftInputShowHideReason.REASON_HIDE_WINDOW_LOST_FOCUS, + SoftInputShowHideReason.HIDE_WINDOW_LOST_FOCUS, false /* fromUser */); if (DEBUG) { Log.d(TAG, "onWindowLostFocus, hiding IME because " diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 9c2833b91a2b..9fe3fd6ddc1a 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -20,6 +20,7 @@ import static android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL; import static android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO; import static android.appwidget.flags.Flags.drawDataParcel; import static android.appwidget.flags.Flags.remoteAdapterConversion; +import static android.content.res.Flags.FLAG_SELF_TARGETING_ANDROID_RESOURCE_FRRO; import static android.util.TypedValue.TYPE_INT_COLOR_ARGB8; import static android.util.proto.ProtoInputStream.NO_MORE_FIELDS; import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR; @@ -8698,6 +8699,7 @@ public class RemoteViews implements Parcelable, Filter { * * @hide */ + @FlaggedApi(FLAG_SELF_TARGETING_ANDROID_RESOURCE_FRRO) @Nullable public static ColorResources createWithOverlay(Context context, SparseIntArray colorMapping) { diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index a05d003b2faf..cfe44f812220 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -414,6 +414,16 @@ flag { } flag { + name: "enable_desktop_recents_transitions_corners_bugfix" + namespace: "lse_desktop_experience" + description: "Enables rounded corners bugfix for Recents transitions." + bug: "383079261" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "enable_move_to_next_display_shortcut" namespace: "lse_desktop_experience" description: "Add new keyboard shortcut of moving a task into next display" diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index 30f0c7371270..1c27515b06f0 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -455,6 +455,17 @@ flag { } flag { + name: "remove_defer_hiding_client" + namespace: "windowing_frontend" + description: "Remove mDeferHidingClient since everything is in shell-transition." + is_fixed_read_only: true + bug: "382485959" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "relative_insets" namespace: "windowing_frontend" description: "Support insets definition and calculation relative to task bounds." diff --git a/core/java/com/android/internal/app/EventLogTags.logtags b/core/java/com/android/internal/app/EventLogTags.logtags index d681a8d26e8e..a18a8243305b 100644 --- a/core/java/com/android/internal/app/EventLogTags.logtags +++ b/core/java/com/android/internal/app/EventLogTags.logtags @@ -1,4 +1,4 @@ -# See system/core/logcat/event.logtags for a description of the format of this file. +# See system/logging/logcat/event.logtags for a description of the format of this file. option java_package com.android.internal.app; diff --git a/core/java/com/android/internal/content/om/OverlayManagerImpl.java b/core/java/com/android/internal/content/om/OverlayManagerImpl.java index fa5cf2a396b9..5d4e6a083af4 100644 --- a/core/java/com/android/internal/content/om/OverlayManagerImpl.java +++ b/core/java/com/android/internal/content/om/OverlayManagerImpl.java @@ -36,6 +36,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.parsing.FrameworkParsingPackageUtils; import android.content.res.AssetManager; +import android.content.res.Flags; import android.os.FabricatedOverlayInfo; import android.os.FabricatedOverlayInternal; import android.os.FabricatedOverlayInternalEntry; @@ -235,17 +236,24 @@ public class OverlayManagerImpl { Preconditions.checkArgument(!entryList.isEmpty(), "overlay entries shouldn't be empty"); final String overlayName = checkOverlayNameValid(overlayInternal.overlayName); checkPackageName(overlayInternal.packageName); - Preconditions.checkStringNotEmpty(overlayInternal.targetPackageName); + if (Flags.selfTargetingAndroidResourceFrro()) { + Preconditions.checkStringNotEmpty(overlayInternal.targetPackageName); + } else { + checkPackageName(overlayInternal.targetPackageName); + Preconditions.checkStringNotEmpty( + overlayInternal.targetOverlayable, + "Target overlayable should be neither null nor empty string."); + } final ApplicationInfo applicationInfo = mContext.getApplicationInfo(); String targetPackage = null; - if (TextUtils.equals(overlayInternal.targetPackageName, "android")) { + if (Flags.selfTargetingAndroidResourceFrro() && TextUtils.equals( + overlayInternal.targetPackageName, "android")) { targetPackage = AssetManager.FRAMEWORK_APK_PATH; } else { targetPackage = Preconditions.checkStringNotEmpty( applicationInfo.getBaseCodePath()); } - final Path frroPath = mBasePath.resolve(overlayName + FRRO_EXTENSION); final Path idmapPath = mBasePath.resolve(overlayName + IDMAP_EXTENSION); diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java index 2a5593f6d584..4d5e67ab8fde 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java +++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java @@ -299,6 +299,12 @@ public final class InputMethodDebug { return "SHOW_SOFT_INPUT_IMM_DEPRECATION"; case SoftInputShowHideReason.CONTROL_WINDOW_INSETS_ANIMATION: return "CONTROL_WINDOW_INSETS_ANIMATION"; + case SoftInputShowHideReason.SHOW_INPUT_TARGET_CHANGED: + return "SHOW_INPUT_TARGET_CHANGED"; + case SoftInputShowHideReason.HIDE_INPUT_TARGET_CHANGED: + return "HIDE_INPUT_TARGET_CHANGED"; + case SoftInputShowHideReason.HIDE_WINDOW_LOST_FOCUS: + return "HIDE_WINDOW_LOST_FOCUS"; default: return "Unknown=" + reason; } diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java index 592ea9e5e600..cf0580c2f021 100644 --- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java +++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java @@ -91,7 +91,7 @@ import java.lang.annotation.Retention; SoftInputShowHideReason.CONTROL_WINDOW_INSETS_ANIMATION, SoftInputShowHideReason.SHOW_INPUT_TARGET_CHANGED, SoftInputShowHideReason.HIDE_INPUT_TARGET_CHANGED, - SoftInputShowHideReason.REASON_HIDE_WINDOW_LOST_FOCUS, + SoftInputShowHideReason.HIDE_WINDOW_LOST_FOCUS, }) public @interface SoftInputShowHideReason { /** Default, undefined reason. */ @@ -340,18 +340,6 @@ public @interface SoftInputShowHideReason { int HIDE_WINDOW_LEGACY_DIRECT = ImeProtoEnums.REASON_HIDE_WINDOW_LEGACY_DIRECT; /** - * Show soft input because the input target changed - * {@link com.android.server.wm.ImeInsetsSourceProvider#onInputTargetChanged}. - */ - int SHOW_INPUT_TARGET_CHANGED = ImeProtoEnums.REASON_SHOW_INPUT_TARGET_CHANGED; - - /** - * Hide soft input because the input target changed by - * {@link com.android.server.wm.ImeInsetsSourceProvider#onInputTargetChanged}. - */ - int HIDE_INPUT_TARGET_CHANGED = ImeProtoEnums.REASON_HIDE_INPUT_TARGET_CHANGED; - - /** * Show / Hide soft input by * {@link android.inputmethodservice.InputMethodService#resetStateForNewConfiguration}. */ @@ -420,6 +408,18 @@ public @interface SoftInputShowHideReason { */ int CONTROL_WINDOW_INSETS_ANIMATION = ImeProtoEnums.REASON_CONTROL_WINDOW_INSETS_ANIMATION; + /** + * Show soft input because the input target changed + * {@link com.android.server.wm.ImeInsetsSourceProvider#onInputTargetChanged}. + */ + int SHOW_INPUT_TARGET_CHANGED = ImeProtoEnums.REASON_SHOW_INPUT_TARGET_CHANGED; + + /** + * Hide soft input because the input target changed by + * {@link com.android.server.wm.ImeInsetsSourceProvider#onInputTargetChanged}. + */ + int HIDE_INPUT_TARGET_CHANGED = ImeProtoEnums.REASON_HIDE_INPUT_TARGET_CHANGED; + /** Hide soft input when the window lost focus. */ - int REASON_HIDE_WINDOW_LOST_FOCUS = ImeProtoEnums.REASON_HIDE_WINDOW_LOST_FOCUS; + int HIDE_WINDOW_LOST_FOCUS = ImeProtoEnums.REASON_HIDE_WINDOW_LOST_FOCUS; } diff --git a/core/java/com/android/internal/logging/EventLogTags.logtags b/core/java/com/android/internal/logging/EventLogTags.logtags index 693bd16e6170..db47797cb03d 100644 --- a/core/java/com/android/internal/logging/EventLogTags.logtags +++ b/core/java/com/android/internal/logging/EventLogTags.logtags @@ -1,4 +1,4 @@ -# See system/core/logcat/event.logtags for a description of the format of this file. +# See system/logging/logcat/event.logtags for a description of the format of this file. option java_package com.android.internal.logging; diff --git a/core/java/com/android/internal/widget/CallLayout.java b/core/java/com/android/internal/widget/CallLayout.java index c85257578492..3a7c75afdd14 100644 --- a/core/java/com/android/internal/widget/CallLayout.java +++ b/core/java/com/android/internal/widget/CallLayout.java @@ -30,7 +30,6 @@ import android.util.AttributeSet; import android.view.RemotableViewMethod; import android.widget.FrameLayout; import android.widget.RemoteViews; -import android.widget.TextView; import android.widget.flags.Flags; import com.android.internal.R; @@ -59,7 +58,6 @@ public class CallLayout extends FrameLayout { private CachingIconView mConversationIconView; private CachingIconView mIcon; private CachingIconView mConversationIconBadgeBg; - private TextView mConversationText; public CallLayout(@NonNull Context context) { super(context); @@ -83,7 +81,6 @@ public class CallLayout extends FrameLayout { protected void onFinishInflate() { super.onFinishInflate(); mPeopleHelper.init(getContext()); - mConversationText = findViewById(R.id.conversation_text); mConversationIconView = findViewById(R.id.conversation_icon); mIcon = findViewById(R.id.icon); mConversationIconBadgeBg = findViewById(R.id.conversation_icon_badge_bg); diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java index 4b90420a75ee..b3ab5d3cd258 100644 --- a/core/java/com/android/internal/widget/ConversationLayout.java +++ b/core/java/com/android/internal/widget/ConversationLayout.java @@ -777,37 +777,40 @@ public class ConversationLayout extends FrameLayout } - int conversationAvatarSize; - int facepileAvatarSize; - int facePileBackgroundSize; - if (mIsCollapsed) { - conversationAvatarSize = mConversationAvatarSize; - facepileAvatarSize = mFacePileAvatarSize; - facePileBackgroundSize = facepileAvatarSize + 2 * mFacePileProtectionWidth; - } else { - conversationAvatarSize = mConversationAvatarSizeExpanded; - facepileAvatarSize = mFacePileAvatarSizeExpandedGroup; - facePileBackgroundSize = facepileAvatarSize + 2 * mFacePileProtectionWidthExpanded; - } - LayoutParams layoutParams = (LayoutParams) mConversationFacePile.getLayoutParams(); - layoutParams.width = conversationAvatarSize; - layoutParams.height = conversationAvatarSize; - mConversationFacePile.setLayoutParams(layoutParams); + if (!notificationsRedesignTemplates()) { + // We no longer need to update the size based on expansion state. + int conversationAvatarSize; + int facepileAvatarSize; + int facePileBackgroundSize; + if (mIsCollapsed) { + conversationAvatarSize = mConversationAvatarSize; + facepileAvatarSize = mFacePileAvatarSize; + facePileBackgroundSize = facepileAvatarSize + 2 * mFacePileProtectionWidth; + } else { + conversationAvatarSize = mConversationAvatarSizeExpanded; + facepileAvatarSize = mFacePileAvatarSizeExpandedGroup; + facePileBackgroundSize = facepileAvatarSize + 2 * mFacePileProtectionWidthExpanded; + } + LayoutParams layoutParams = (LayoutParams) mConversationFacePile.getLayoutParams(); + layoutParams.width = conversationAvatarSize; + layoutParams.height = conversationAvatarSize; + mConversationFacePile.setLayoutParams(layoutParams); - layoutParams = (LayoutParams) bottomView.getLayoutParams(); - layoutParams.width = facepileAvatarSize; - layoutParams.height = facepileAvatarSize; - bottomView.setLayoutParams(layoutParams); + layoutParams = (LayoutParams) bottomView.getLayoutParams(); + layoutParams.width = facepileAvatarSize; + layoutParams.height = facepileAvatarSize; + bottomView.setLayoutParams(layoutParams); - layoutParams = (LayoutParams) topView.getLayoutParams(); - layoutParams.width = facepileAvatarSize; - layoutParams.height = facepileAvatarSize; - topView.setLayoutParams(layoutParams); + layoutParams = (LayoutParams) topView.getLayoutParams(); + layoutParams.width = facepileAvatarSize; + layoutParams.height = facepileAvatarSize; + topView.setLayoutParams(layoutParams); - layoutParams = (LayoutParams) bottomBackground.getLayoutParams(); - layoutParams.width = facePileBackgroundSize; - layoutParams.height = facePileBackgroundSize; - bottomBackground.setLayoutParams(layoutParams); + layoutParams = (LayoutParams) bottomBackground.getLayoutParams(); + layoutParams.width = facePileBackgroundSize; + layoutParams.height = facePileBackgroundSize; + bottomBackground.setLayoutParams(layoutParams); + } } /** @@ -832,6 +835,11 @@ public class ConversationLayout extends FrameLayout * update the icon position and sizing */ private void updateIconPositionAndSize() { + if (notificationsRedesignTemplates()) { + // Icon size is fixed in the redesign. + return; + } + int badgeProtrusion; int conversationAvatarSize; if (mIsOneToOne || mIsCollapsed) { @@ -864,6 +872,11 @@ public class ConversationLayout extends FrameLayout } private void updatePaddingsBasedOnContentAvailability() { + if (notificationsRedesignTemplates()) { + // group icons have the same size as 1:1 conversations + return; + } + // groups have avatars that need more spacing mMessagingLinearLayout.setSpacing( mIsOneToOne ? mMessageSpacingStandard : mMessageSpacingGroup); diff --git a/core/java/org/chromium/arc/EventLogTags.logtags b/core/java/org/chromium/arc/EventLogTags.logtags index 1b7160e90224..8102d6f10ed4 100644 --- a/core/java/org/chromium/arc/EventLogTags.logtags +++ b/core/java/org/chromium/arc/EventLogTags.logtags @@ -1,4 +1,4 @@ -# See system/core/logcat/event.logtags for a description of the format of this file. +# See system/logging/logcat/event.logtags for a description of the format of this file. option java_package org.chromium.arc diff --git a/core/res/res/layout/notification_2025_conversation_face_pile_layout.xml b/core/res/res/layout/notification_2025_conversation_face_pile_layout.xml index b25adaabf8e8..68eafee03848 100644 --- a/core/res/res/layout/notification_2025_conversation_face_pile_layout.xml +++ b/core/res/res/layout/notification_2025_conversation_face_pile_layout.xml @@ -18,14 +18,14 @@ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/conversation_face_pile" - android:layout_width="@dimen/conversation_avatar_size" - android:layout_height="@dimen/conversation_avatar_size" + android:layout_width="@dimen/notification_2025_icon_circle_size" + android:layout_height="@dimen/notification_2025_icon_circle_size" android:forceHasOverlappingRendering="false" > <ImageView android:id="@+id/conversation_face_pile_top" - android:layout_width="@dimen/messaging_avatar_size" - android:layout_height="@dimen/messaging_avatar_size" + android:layout_width="@dimen/notification_2025_face_pile_avatar_size" + android:layout_height="@dimen/notification_2025_face_pile_avatar_size" android:scaleType="centerCrop" android:layout_gravity="end|top" android:background="@drawable/notification_icon_circle" @@ -43,8 +43,8 @@ /> <ImageView android:id="@+id/conversation_face_pile_bottom" - android:layout_width="@dimen/messaging_avatar_size" - android:layout_height="@dimen/messaging_avatar_size" + android:layout_width="@dimen/notification_2025_face_pile_avatar_size" + android:layout_height="@dimen/notification_2025_face_pile_avatar_size" android:scaleType="centerCrop" android:layout_gravity="center" android:background="@drawable/notification_icon_circle" diff --git a/core/res/res/layout/notification_2025_conversation_header.xml b/core/res/res/layout/notification_2025_conversation_header.xml new file mode 100644 index 000000000000..db79e79c96df --- /dev/null +++ b/core/res/res/layout/notification_2025_conversation_header.xml @@ -0,0 +1,171 @@ +<?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 + --> + +<com.android.internal.widget.ConversationHeaderLinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/conversation_header" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:paddingTop="@dimen/notification_2025_margin" + > + + <TextView + android:id="@+id/conversation_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title" + android:textSize="16sp" + android:singleLine="true" + android:layout_weight="1" + /> + + <TextView + android:id="@+id/app_name_divider" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info" + android:layout_marginStart="@dimen/notification_conversation_header_separating_margin" + android:text="@string/notification_header_divider_symbol" + android:singleLine="true" + android:visibility="gone" + /> + + <!-- App Name --> + <com.android.internal.widget.ObservableTextView + android:id="@+id/app_name_text" + android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/notification_conversation_header_separating_margin" + android:singleLine="true" + android:visibility="gone" + /> + + <TextView + android:id="@+id/time_divider" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info" + android:layout_marginStart="@dimen/notification_conversation_header_separating_margin" + android:text="@string/notification_header_divider_symbol" + android:singleLine="true" + android:visibility="gone" + /> + + <DateTimeView + android:id="@+id/time" + android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Time" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/notification_conversation_header_separating_margin" + android:showRelative="true" + android:singleLine="true" + android:visibility="gone" + /> + + <ViewStub + android:id="@+id/chronometer" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/notification_conversation_header_separating_margin" + android:layout="@layout/notification_template_part_chronometer" + android:visibility="gone" + /> + + <TextView + android:id="@+id/verification_divider" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info" + android:layout_marginStart="@dimen/notification_conversation_header_separating_margin" + android:text="@string/notification_header_divider_symbol" + android:singleLine="true" + android:visibility="gone" + /> + + <ImageView + android:id="@+id/verification_icon" + android:layout_width="@dimen/notification_verification_icon_size" + android:layout_height="@dimen/notification_verification_icon_size" + android:layout_marginStart="@dimen/notification_conversation_header_separating_margin" + android:baseline="10dp" + android:scaleType="fitCenter" + android:src="@drawable/ic_notifications_alerted" + android:visibility="gone" + /> + + <TextView + android:id="@+id/verification_text" + android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/notification_conversation_header_separating_margin" + android:layout_weight="100" + android:showRelative="true" + android:singleLine="true" + android:visibility="gone" + /> + + <ImageButton + android:id="@+id/feedback" + android:layout_width="@dimen/notification_feedback_size" + android:layout_height="@dimen/notification_feedback_size" + android:layout_marginStart="@dimen/notification_header_separating_margin" + android:background="?android:selectableItemBackgroundBorderless" + android:contentDescription="@string/notification_feedback_indicator" + android:baseline="13dp" + android:scaleType="fitCenter" + android:src="@drawable/ic_feedback_indicator" + android:visibility="gone" + /> + + <ImageView + android:id="@+id/phishing_alert" + android:layout_width="@dimen/notification_phishing_alert_size" + android:layout_height="@dimen/notification_phishing_alert_size" + android:layout_marginStart="@dimen/notification_conversation_header_separating_margin" + android:baseline="10dp" + android:scaleType="fitCenter" + android:src="@drawable/ic_dialog_alert_material" + android:visibility="gone" + android:contentDescription="@string/notification_phishing_alert_content_description" + /> + + <ImageView + android:id="@+id/profile_badge" + android:layout_width="@dimen/notification_badge_size" + android:layout_height="@dimen/notification_badge_size" + android:layout_marginStart="@dimen/notification_conversation_header_separating_margin" + android:baseline="10dp" + android:scaleType="fitCenter" + android:visibility="gone" + android:contentDescription="@string/notification_work_profile_content_description" + /> + + <ImageView + android:id="@+id/alerted_icon" + android:layout_width="@dimen/notification_alerted_size" + android:layout_height="@dimen/notification_alerted_size" + android:layout_marginStart="@dimen/notification_conversation_header_separating_margin" + android:baseline="10dp" + android:contentDescription="@string/notification_alerted_content_description" + android:scaleType="fitCenter" + android:src="@drawable/ic_notifications_alerted" + android:visibility="gone" + /> +</com.android.internal.widget.ConversationHeaderLinearLayout> diff --git a/core/res/res/layout/notification_2025_conversation_icon_container.xml b/core/res/res/layout/notification_2025_conversation_icon_container.xml index 90befd911bdf..7ec2450ceb71 100644 --- a/core/res/res/layout/notification_2025_conversation_icon_container.xml +++ b/core/res/res/layout/notification_2025_conversation_icon_container.xml @@ -18,32 +18,27 @@ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/conversation_icon_container" - android:layout_width="@dimen/conversation_content_start" + android:layout_width="@dimen/notification_2025_content_margin_start" android:layout_height="wrap_content" android:gravity="start|top" android:clipChildren="false" android:clipToPadding="false" - android:paddingTop="20dp" - android:paddingBottom="16dp" android:importantForAccessibility="no" > <FrameLayout android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_margin="@dimen/notification_2025_margin" android:clipChildren="false" android:clipToPadding="false" android:layout_gravity="top|center_horizontal" > - <!-- Big icon: 48x48, 12dp padding top, 16dp padding sides --> <com.android.internal.widget.CachingIconView android:id="@+id/conversation_icon" - android:layout_width="@dimen/conversation_avatar_size" - android:layout_height="@dimen/conversation_avatar_size" - android:layout_marginLeft="@dimen/conversation_badge_protrusion" - android:layout_marginRight="@dimen/conversation_badge_protrusion" - android:layout_marginBottom="@dimen/conversation_badge_protrusion" + android:layout_width="@dimen/notification_2025_icon_circle_size" + android:layout_height="@dimen/notification_2025_icon_circle_size" android:background="@drawable/notification_icon_circle" android:clipToOutline="true" android:scaleType="centerCrop" @@ -52,19 +47,25 @@ <ViewStub android:layout="@layout/notification_2025_conversation_face_pile_layout" - android:layout_width="@dimen/conversation_avatar_size" - android:layout_height="@dimen/conversation_avatar_size" - android:layout_marginLeft="@dimen/conversation_badge_protrusion" - android:layout_marginRight="@dimen/conversation_badge_protrusion" - android:layout_marginBottom="@dimen/conversation_badge_protrusion" + android:layout_width="@dimen/notification_2025_icon_circle_size" + android:layout_height="@dimen/notification_2025_icon_circle_size" android:id="@+id/conversation_face_pile" /> + <!-- The badge icon is visually aligned to the square containing the conversation icon, + but it has a border in the color of the background that is meant to delimit it from the + conversation icon. This border, although not visible due to the color, is technically + outside these bounds. + In order to align the badge properly to the bottom end of the square, we use a top/start + margin that is equal to (size of the conversation icon - size of the badge - size of the + border on one side). + --> <FrameLayout android:id="@+id/conversation_icon_badge" - android:layout_width="@dimen/conversation_icon_size_badged" - android:layout_height="@dimen/conversation_icon_size_badged" - android:layout_gravity="end|bottom" + android:layout_width="@dimen/notification_2025_conversation_icon_badge_size" + android:layout_height="@dimen/notification_2025_conversation_icon_badge_size" + android:layout_marginTop="@dimen/notification_2025_conversation_icon_badge_position" + android:layout_marginStart="@dimen/notification_2025_conversation_icon_badge_position" android:clipChildren="false" android:clipToPadding="false" > @@ -83,7 +84,7 @@ android:id="@+id/icon" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_margin="4dp" + android:layout_margin="@dimen/notification_2025_conversation_icon_badge_padding" android:layout_gravity="center" android:forceHasOverlappingRendering="false" /> diff --git a/core/res/res/layout/notification_2025_template_collapsed_call.xml b/core/res/res/layout/notification_2025_template_collapsed_call.xml index 614444d6b2f0..c4bca1142ece 100644 --- a/core/res/res/layout/notification_2025_template_collapsed_call.xml +++ b/core/res/res/layout/notification_2025_template_collapsed_call.xml @@ -41,13 +41,13 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" - android:layout_marginStart="@dimen/conversation_content_start" + android:layout_marginStart="@dimen/notification_2025_content_margin_start" android:orientation="vertical" android:paddingBottom="@dimen/notification_2025_margin" > <include - layout="@layout/notification_template_conversation_header" + layout="@layout/notification_2025_conversation_header" android:layout_width="wrap_content" android:layout_height="wrap_content" /> diff --git a/core/res/res/layout/notification_2025_template_conversation.xml b/core/res/res/layout/notification_2025_template_conversation.xml index 0c4c7fba90b1..f31f65e90950 100644 --- a/core/res/res/layout/notification_2025_template_conversation.xml +++ b/core/res/res/layout/notification_2025_template_conversation.xml @@ -60,11 +60,11 @@ <!-- Use layout_marginStart instead of paddingStart to work around strange measurement behavior on lower display densities. --> <include - layout="@layout/notification_template_conversation_header" + layout="@layout/notification_2025_conversation_header" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="2dp" - android:layout_marginStart="@dimen/conversation_content_start" + android:layout_marginStart="@dimen/notification_2025_content_margin_start" /> <!-- Messages --> @@ -86,7 +86,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/notification_content_margin" - android:layout_marginStart="@dimen/conversation_content_start" + android:layout_marginStart="@dimen/notification_2025_content_margin_start" android:layout_marginEnd="@dimen/notification_content_margin_end" /> <include layout="@layout/notification_material_action_list" /> </com.android.internal.widget.RemeasuringLinearLayout> diff --git a/core/res/res/layout/notification_2025_template_expanded_call.xml b/core/res/res/layout/notification_2025_template_expanded_call.xml index 3ff71b78835d..2af0ec2972df 100644 --- a/core/res/res/layout/notification_2025_template_expanded_call.xml +++ b/core/res/res/layout/notification_2025_template_expanded_call.xml @@ -49,13 +49,13 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" - android:layout_marginStart="@dimen/conversation_content_start" + android:layout_marginStart="@dimen/notification_2025_content_margin_start" android:orientation="vertical" android:minHeight="68dp" > <include - layout="@layout/notification_template_conversation_header" + layout="@layout/notification_2025_conversation_header" android:layout_width="wrap_content" android:layout_height="wrap_content" /> @@ -97,7 +97,7 @@ layout="@layout/notification_template_smart_reply_container" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginStart="@dimen/notification_content_margin_start" + android:layout_marginStart="@dimen/notification_2025_content_margin_start" android:layout_marginEnd="@dimen/notification_content_margin_end" android:layout_marginTop="@dimen/notification_content_margin" /> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index f53acbfac71d..51bd4cc6cc8a 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -912,6 +912,8 @@ <dimen name="conversation_icon_size_badged">20dp</dimen> <!-- size of the conversation avatar in an expanded group --> <dimen name="conversation_avatar_size_group_expanded">@dimen/messaging_avatar_size</dimen> + <!-- size of the face pile icons (2025 redesign version) --> + <dimen name="notification_2025_face_pile_avatar_size">24dp</dimen> <!-- size of the face pile icons --> <dimen name="conversation_face_pile_avatar_size">32dp</dimen> <!-- size of the face pile icons when the group is expanded --> @@ -939,6 +941,18 @@ <!-- The size of the importance ring --> <dimen name="importance_ring_size">20dp</dimen> + <!-- The spacing around the app icon badge shown next to the conversation icon --> + <dimen name="notification_2025_conversation_icon_badge_padding">2dp</dimen> + + <!-- Top and start margin for the app icon badge shown next to the conversation icon, to align + it to the bottom end corner. + 40dp (conversation icon size) - 16dp (actual size of badge) - 2dp (badge padding) --> + <dimen name="notification_2025_conversation_icon_badge_position">22dp</dimen> + + <!-- The size of the app icon badge shown next to the conversation icon, including its padding. + The actual size of the icon is 16dp, plus 2dp for each side for the padding. --> + <dimen name="notification_2025_conversation_icon_badge_size">20dp</dimen> + <!-- The top padding of the conversation icon container in the regular state--> <dimen name="conversation_icon_container_top_padding">20dp</dimen> diff --git a/core/tests/coretests/src/android/view/ViewGroupTest.java b/core/tests/coretests/src/android/view/ViewGroupTest.java index 43c404e849fe..ae3ad36b532c 100644 --- a/core/tests/coretests/src/android/view/ViewGroupTest.java +++ b/core/tests/coretests/src/android/view/ViewGroupTest.java @@ -213,35 +213,6 @@ public class ViewGroupTest { assertTrue(autofillableViews.containsAll(Arrays.asList(viewA, viewC))); } - @Test - public void testMeasureCache() { - final int spec1 = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.AT_MOST); - final int spec2 = View.MeasureSpec.makeMeasureSpec(50, View.MeasureSpec.AT_MOST); - final Context context = getInstrumentation().getContext(); - final View child = new View(context); - final TestView parent = new TestView(context, 0); - parent.addView(child); - - child.setPadding(1, 2, 3, 4); - parent.measure(spec1, spec1); - assertEquals(4, parent.getMeasuredWidth()); - assertEquals(6, parent.getMeasuredHeight()); - - child.setPadding(5, 6, 7, 8); - parent.measure(spec2, spec2); - assertEquals(12, parent.getMeasuredWidth()); - assertEquals(14, parent.getMeasuredHeight()); - - // This ends the state of forceLayout. - parent.layout(0, 0, 50, 50); - - // The cached values should be cleared after the new setPadding is called. And the measured - // width and height should be up-to-date. - parent.measure(spec1, spec1); - assertEquals(12, parent.getMeasuredWidth()); - assertEquals(14, parent.getMeasuredHeight()); - } - private static void getUnobscuredTouchableRegion(Region outRegion, View view) { outRegion.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()); final ViewParent parent = view.getParent(); @@ -269,19 +240,6 @@ public class ViewGroupTest { protected void onLayout(boolean changed, int l, int t, int r, int b) { // We don't layout this view. } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int measuredWidth = 0; - int measuredHeight = 0; - final int count = getChildCount(); - for (int i = 0; i < count; i++) { - final View child = getChildAt(i); - measuredWidth += child.getPaddingLeft() + child.getPaddingRight(); - measuredHeight += child.getPaddingTop() + child.getPaddingBottom(); - } - setMeasuredDimension(measuredWidth, measuredHeight); - } } public static class AutofillableTestView extends TestView { diff --git a/core/tests/overlaytests/device_self_targeting/Android.bp b/core/tests/overlaytests/device_self_targeting/Android.bp index 931eac515e31..14a3cdf98436 100644 --- a/core/tests/overlaytests/device_self_targeting/Android.bp +++ b/core/tests/overlaytests/device_self_targeting/Android.bp @@ -31,6 +31,7 @@ android_test { "androidx.test.ext.junit", "mockito-target-minus-junit4", "truth", + "flag-junit", ], optimize: { diff --git a/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java b/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java index 28d6545c8a5b..bcf1446b3467 100644 --- a/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java +++ b/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java @@ -16,6 +16,7 @@ package com.android.overlaytest; +import static android.content.res.Flags.FLAG_SELF_TARGETING_ANDROID_RESOURCE_FRRO; import static android.content.Context.MODE_PRIVATE; import static android.content.pm.PackageManager.SIGNATURE_NO_MATCH; @@ -41,6 +42,8 @@ import android.os.FabricatedOverlayInternal; import android.os.FabricatedOverlayInternalEntry; import android.os.ParcelFileDescriptor; import android.os.UserHandle; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.Log; import android.util.Pair; import android.util.TypedValue; @@ -76,6 +79,8 @@ import java.util.List; */ @RunWith(AndroidJUnit4.class) public class OverlayManagerImplTest { + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private static final String TAG = "OverlayManagerImplTest"; private static final String TARGET_COLOR_RES = "color/mycolor"; @@ -210,6 +215,22 @@ public class OverlayManagerImplTest { } @Test + @DisableFlags(FLAG_SELF_TARGETING_ANDROID_RESOURCE_FRRO) + public void registerOverlay_forAndroidPackage_shouldFail() { + FabricatedOverlayInternal overlayInternal = + createOverlayWithName( + mOverlayName, + SYSTEM_APP_OVERLAYABLE, + "android", + List.of(Pair.create("color/white", Pair.create(null, Color.BLACK)))); + + assertThrows( + "Wrong target package name", + IllegalArgumentException.class, + () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal)); + } + + @Test public void getOverlayInfosForTarget_defaultShouldBeZero() { List<OverlayInfo> overlayInfos = mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()); diff --git a/core/tests/overlaytests/host/Android.bp b/core/tests/overlaytests/host/Android.bp index 634098074cca..9b7200436f02 100644 --- a/core/tests/overlaytests/host/Android.bp +++ b/core/tests/overlaytests/host/Android.bp @@ -28,14 +28,14 @@ java_test_host { test_suites: [ "device-tests", ], - target_required: [ - "OverlayHostTests_NonPlatformSignatureOverlay", - "OverlayHostTests_PlatformSignatureStaticOverlay", - "OverlayHostTests_PlatformSignatureOverlay", - "OverlayHostTests_UpdateOverlay", - "OverlayHostTests_FrameworkOverlayV1", - "OverlayHostTests_FrameworkOverlayV2", - "OverlayHostTests_AppOverlayV1", - "OverlayHostTests_AppOverlayV2", + device_common_data: [ + ":OverlayHostTests_NonPlatformSignatureOverlay", + ":OverlayHostTests_PlatformSignatureStaticOverlay", + ":OverlayHostTests_PlatformSignatureOverlay", + ":OverlayHostTests_UpdateOverlay", + ":OverlayHostTests_FrameworkOverlayV1", + ":OverlayHostTests_FrameworkOverlayV2", + ":OverlayHostTests_AppOverlayV1", + ":OverlayHostTests_AppOverlayV2", ], } diff --git a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_immersive_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_immersive_button_dark.xml deleted file mode 100644 index f3800e05148e..000000000000 --- a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_immersive_button_dark.xml +++ /dev/null @@ -1,25 +0,0 @@ -<?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:height="24dp" - android:width="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> - <path - android:fillColor="#000000" - android:pathData="M5,5H10V7H7V10H5V5M14,5H19V10H17V7H14V5M17,14H19V19H14V17H17V14M10,17V19H5V14H7V17H10Z"/> -</vector> diff --git a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_immersive_exit_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_immersive_or_maximize_exit_button_dark.xml index 5260450e8a13..b6289e2d6dd7 100644 --- a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_immersive_exit_button_dark.xml +++ b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_immersive_or_maximize_exit_button_dark.xml @@ -21,6 +21,6 @@ android:viewportHeight="960" android:tint="?attr/colorControlNormal"> <path - android:fillColor="@android:color/white" - android:pathData="M240,840L240,720L120,720L120,640L320,640L320,840L240,840ZM640,840L640,640L840,640L840,720L720,720L720,840L640,840ZM120,320L120,240L240,240L240,120L320,120L320,320L120,320ZM640,320L640,120L720,120L720,240L840,240L840,320L640,320Z"/> + android:fillColor="@android:color/black" + android:pathData="M520,560L600,560L600,560ZM320,720Q287,720 263.5,696.5Q240,673 240,640L240,160Q240,127 263.5,103.5Q287,80 320,80L800,80Q833,80 856.5,103.5Q880,127 880,160L880,640Q880,673 856.5,696.5Q833,720 800,720L320,720ZM320,640L800,640Q800,640 800,640Q800,640 800,640L800,160Q800,160 800,160Q800,160 800,160L320,160Q320,160 320,160Q320,160 320,160L320,640Q320,640 320,640Q320,640 320,640ZM160,880Q127,880 103.5,856.5Q80,833 80,800L80,240L160,240L160,800Q160,800 160,800Q160,800 160,800L720,800L720,880L160,880ZM320,160L320,160Q320,160 320,160Q320,160 320,160L320,640Q320,640 320,640Q320,640 320,640L320,640Q320,640 320,640Q320,640 320,640L320,160Q320,160 320,160Q320,160 320,160Z"/> </vector> diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java index f9f43bc8dfae..86be0d4a4ea5 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java @@ -159,7 +159,8 @@ public class SplitScreenConstants { * {@link PersistentSnapPosition} + {@link #NOT_IN_SPLIT}. */ @IntDef(value = { - NOT_IN_SPLIT, + NOT_IN_SPLIT, // user is not in split screen + SNAP_TO_NONE, // in "free snap mode," where apps are fully resizable SNAP_TO_2_33_66, SNAP_TO_2_50_50, SNAP_TO_2_66_33, @@ -171,6 +172,23 @@ public class SplitScreenConstants { }) public @interface SplitScreenState {} + /** Converts a {@link SplitScreenState} to a human-readable string. */ + public static String stateToString(@SplitScreenState int state) { + return switch (state) { + case NOT_IN_SPLIT -> "NOT_IN_SPLIT"; + case SNAP_TO_NONE -> "SNAP_TO_NONE"; + case SNAP_TO_2_33_66 -> "SNAP_TO_2_33_66"; + case SNAP_TO_2_50_50 -> "SNAP_TO_2_50_50"; + case SNAP_TO_2_66_33 -> "SNAP_TO_2_66_33"; + case SNAP_TO_2_90_10 -> "SNAP_TO_2_90_10"; + case SNAP_TO_2_10_90 -> "SNAP_TO_2_10_90"; + case SNAP_TO_3_33_33_33 -> "SNAP_TO_3_33_33_33"; + case SNAP_TO_3_45_45_10 -> "SNAP_TO_3_45_45_10"; + case SNAP_TO_3_10_45_45 -> "SNAP_TO_3_10_45_45"; + default -> "UNKNOWN"; + }; + } + /** * Checks if the snapPosition in question is a {@link PersistentSnapPosition}. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt index 7243ea36b137..68c42d6a2648 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt @@ -18,6 +18,8 @@ package com.android.wm.shell.apptoweb +import android.app.assist.AssistContent +import android.app.assist.AssistContent.EXTRA_SESSION_TRANSFER_WEB_URI import android.content.Context import android.content.Intent import android.content.Intent.ACTION_VIEW @@ -102,3 +104,10 @@ fun getDomainVerificationUserState( return null } } + +/** + * Returns the web uri from the given [AssistContent]. + */ +fun AssistContent.getSessionWebUri(): Uri? { + return extras.getParcelable(EXTRA_SESSION_TRANSFER_WEB_URI) ?: webUri +} 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 60a52a808a54..56efdb885512 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 @@ -1314,7 +1314,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } } - if (handlePrepareTransition(info, st, ft, finishCallback)) { + if (handlePrepareTransition(transition, info, st, ft, finishCallback)) { if (checkTakeoverFlags()) { mTakeoverHandler = mTransitions.getHandlerForTakeover(transition, info); } @@ -1630,7 +1630,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont * happen when core make an activity become visible. */ @VisibleForTesting - boolean handlePrepareTransition( + boolean handlePrepareTransition(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction st, @NonNull SurfaceControl.Transaction ft, @@ -1678,6 +1678,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } } st.apply(); + // In case other transition handler took the handleRequest before this class. + mPrepareOpenTransition = transition; mFinishOpenTransaction = ft; mFinishOpenTransitionCallback = finishCallback; mOpenTransitionInfo = info; 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 5129d83cdd8b..c74bf53268f9 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 @@ -338,6 +338,11 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged // Make mImeSourceControl point to the new control before starting the animation. if (hadImeSourceControl && mImeSourceControl != imeSourceControl) { mImeSourceControl.release(SurfaceControl::release); + if (android.view.inputmethod.Flags.refactorInsetsController() + && !hasImeLeash && mAnimation != null) { + // In case of losing the leash, the animation should be cancelled. + mAnimation.cancel(); + } } mImeSourceControl = imeSourceControl; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java index 813772f20a8a..2f5afcaa907b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java @@ -352,8 +352,8 @@ public class DividerSnapAlgorithm { ? mPinnedTaskbarInsets.right : mPinnedTaskbarInsets.bottom; float ratio = areOffscreenRatiosSupported() - ? SplitLayout.OFFSCREEN_ASYMMETRIC_RATIO - : SplitLayout.ONSCREEN_ONLY_ASYMMETRIC_RATIO; + ? SplitSpec.OFFSCREEN_ASYMMETRIC_RATIO + : SplitSpec.ONSCREEN_ONLY_ASYMMETRIC_RATIO; int size = (int) (ratio * (end - start)) - mDividerSize / 2; int leftTopPosition = start + pinnedTaskbarShiftStart + size; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index e6b6ef737f6e..88c91db27bf3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -112,11 +112,6 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange private static final int FLING_EXIT_DURATION = 450; private static final int FLING_OFFSCREEN_DURATION = 500; - /** A split ratio used on larger screens, where we can fit both apps onscreen. */ - public static final float ONSCREEN_ONLY_ASYMMETRIC_RATIO = 0.33f; - /** A split ratio used on smaller screens, where we place one app mostly offscreen. */ - public static final float OFFSCREEN_ASYMMETRIC_RATIO = 0.1f; - // Here are some (arbitrarily decided) layer definitions used during animations to make sure the // layers stay in order. (During transitions, everything is reparented onto a transition root // and can be freely relayered.) @@ -236,7 +231,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange updateDividerConfig(mContext); mRootBounds.set(configuration.windowConfiguration.getBounds()); - mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds); + updateLayouts(); mInteractionJankMonitor = InteractionJankMonitor.getInstance(); resetDividerPosition(); updateInvisibleRect(); @@ -490,7 +485,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange mIsLargeScreen = configuration.smallestScreenWidthDp >= 600; mIsLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait, configuration); - mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds); + updateLayouts(); updateDividerConfig(mContext); initDividerPosition(mTempRect, wasLeftRightSplit); updateInvisibleRect(); @@ -518,7 +513,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange mRootBounds.set(tmpRect); mIsLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait, mIsLargeScreen, mRootBounds.width() >= mRootBounds.height()); - mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds); + updateLayouts(); initDividerPosition(mTempRect, wasLeftRightSplit); } @@ -652,7 +647,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange if (!mPinnedTaskbarInsets.equals(pinnedTaskbarInsets)) { mPinnedTaskbarInsets = pinnedTaskbarInsets; // Refresh the DividerSnapAlgorithm. - mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds); + updateLayouts(); // If the divider is no longer placed on a snap point, animate it to the nearest one. DividerSnapAlgorithm.SnapTarget snapTarget = findSnapTarget(mDividerPosition, 0, false /* hardDismiss */); @@ -824,8 +819,22 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange return mDividerSnapAlgorithm.calculateSnapTarget(position, velocity, hardDismiss); } - private DividerSnapAlgorithm getSnapAlgorithm(Context context, Rect rootBounds) { - final Rect insets = getDisplayStableInsets(context); + /** + * (Re)calculates the split screen logic for this particular display/orientation. Refreshes the + * DividerSnapAlgorithm, which controls divider snap points, and populates a map in SplitState + * with bounds for all valid split layouts. + */ + private void updateLayouts() { + // Update SplitState map + + if (Flags.enableFlexibleTwoAppSplit()) { + mSplitState.populateLayouts( + mRootBounds, mDividerSize, mIsLeftRightSplit, mPinnedTaskbarInsets.toRect()); + } + + // Get new DividerSnapAlgorithm + + final Rect insets = getDisplayStableInsets(mContext); // Make split axis insets value same as the larger one to avoid bounds1 and bounds2 // have difference for avoiding size-compat mode when switching unresizable apps in @@ -835,10 +844,10 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange insets.set(insets.left, largerInsets, insets.right, largerInsets); } - return new DividerSnapAlgorithm( - context.getResources(), - rootBounds.width(), - rootBounds.height(), + mDividerSnapAlgorithm = new DividerSnapAlgorithm( + mContext.getResources(), + mRootBounds.width(), + mRootBounds.height(), mDividerSize, mIsLeftRightSplit, insets, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitSpec.java new file mode 100644 index 000000000000..9c951bd89876 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitSpec.java @@ -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.wm.shell.common.split; + +import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_10_90; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_33_66; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_66_33; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_90_10; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_3_10_45_45; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_3_33_33_33; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_3_45_45_10; +import static com.android.wm.shell.shared.split.SplitScreenConstants.stateToString; + +import android.graphics.Rect; +import android.graphics.RectF; +import android.util.Log; + +import com.android.wm.shell.shared.split.SplitScreenConstants.SplitScreenState; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A reference class that stores the split layouts available in this device/orientation. Layouts are + * available as lists of RectFs, where each RectF represents the bounds of an app. + */ +public class SplitSpec { + private static final String TAG = "SplitSpec"; + private static final boolean DEBUG = true; + + /** A split ratio used on larger screens, where we can fit both apps onscreen. */ + public static final float ONSCREEN_ONLY_ASYMMETRIC_RATIO = 0.33f; + /** A split ratio used on smaller screens, where we place one app mostly offscreen. */ + public static final float OFFSCREEN_ASYMMETRIC_RATIO = 0.1f; + /** A 50-50 split ratio. */ + public static final float MIDDLE_RATIO = 0.5f; + + private final boolean mIsLeftRightSplit; + /** The usable display area, considering insets that affect split bounds. */ + private final RectF mUsableArea; + /** Half the divider size. */ + private final float mHalfDiv; + + /** A large map that stores all valid split layouts. */ + private final Map<Integer, List<RectF>> mLayouts = new HashMap<>(); + + /** Constructor; initializes the layout map. */ + public SplitSpec(Rect displayBounds, int dividerSize, boolean isLeftRightSplit, + Rect pinnedTaskbarInsets) { + mIsLeftRightSplit = isLeftRightSplit; + mUsableArea = new RectF(displayBounds); + mUsableArea.left += pinnedTaskbarInsets.left; + mUsableArea.top += pinnedTaskbarInsets.top; + mUsableArea.right -= pinnedTaskbarInsets.right; + mUsableArea.bottom -= pinnedTaskbarInsets.bottom; + mHalfDiv = dividerSize / 2f; + + // The "start" position, considering insets. + float s = isLeftRightSplit ? mUsableArea.left : mUsableArea.top; + // The "end" position, considering insets. + float e = isLeftRightSplit ? mUsableArea.right : mUsableArea.bottom; + // The "length" of the usable display (width or height). Apps are arranged along this axis. + float l = e - s; + float divPos; + float divPos2; + + // SNAP_TO_2_10_90 + divPos = s + (l * OFFSCREEN_ASYMMETRIC_RATIO); + createAppLayout(SNAP_TO_2_10_90, divPos); + + // SNAP_TO_2_33_66 + divPos = s + (l * ONSCREEN_ONLY_ASYMMETRIC_RATIO); + createAppLayout(SNAP_TO_2_33_66, divPos); + + // SNAP_TO_2_50_50 + divPos = s + (l * MIDDLE_RATIO); + createAppLayout(SNAP_TO_2_50_50, divPos); + + // SNAP_TO_2_66_33 + divPos = s + (l * (1 - ONSCREEN_ONLY_ASYMMETRIC_RATIO)); + createAppLayout(SNAP_TO_2_66_33, divPos); + + // SNAP_TO_2_90_10 + divPos = s + (l * (1 - OFFSCREEN_ASYMMETRIC_RATIO)); + createAppLayout(SNAP_TO_2_90_10, divPos); + + // SNAP_TO_3_10_45_45 + divPos = s + (l * OFFSCREEN_ASYMMETRIC_RATIO); + divPos2 = e - ((l * (1 - OFFSCREEN_ASYMMETRIC_RATIO)) / 2f); + createAppLayout(SNAP_TO_3_10_45_45, divPos, divPos2); + + // SNAP_TO_3_33_33_33 + divPos = s + (l * ONSCREEN_ONLY_ASYMMETRIC_RATIO); + divPos2 = e - (l * ONSCREEN_ONLY_ASYMMETRIC_RATIO); + createAppLayout(SNAP_TO_3_33_33_33, divPos, divPos2); + + // SNAP_TO_3_45_45_10 + divPos = s + ((l * (1 - OFFSCREEN_ASYMMETRIC_RATIO)) / 2f); + divPos2 = e - (l * OFFSCREEN_ASYMMETRIC_RATIO); + createAppLayout(SNAP_TO_3_45_45_10, divPos, divPos2); + + if (DEBUG) { + dump(); + } + } + + /** + * Creates a two-app layout and enters it into the layout map. + * @param divPos The position of the divider. + */ + private void createAppLayout(@SplitScreenState int state, float divPos) { + List<RectF> list = new ArrayList<>(); + RectF rect1 = new RectF(mUsableArea); + RectF rect2 = new RectF(mUsableArea); + if (mIsLeftRightSplit) { + rect1.right = divPos - mHalfDiv; + rect2.left = divPos + mHalfDiv; + } else { + rect1.top = divPos - mHalfDiv; + rect2.bottom = divPos + mHalfDiv; + } + list.add(rect1); + list.add(rect2); + mLayouts.put(state, list); + } + + /** + * Creates a three-app layout and enters it into the layout map. + * @param divPos1 The position of the first divider. + * @param divPos2 The position of the second divider. + */ + private void createAppLayout(@SplitScreenState int state, float divPos1, float divPos2) { + List<RectF> list = new ArrayList<>(); + RectF rect1 = new RectF(mUsableArea); + RectF rect2 = new RectF(mUsableArea); + RectF rect3 = new RectF(mUsableArea); + if (mIsLeftRightSplit) { + rect1.right = divPos1 - mHalfDiv; + rect2.left = divPos1 + mHalfDiv; + rect2.right = divPos2 - mHalfDiv; + rect3.left = divPos2 + mHalfDiv; + } else { + rect1.right = divPos1 - mHalfDiv; + rect2.left = divPos1 + mHalfDiv; + rect3.right = divPos2 - mHalfDiv; + rect3.left = divPos2 + mHalfDiv; + } + list.add(rect1); + list.add(rect2); + list.add(rect3); + mLayouts.put(state, list); + } + + /** Logs all calculated layouts */ + private void dump() { + mLayouts.forEach((k, v) -> { + Log.d(TAG, stateToString(k)); + v.forEach(rect -> Log.d(TAG, " - " + rect.toShortString())); + }); + } + + /** Returns the layout associated with a given split state. */ + List<RectF> getSpec(@SplitScreenState int state) { + return mLayouts.get(state); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java index 71758e0d2159..d1d133d16ae4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java @@ -19,11 +19,17 @@ package com.android.wm.shell.common.split; import static com.android.wm.shell.shared.split.SplitScreenConstants.NOT_IN_SPLIT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SplitScreenState; +import android.graphics.Rect; +import android.graphics.RectF; + +import java.util.List; + /** * A class that manages the "state" of split screen. See {@link SplitScreenState} for definitions. */ public class SplitState { private @SplitScreenState int mState = NOT_IN_SPLIT; + private SplitSpec mSplitSpec; /** Updates the current state of split screen on this device. */ public void set(@SplitScreenState int newState) { @@ -39,4 +45,16 @@ public class SplitState { public void exit() { set(NOT_IN_SPLIT); } + + /** Refresh the valid layouts for this display/orientation. */ + public void populateLayouts(Rect displayBounds, int dividerSize, boolean isLeftRightSplit, + Rect pinnedTaskbarInsets) { + mSplitSpec = + new SplitSpec(displayBounds, dividerSize, isLeftRightSplit, pinnedTaskbarInsets); + } + + /** Returns the layout associated with a given split state. */ + public List<RectF> getLayout(@SplitScreenState int state) { + return mSplitSpec.getSpec(state); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt index 8e2a412764eb..536dc2a58534 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt @@ -74,13 +74,11 @@ class DesktopImmersiveController( { SurfaceControl.Transaction() }, ) - @VisibleForTesting var state: TransitionState? = null - - @VisibleForTesting val pendingExternalExitTransitions = mutableListOf<ExternalPendingExit>() + @VisibleForTesting val pendingImmersiveTransitions = mutableListOf<PendingTransition>() /** Whether there is an immersive transition that hasn't completed yet. */ private val inProgress: Boolean - get() = state != null || pendingExternalExitTransitions.isNotEmpty() + get() = pendingImmersiveTransitions.isNotEmpty() private val rectEvaluator = RectEvaluator() @@ -101,20 +99,19 @@ class DesktopImmersiveController( if (inProgress) { logV( "Cannot start entry because transition(s) already in progress: %s", - getRunningTransitions(), + pendingImmersiveTransitions, ) return } val wct = WindowContainerTransaction().apply { setBounds(taskInfo.token, Rect()) } logV("Moving task ${taskInfo.taskId} into immersive mode") val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ this) - state = - TransitionState( - transition = transition, - displayId = taskInfo.displayId, - taskId = taskInfo.taskId, - direction = Direction.ENTER, - ) + addPendingImmersiveTransition( + taskId = taskInfo.taskId, + displayId = taskInfo.displayId, + direction = Direction.ENTER, + transition = transition, + ) } /** Starts a transition to move an immersive task out of immersive. */ @@ -123,7 +120,7 @@ class DesktopImmersiveController( if (inProgress) { logV( "Cannot start exit because transition(s) already in progress: %s", - getRunningTransitions(), + pendingImmersiveTransitions, ) return } @@ -134,13 +131,12 @@ class DesktopImmersiveController( } logV("Moving task %d out of immersive mode, reason: %s", taskInfo.taskId, reason) val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ this) - state = - TransitionState( - transition = transition, - displayId = taskInfo.displayId, - taskId = taskInfo.taskId, - direction = Direction.EXIT, - ) + addPendingImmersiveTransition( + taskId = taskInfo.taskId, + displayId = taskInfo.displayId, + direction = Direction.EXIT, + transition = transition, + ) } /** @@ -194,7 +190,13 @@ class DesktopImmersiveController( return ExitResult.Exit( exitingTask = immersiveTask, runOnTransitionStart = { transition -> - addPendingImmersiveExit(immersiveTask, displayId, transition) + addPendingImmersiveTransition( + taskId = immersiveTask, + displayId = displayId, + direction = Direction.EXIT, + transition = transition, + animate = false, + ) }, ) } @@ -220,10 +222,12 @@ class DesktopImmersiveController( return ExitResult.Exit( exitingTask = taskInfo.taskId, runOnTransitionStart = { transition -> - addPendingImmersiveExit( + addPendingImmersiveTransition( taskId = taskInfo.taskId, displayId = taskInfo.displayId, + direction = Direction.EXIT, transition = transition, + animate = false, ) }, ) @@ -233,14 +237,26 @@ class DesktopImmersiveController( /** Whether the [change] in the [transition] is a known immersive change. */ fun isImmersiveChange(transition: IBinder, change: TransitionInfo.Change): Boolean { - return pendingExternalExitTransitions.any { + return pendingImmersiveTransitions.any { it.transition == transition && it.taskId == change.taskInfo?.taskId } } - private fun addPendingImmersiveExit(taskId: Int, displayId: Int, transition: IBinder) { - pendingExternalExitTransitions.add( - ExternalPendingExit(taskId = taskId, displayId = displayId, transition = transition) + private fun addPendingImmersiveTransition( + taskId: Int, + displayId: Int, + direction: Direction, + transition: IBinder, + animate: Boolean = true, + ) { + pendingImmersiveTransitions.add( + PendingTransition( + taskId = taskId, + displayId = displayId, + direction = direction, + transition = transition, + animate = animate, + ) ) } @@ -251,19 +267,17 @@ class DesktopImmersiveController( finishTransaction: SurfaceControl.Transaction, finishCallback: Transitions.TransitionFinishCallback, ): Boolean { - val state = requireState() - check(state.transition == transition) { - "Transition $transition did not match expected state=$state" - } + val immersiveTransition = getImmersiveTransition(transition) ?: return false + if (!immersiveTransition.animate) return false logD("startAnimation transition=%s", transition) animateResize( - targetTaskId = state.taskId, + targetTaskId = immersiveTransition.taskId, info = info, startTransaction = startTransaction, finishTransaction = finishTransaction, finishCallback = { finishCallback.onTransitionFinished(/* wct= */ null) - clearState() + pendingImmersiveTransitions.remove(immersiveTransition) }, ) return true @@ -346,18 +360,6 @@ class DesktopImmersiveController( request: TransitionRequestInfo, ): WindowContainerTransaction? = null - override fun onTransitionConsumed( - transition: IBinder, - aborted: Boolean, - finishTransaction: SurfaceControl.Transaction?, - ) { - val state = this.state ?: return - if (transition == state.transition && aborted) { - clearState() - } - super.onTransitionConsumed(transition, aborted, finishTransaction) - } - /** * Called when any transition in the system is ready to play. This is needed to update the * repository state before window decorations are drawn (which happens immediately after @@ -371,67 +373,42 @@ class DesktopImmersiveController( finishTransaction: SurfaceControl.Transaction, ) { val desktopRepository: DesktopRepository = desktopUserRepositories.current - // Check if this is a pending external exit transition. - val pendingExit = - pendingExternalExitTransitions.firstOrNull { pendingExit -> - pendingExit.transition == transition - } - if (pendingExit != null) { - if (info.hasTaskChange(taskId = pendingExit.taskId)) { - if (desktopRepository.isTaskInFullImmersiveState(pendingExit.taskId)) { - logV("Pending external exit for task#%d verified", pendingExit.taskId) - desktopRepository.setTaskInFullImmersiveState( - displayId = pendingExit.displayId, - taskId = pendingExit.taskId, - immersive = false, - ) - if (Flags.enableRestoreToPreviousSizeFromDesktopImmersive()) { - desktopRepository.removeBoundsBeforeFullImmersive(pendingExit.taskId) - } - } - } - return - } + val pendingTransition = getImmersiveTransition(transition) - // Check if this is a direct immersive enter/exit transition. - if (transition == state?.transition) { - val state = requireState() - val immersiveChange = - info.changes.firstOrNull { c -> c.taskInfo?.taskId == state.taskId } + if (pendingTransition != null) { + val taskId = pendingTransition.taskId + val immersiveChange = info.getTaskChange(taskId = taskId) if (immersiveChange == null) { logV( - "Direct move for task#%d in %s direction missing immersive change.", - state.taskId, - state.direction, + "Transition for task#%d in %s direction missing immersive change.", + taskId, + pendingTransition.direction, ) return } - val startBounds = immersiveChange.startAbsBounds - logV("Direct move for task#%d in %s direction verified", state.taskId, state.direction) - - when (state.direction) { - Direction.ENTER -> { - desktopRepository.setTaskInFullImmersiveState( - displayId = state.displayId, - taskId = state.taskId, - immersive = true, - ) - if (Flags.enableRestoreToPreviousSizeFromDesktopImmersive()) { - desktopRepository.saveBoundsBeforeFullImmersive(state.taskId, startBounds) + logV( + "Immersive transition for task#%d in %s direction verified", + taskId, + pendingTransition.direction, + ) + desktopRepository.setTaskInFullImmersiveState( + displayId = pendingTransition.displayId, + taskId = taskId, + immersive = pendingTransition.direction == Direction.ENTER, + ) + if (Flags.enableRestoreToPreviousSizeFromDesktopImmersive()) { + when (pendingTransition.direction) { + Direction.EXIT -> { + desktopRepository.removeBoundsBeforeFullImmersive(taskId) } - } - Direction.EXIT -> { - desktopRepository.setTaskInFullImmersiveState( - displayId = state.displayId, - taskId = state.taskId, - immersive = false, - ) - if (Flags.enableRestoreToPreviousSizeFromDesktopImmersive()) { - desktopRepository.removeBoundsBeforeFullImmersive(state.taskId) + Direction.ENTER -> { + desktopRepository.saveBoundsBeforeFullImmersive( + taskId, + immersiveChange.startAbsBounds, + ) } } } - return } // Check if this is an untracked exit transition, like display rotation. @@ -450,35 +427,31 @@ class DesktopImmersiveController( } override fun onTransitionMerged(merged: IBinder, playing: IBinder) { - val pendingExit = - pendingExternalExitTransitions.firstOrNull { pendingExit -> - pendingExit.transition == merged + val pendingTransition = + pendingImmersiveTransitions.firstOrNull { pendingTransition -> + pendingTransition.transition == merged } - if (pendingExit != null) { + if (pendingTransition != null) { logV( - "Pending exit transition %s for task#%s merged into %s", + "Pending transition %s for task#%s merged into %s", merged, - pendingExit.taskId, + pendingTransition.taskId, playing, ) - pendingExit.transition = playing + pendingTransition.transition = playing } } override fun onTransitionFinished(transition: IBinder, aborted: Boolean) { - val pendingExit = - pendingExternalExitTransitions.firstOrNull { pendingExit -> - pendingExit.transition == transition - } - if (pendingExit != null) { - logV("Pending exit transition %s for task#%s finished", transition, pendingExit) - pendingExternalExitTransitions.remove(pendingExit) + val pendingTransition = getImmersiveTransition(transition) + if (pendingTransition != null) { + logV("Pending exit transition %s for task#%s finished", transition, pendingTransition) + pendingImmersiveTransitions.remove(pendingTransition) } } - private fun clearState() { - state = null - } + private fun getImmersiveTransition(transition: IBinder) = + pendingImmersiveTransitions.firstOrNull { it.transition == transition } private fun getExitDestinationBounds(taskInfo: RunningTaskInfo): Rect { val displayLayout = @@ -496,24 +469,13 @@ class DesktopImmersiveController( } } - private fun requireState(): TransitionState = - state ?: error("Expected non-null transition state") - - private fun getRunningTransitions(): List<IBinder> { - val running = mutableListOf<IBinder>() - state?.let { running.add(it.transition) } - pendingExternalExitTransitions.forEach { running.add(it.transition) } - return running - } - - private fun TransitionInfo.hasTaskChange(taskId: Int): Boolean = - changes.any { c -> c.taskInfo?.taskId == taskId } + private fun TransitionInfo.getTaskChange(taskId: Int): TransitionInfo.Change? = + changes.firstOrNull { c -> c.taskInfo?.taskId == taskId } private fun dump(pw: PrintWriter, prefix: String) { val innerPrefix = "$prefix " pw.println("${prefix}DesktopImmersiveController") - pw.println(innerPrefix + "state=" + state) - pw.println(innerPrefix + "pendingExternalExitTransitions=" + pendingExternalExitTransitions) + pw.println(innerPrefix + "pendingImmersiveTransitions=" + pendingImmersiveTransitions) } /** The state of the currently running transition. */ @@ -526,12 +488,22 @@ class DesktopImmersiveController( ) /** - * Tracks state of a transition involving an immersive exit that is external to this class' own - * transitions. This usually means transitions that exit immersive mode as a side-effect and not - * the primary action (for example, minimizing the immersive task or launching a new task on top - * of the immersive task). + * Tracks state of a transition involving an immersive enter or exit. This includes both + * transitions that should and should not be animated by this handler. + * + * @param taskId of the task that should enter/exit immersive mode + * @param displayId of the display that should enter/exit immersive mode + * @param direction of the immersive transition + * @param transition that will apply this transaction + * @param animate whether transition should be animated by this handler */ - data class ExternalPendingExit(val taskId: Int, val displayId: Int, var transition: IBinder) + data class PendingTransition( + val taskId: Int, + val displayId: Int, + val direction: Direction, + var transition: IBinder, + val animate: Boolean, + ) /** The result of an external exit request. */ sealed class ExitResult { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt index 14623cf9e703..606a729305b4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt @@ -235,8 +235,7 @@ fun isTaskWidthOrHeightEqual(taskBounds: Rect, stableBounds: Rect): Boolean { /** Returns true if task bound is equal to stable bounds else returns false. */ fun isTaskBoundsEqual(taskBounds: Rect, stableBounds: Rect): Boolean { - return taskBounds.width() == stableBounds.width() && - taskBounds.height() == stableBounds.height() + return taskBounds == stableBounds } /** 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 ba724edb9747..e93ca9e0bd72 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 @@ -1969,32 +1969,32 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } - int currentSnapPosition = mSplitLayout.calculateCurrentSnapPosition(); - - if (Flags.enableFlexibleTwoAppSplit()) { - // Split screen can be laid out in such a way that some of the apps are offscreen. - // For the purposes of passing SplitBounds up to launcher (for use in thumbnails - // etc.), we crop the bounds down to the screen size. - topLeftBounds.left = - Math.max(topLeftBounds.left, 0); - topLeftBounds.top = - Math.max(topLeftBounds.top, 0); - bottomRightBounds.right = - Math.min(bottomRightBounds.right, mSplitLayout.getDisplayWidth()); - bottomRightBounds.top = - Math.min(bottomRightBounds.top, mSplitLayout.getDisplayHeight()); - - // TODO (b/349828130): Can change to getState() fully after brief soak time. - if (mSplitState.get() != currentSnapPosition) { - Log.wtf(TAG, "SplitState is " + mSplitState.get() - + ", expected " + currentSnapPosition); - currentSnapPosition = mSplitState.get(); + // If all stages are filled, create new SplitBounds and update Recents. + if (mainStageTopTaskId != INVALID_TASK_ID && sideStageTopTaskId != INVALID_TASK_ID) { + int currentSnapPosition = mSplitLayout.calculateCurrentSnapPosition(); + if (Flags.enableFlexibleTwoAppSplit()) { + // Split screen can be laid out in such a way that some of the apps are + // offscreen. For the purposes of passing SplitBounds up to launcher (for use in + // thumbnails etc.), we crop the bounds down to the screen size. + topLeftBounds.left = + Math.max(topLeftBounds.left, 0); + topLeftBounds.top = + Math.max(topLeftBounds.top, 0); + bottomRightBounds.right = + Math.min(bottomRightBounds.right, mSplitLayout.getDisplayWidth()); + bottomRightBounds.top = + Math.min(bottomRightBounds.top, mSplitLayout.getDisplayHeight()); + + // TODO (b/349828130): Can change to getState() fully after brief soak time. + if (mSplitState.get() != currentSnapPosition) { + Log.wtf(TAG, "SplitState is " + mSplitState.get() + + ", expected " + currentSnapPosition); + currentSnapPosition = mSplitState.get(); + } } - } + SplitBounds splitBounds = new SplitBounds(topLeftBounds, bottomRightBounds, + leftTopTaskId, rightBottomTaskId, currentSnapPosition); - SplitBounds splitBounds = new SplitBounds(topLeftBounds, bottomRightBounds, - leftTopTaskId, rightBottomTaskId, currentSnapPosition); - if (mainStageTopTaskId != INVALID_TASK_ID && sideStageTopTaskId != INVALID_TASK_ID) { // Update the pair for the top tasks boolean added = recentTasks.addSplitPair(mainStageTopTaskId, sideStageTopTaskId, splitBounds); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt index 3fa8df40dfef..a92100410d3d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt @@ -95,13 +95,16 @@ class StageOrderOperator ( */ fun onEnteringSplit(@SnapPosition goingToLayout: Int) { if (goingToLayout == currentLayout) { - // Add protolog here. Return for now, but maybe we want to handle swap case, TBD + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, + "Entering Split requested same layout split is in: %d", goingToLayout) return } val freeStages: List<StageTaskListener> = allStages.filterNot { activeStages.contains(it) } when(goingToLayout) { - SplitScreenConstants.SNAP_TO_2_50_50 -> { + SplitScreenConstants.SNAP_TO_2_50_50, + SplitScreenConstants.SNAP_TO_2_33_66, + SplitScreenConstants.SNAP_TO_2_66_33 -> { if (activeStages.size < 2) { // take from allStages and add into activeStages for (i in 0 until (2 - activeStages.size)) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 611f3e0ac5e8..a7d6301ecf06 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -1076,9 +1076,11 @@ public class Transitions implements RemoteCallable<Transitions>, @Nullable TransitionHandler skip ) { for (int i = mHandlers.size() - 1; i >= 0; --i) { - if (mHandlers.get(i) == skip) continue; - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try handler %s", - mHandlers.get(i)); + if (mHandlers.get(i) == skip) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " skip handler %s", + mHandlers.get(i)); + continue; + } boolean consumed = mHandlers.get(i).startAnimation(transition, info, startT, finishT, finishCB); if (consumed) { 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 6562f38e724d..01319fb8c713 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 @@ -98,6 +98,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.desktopmode.CaptionState; import com.android.wm.shell.desktopmode.DesktopModeEventLogger; +import com.android.wm.shell.desktopmode.DesktopModeUtils; import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; @@ -522,7 +523,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } else { mWindowDecorViewHolder.bindData(new AppHeaderViewHolder.HeaderData( mTaskInfo, - TaskInfoKt.getRequestingImmersive(mTaskInfo), + DesktopModeUtils.isTaskMaximized(mTaskInfo, mDisplayController), inFullImmersive, hasGlobalFocus, /* maximizeHoverEnabled= */ canOpenMaximizeMenu( @@ -556,10 +557,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin @Nullable private Intent getBrowserLink() { final Uri browserLink; - if (isCapturedLinkAvailable()) { - browserLink = mCapturedLink.mUri; - } else if (mWebUri != null) { + if (mWebUri != null) { browserLink = mWebUri; + } else if (isCapturedLinkAvailable()) { + browserLink = mCapturedLink.mUri; } else { browserLink = mGenericLink; } @@ -1316,7 +1317,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin */ @VisibleForTesting void onAssistContentReceived(@Nullable AssistContent assistContent) { - mWebUri = assistContent == null ? null : assistContent.getWebUri(); + mWebUri = assistContent == null ? null : AppToWebUtils.getSessionWebUri(assistContent); loadAppInfoIfNeeded(); updateGenericLink(); final boolean supportsMultiInstance = mMultiInstanceHelper @@ -1705,7 +1706,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin .isTaskInFullImmersiveState(mTaskInfo.taskId); asAppHeader(mWindowDecorViewHolder).bindData(new AppHeaderViewHolder.HeaderData( mTaskInfo, - TaskInfoKt.getRequestingImmersive(mTaskInfo), + DesktopModeUtils.isTaskMaximized(mTaskInfo, mDisplayController), inFullImmersive, isFocused(), /* maximizeHoverEnabled= */ canOpenMaximizeMenu(animatingTaskResizeOrReposition))); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt index a47416068c3a..f3a8b206867d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt @@ -79,7 +79,7 @@ class AppHeaderViewHolder( data class HeaderData( val taskInfo: RunningTaskInfo, - val isRequestingImmersive: Boolean, + val isTaskMaximized: Boolean, val inFullImmersiveState: Boolean, val hasGlobalFocus: Boolean, val enableMaximizeLongClick: Boolean, @@ -163,7 +163,7 @@ class AppHeaderViewHolder( override fun bindData(data: HeaderData) { bindData( data.taskInfo, - data.isRequestingImmersive, + data.isTaskMaximized, data.inFullImmersiveState, data.hasGlobalFocus, data.enableMaximizeLongClick @@ -172,7 +172,7 @@ class AppHeaderViewHolder( private fun bindData( taskInfo: RunningTaskInfo, - isRequestingImmersive: Boolean, + isTaskMaximized: Boolean, inFullImmersiveState: Boolean, hasGlobalFocus: Boolean, enableMaximizeLongClick: Boolean, @@ -180,7 +180,7 @@ class AppHeaderViewHolder( if (DesktopModeFlags.ENABLE_THEMED_APP_HEADERS.isTrue()) { bindDataWithThemedHeaders( taskInfo, - isRequestingImmersive, + isTaskMaximized, inFullImmersiveState, hasGlobalFocus, enableMaximizeLongClick, @@ -225,7 +225,7 @@ class AppHeaderViewHolder( private fun bindDataWithThemedHeaders( taskInfo: RunningTaskInfo, - requestingImmersive: Boolean, + isTaskMaximized: Boolean, inFullImmersiveState: Boolean, hasGlobalFocus: Boolean, enableMaximizeLongClick: Boolean, @@ -283,7 +283,7 @@ class AppHeaderViewHolder( drawableInsets = maximizeDrawableInsets ) ) - setIcon(getMaximizeButtonIcon(requestingImmersive, inFullImmersiveState)) + setIcon(getMaximizeButtonIcon(isTaskMaximized, inFullImmersiveState)) } // Close button. closeWindowButton.apply { @@ -358,34 +358,19 @@ class AppHeaderViewHolder( @DrawableRes private fun getMaximizeButtonIcon( - requestingImmersive: Boolean, + isTaskMaximized: Boolean, inFullImmersiveState: Boolean ): Int = when { - shouldShowEnterFullImmersiveIcon(requestingImmersive, inFullImmersiveState) -> { - R.drawable.decor_desktop_mode_immersive_button_dark - } - shouldShowExitFullImmersiveIcon(requestingImmersive, inFullImmersiveState) -> { - R.drawable.decor_desktop_mode_immersive_exit_button_dark + shouldShowExitFullImmersiveOrMaximizeIcon(isTaskMaximized, inFullImmersiveState) -> { + R.drawable.decor_desktop_mode_immersive_or_maximize_exit_button_dark } else -> R.drawable.decor_desktop_mode_maximize_button_dark } - private fun shouldShowEnterFullImmersiveIcon( - requestingImmersive: Boolean, - inFullImmersiveState: Boolean - ): Boolean = Flags.enableFullyImmersiveInDesktop() - && requestingImmersive && !inFullImmersiveState - - private fun shouldShowExitFullImmersiveIcon( - requestingImmersive: Boolean, - inFullImmersiveState: Boolean - ): Boolean = isInFullImmersiveStateAndRequesting(requestingImmersive, inFullImmersiveState) - - private fun isInFullImmersiveStateAndRequesting( - requestingImmersive: Boolean, + private fun shouldShowExitFullImmersiveOrMaximizeIcon( + isTaskMaximized: Boolean, inFullImmersiveState: Boolean - ): Boolean = Flags.enableFullyImmersiveInDesktop() - && requestingImmersive && inFullImmersiveState + ): Boolean = (Flags.enableFullyImmersiveInDesktop() && inFullImmersiveState) || isTaskMaximized private fun getHeaderStyle(header: Header): HeaderStyle { return HeaderStyle( diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index a2afd2c92d3d..47ee7bb20199 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -718,7 +718,7 @@ public class BackAnimationControllerTest extends ShellTestCase { tInfo = createTransitionInfo(TRANSIT_PREPARE_BACK_NAVIGATION, open); callback = mock(Transitions.TransitionFinishCallback.class); mBackTransitionHandler.startAnimation(mockBinder, tInfo, st, ft, callback); - verify(mBackTransitionHandler).handlePrepareTransition( + verify(mBackTransitionHandler).handlePrepareTransition(eq(mockBinder), eq(tInfo), eq(st), eq(ft), eq(callback)); mBackTransitionHandler.mCloseTransitionRequested = true; TransitionInfo tInfo2 = createTransitionInfo(TRANSIT_CLOSE, close); @@ -750,7 +750,7 @@ public class BackAnimationControllerTest extends ShellTestCase { null /* remoteTransition */); mBackTransitionHandler.handleRequest(mockBinder, requestInfo); mBackTransitionHandler.startAnimation(mockBinder, tInfo, st, ft, callback); - verify(mBackTransitionHandler).handlePrepareTransition( + verify(mBackTransitionHandler).handlePrepareTransition(eq(mockBinder), eq(tInfo), eq(st), eq(ft), eq(callback)); mBackTransitionHandler.onAnimationFinished(); @@ -801,7 +801,7 @@ public class BackAnimationControllerTest extends ShellTestCase { canHandle = mBackTransitionHandler.startAnimation(mockBinder, prepareInfo, st, ft, callback2); assertTrue("Handle prepare transition" , canHandle); - verify(mBackTransitionHandler).handlePrepareTransition( + verify(mBackTransitionHandler).handlePrepareTransition(eq(mockBinder), eq(prepareInfo), eq(st), eq(ft), eq(callback2)); final TransitionInfo closeInfo = createTransitionInfo(TRANSIT_CLOSE, close); Transitions.TransitionFinishCallback mergeCallback = @@ -819,7 +819,7 @@ public class BackAnimationControllerTest extends ShellTestCase { canHandle = mBackTransitionHandler.startAnimation( mockBinder, prepareInfo, st, ft, callback3); assertTrue("Handle prepare transition" , canHandle); - verify(mBackTransitionHandler).handlePrepareTransition( + verify(mBackTransitionHandler).handlePrepareTransition(eq(mockBinder), eq(prepareInfo), eq(st), eq(ft), eq(callback3)); final TransitionInfo.Change open2 = createAppChange( openTaskId2, TRANSIT_OPEN, FLAG_MOVED_TO_TOP); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerRobotTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerRobotTest.kt index 95a0c82c76df..88cc981dd30c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerRobotTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerRobotTest.kt @@ -16,10 +16,8 @@ package com.android.wm.shell.compatui.letterbox -import android.content.Context import android.graphics.Rect import android.view.SurfaceControl -import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn import com.android.wm.shell.compatui.letterbox.LetterboxMatchers.asAnyMode import org.mockito.kotlin.any import org.mockito.kotlin.clearInvocations @@ -31,10 +29,7 @@ import org.mockito.kotlin.verify /** * Robot to test [LetterboxController] implementations. */ -open class LetterboxControllerRobotTest( - ctx: Context, - controllerBuilder: (LetterboxSurfaceBuilder) -> LetterboxController -) { +abstract class LetterboxControllerRobotTest { companion object { @JvmStatic @@ -44,21 +39,21 @@ open class LetterboxControllerRobotTest( private val TASK_ID = 20 } - private val letterboxConfiguration: LetterboxConfiguration - private val surfaceBuilder: LetterboxSurfaceBuilder - private val letterboxController: LetterboxController - private val transaction: SurfaceControl.Transaction - private val parentLeash: SurfaceControl + lateinit var letterboxController: LetterboxController + val transaction: SurfaceControl.Transaction + val parentLeash: SurfaceControl init { - letterboxConfiguration = LetterboxConfiguration(ctx) - surfaceBuilder = LetterboxSurfaceBuilder(letterboxConfiguration) - letterboxController = controllerBuilder(surfaceBuilder) transaction = getTransactionMock() parentLeash = mock<SurfaceControl>() - spyOn(surfaceBuilder) } + fun initController() { + letterboxController = buildController() + } + + abstract fun buildController(): LetterboxController + fun sendCreateSurfaceRequest( displayId: Int = DISPLAY_ID, taskId: Int = TASK_ID @@ -102,16 +97,6 @@ open class LetterboxControllerRobotTest( letterboxController.dump() } - fun checkSurfaceBuilderInvoked(times: Int = 1, name: String = "", callSite: String = "") { - verify(surfaceBuilder, times(times)).createSurface( - eq(transaction), - eq(parentLeash), - name.asAnyMode(), - callSite.asAnyMode(), - any() - ) - } - fun checkTransactionRemovedInvoked(times: Int = 1) { verify(transaction, times(times)).remove(any()) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxUtilsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxUtilsTest.kt index 667511288bfa..dd4cb1185b31 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxUtilsTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxUtilsTest.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.compatui.letterbox -import android.content.Context import android.graphics.Rect import android.testing.AndroidTestingRunner import android.view.SurfaceControl @@ -44,24 +43,14 @@ import org.mockito.kotlin.verify @SmallTest class LetterboxUtilsTest : ShellTestCase() { - val firstLetterboxController = mock<LetterboxController>() - val secondLetterboxController = mock<LetterboxController>() - val thirdLetterboxController = mock<LetterboxController>() - - private val letterboxControllerBuilder: (LetterboxSurfaceBuilder) -> LetterboxController = - { _ -> - firstLetterboxController.append(secondLetterboxController) - .append(thirdLetterboxController) - } - @Test fun `Appended LetterboxController invoked creation on all the controllers`() { runTestScenario { r -> r.sendCreateSurfaceRequest() - r.verifyCreateSurfaceInvokedWithRequest(target = firstLetterboxController) - r.verifyCreateSurfaceInvokedWithRequest(target = secondLetterboxController) - r.verifyCreateSurfaceInvokedWithRequest(target = thirdLetterboxController) + r.verifyCreateSurfaceInvokedWithRequest(target = r.firstLetterboxController) + r.verifyCreateSurfaceInvokedWithRequest(target = r.secondLetterboxController) + r.verifyCreateSurfaceInvokedWithRequest(target = r.thirdLetterboxController) } } @@ -69,9 +58,9 @@ class LetterboxUtilsTest : ShellTestCase() { fun `Appended LetterboxController invoked destroy on all the controllers`() { runTestScenario { r -> r.sendDestroySurfaceRequest() - r.verifyDestroySurfaceInvokedWithRequest(target = firstLetterboxController) - r.verifyDestroySurfaceInvokedWithRequest(target = secondLetterboxController) - r.verifyDestroySurfaceInvokedWithRequest(target = thirdLetterboxController) + r.verifyDestroySurfaceInvokedWithRequest(target = r.firstLetterboxController) + r.verifyDestroySurfaceInvokedWithRequest(target = r.secondLetterboxController) + r.verifyDestroySurfaceInvokedWithRequest(target = r.thirdLetterboxController) } } @@ -79,9 +68,9 @@ class LetterboxUtilsTest : ShellTestCase() { fun `Appended LetterboxController invoked update visibility on all the controllers`() { runTestScenario { r -> r.sendUpdateSurfaceVisibilityRequest(visible = true) - r.verifyUpdateVisibilitySurfaceInvokedWithRequest(target = firstLetterboxController) - r.verifyUpdateVisibilitySurfaceInvokedWithRequest(target = secondLetterboxController) - r.verifyUpdateVisibilitySurfaceInvokedWithRequest(target = thirdLetterboxController) + r.verifyUpdateVisibilitySurfaceInvokedWithRequest(target = r.firstLetterboxController) + r.verifyUpdateVisibilitySurfaceInvokedWithRequest(target = r.secondLetterboxController) + r.verifyUpdateVisibilitySurfaceInvokedWithRequest(target = r.thirdLetterboxController) } } @@ -89,9 +78,9 @@ class LetterboxUtilsTest : ShellTestCase() { fun `Appended LetterboxController invoked update bounds on all the controllers`() { runTestScenario { r -> r.sendUpdateSurfaceBoundsRequest(taskBounds = Rect(), activityBounds = Rect()) - r.verifyUpdateSurfaceBoundsInvokedWithRequest(target = firstLetterboxController) - r.verifyUpdateSurfaceBoundsInvokedWithRequest(target = secondLetterboxController) - r.verifyUpdateSurfaceBoundsInvokedWithRequest(target = thirdLetterboxController) + r.verifyUpdateSurfaceBoundsInvokedWithRequest(target = r.firstLetterboxController) + r.verifyUpdateSurfaceBoundsInvokedWithRequest(target = r.secondLetterboxController) + r.verifyUpdateSurfaceBoundsInvokedWithRequest(target = r.thirdLetterboxController) } } @@ -99,9 +88,9 @@ class LetterboxUtilsTest : ShellTestCase() { fun `Appended LetterboxController invoked update dump on all the controllers`() { runTestScenario { r -> r.invokeDump() - r.verifyDumpInvoked(target = firstLetterboxController) - r.verifyDumpInvoked(target = secondLetterboxController) - r.verifyDumpInvoked(target = thirdLetterboxController) + r.verifyDumpInvoked(target = r.firstLetterboxController) + r.verifyDumpInvoked(target = r.secondLetterboxController) + r.verifyDumpInvoked(target = r.thirdLetterboxController) } } @@ -138,21 +127,20 @@ class LetterboxUtilsTest : ShellTestCase() { * Runs a test scenario providing a Robot. */ fun runTestScenario(consumer: Consumer<AppendLetterboxControllerRobotTest>) { - val robot = AppendLetterboxControllerRobotTest(mContext, letterboxControllerBuilder) - consumer.accept(robot) + consumer.accept(AppendLetterboxControllerRobotTest().apply { initController() }) } - class AppendLetterboxControllerRobotTest( - ctx: Context, - builder: (LetterboxSurfaceBuilder) -> LetterboxController - ) : LetterboxControllerRobotTest(ctx, builder) { + class AppendLetterboxControllerRobotTest : LetterboxControllerRobotTest() { + + val firstLetterboxController = mock<LetterboxController>() + val secondLetterboxController = mock<LetterboxController>() + val thirdLetterboxController = mock<LetterboxController>() private var testableMap = mutableMapOf<Int, Int>() private var onItemState: Int? = null private var onMissingStateKey: Int? = null private var onMissingStateMap: MutableMap<Int, Int>? = null - private val transaction = getTransactionMock() private val surface = SurfaceControl() fun verifyCreateSurfaceInvokedWithRequest( @@ -230,5 +218,9 @@ class LetterboxUtilsTest : ShellTestCase() { fun verifySetWindowCrop(expectedWidth: Int, expectedHeight: Int) { verify(transaction).setWindowCrop(surface, expectedWidth, expectedHeight) } + + override fun buildController(): LetterboxController = + firstLetterboxController.append(secondLetterboxController) + .append(thirdLetterboxController) } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxControllerTest.kt index e6bff4c1ec15..3b72ff1cac71 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxControllerTest.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.compatui.letterbox -import android.content.Context import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.wm.shell.ShellTestCase @@ -64,65 +63,48 @@ class MixedLetterboxControllerTest : ShellTestCase() { * Runs a test scenario providing a Robot. */ fun runTestScenario(consumer: Consumer<MixedLetterboxControllerRobotTest>) { - val robot = MixedLetterboxControllerRobotTest(mContext, ObjectToTestHolder()) - consumer.accept(robot) + consumer.accept(MixedLetterboxControllerRobotTest().apply { initController() }) } - class MixedLetterboxControllerRobotTest( - ctx: Context, - private val objectToTestHolder: ObjectToTestHolder - ) : LetterboxControllerRobotTest(ctx, objectToTestHolder.controllerBuilder) { + class MixedLetterboxControllerRobotTest : LetterboxControllerRobotTest() { + val singleLetterboxController: SingleSurfaceLetterboxController = + mock<SingleSurfaceLetterboxController>() + val multipleLetterboxController: MultiSurfaceLetterboxController = + mock<MultiSurfaceLetterboxController>() + val controllerStrategy: LetterboxControllerStrategy = mock<LetterboxControllerStrategy>() fun configureStrategyFor(letterboxMode: LetterboxMode) { - doReturn(letterboxMode).`when`(objectToTestHolder.controllerStrategy) - .getLetterboxImplementationMode() + doReturn(letterboxMode).`when`(controllerStrategy).getLetterboxImplementationMode() } fun checkCreateInvokedOnSingleController(times: Int = 1) { - verify( - objectToTestHolder.singleLetterboxController, - times(times) - ).createLetterboxSurface(any(), any(), any()) + verify(singleLetterboxController, times(times)).createLetterboxSurface( + any(), + any(), + any() + ) } fun checkCreateInvokedOnMultiController(times: Int = 1) { - verify( - objectToTestHolder.multipleLetterboxController, - times(times) - ).createLetterboxSurface(any(), any(), any()) + verify(multipleLetterboxController, times(times)).createLetterboxSurface( + any(), + any(), + any() + ) } fun checkDestroyInvokedOnSingleController(times: Int = 1) { - verify( - objectToTestHolder.singleLetterboxController, - times(times) - ).destroyLetterboxSurface(any(), any()) + verify(singleLetterboxController, times(times)).destroyLetterboxSurface(any(), any()) } fun checkDestroyInvokedOnMultiController(times: Int = 1) { - verify( - objectToTestHolder.multipleLetterboxController, - times(times) - ).destroyLetterboxSurface(any(), any()) + verify(multipleLetterboxController, times(times)).destroyLetterboxSurface(any(), any()) } - } - - data class ObjectToTestHolder( - val singleLetterboxController: SingleSurfaceLetterboxController = - mock<SingleSurfaceLetterboxController>(), - val multipleLetterboxController: MultiSurfaceLetterboxController = - mock<MultiSurfaceLetterboxController>(), - val controllerStrategy: LetterboxControllerStrategy = mock<LetterboxControllerStrategy>() - ) { - - private val mixedController = - MixedLetterboxController( - singleLetterboxController, - multipleLetterboxController, - controllerStrategy - ) - val controllerBuilder: (LetterboxSurfaceBuilder) -> LetterboxController = - { _ -> mixedController } + override fun buildController(): LetterboxController = MixedLetterboxController( + singleLetterboxController, + multipleLetterboxController, + controllerStrategy + ) } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxControllerTest.kt index 295d4edf206b..3fd837db478c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxControllerTest.kt @@ -16,13 +16,20 @@ package com.android.wm.shell.compatui.letterbox +import android.content.Context import android.graphics.Rect import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest +import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.compatui.letterbox.LetterboxMatchers.asAnyMode import java.util.function.Consumer import org.junit.Test import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.times +import org.mockito.kotlin.verify /** * Tests for [MultiSurfaceLetterboxController]. @@ -147,9 +154,33 @@ class MultiSurfaceLetterboxControllerTest : ShellTestCase() { /** * Runs a test scenario providing a Robot. */ - fun runTestScenario(consumer: Consumer<LetterboxControllerRobotTest>) { - val robot = - LetterboxControllerRobotTest(mContext, { sb -> MultiSurfaceLetterboxController(sb) }) - consumer.accept(robot) + fun runTestScenario(consumer: Consumer<MultiLetterboxControllerRobotTest>) { + consumer.accept(MultiLetterboxControllerRobotTest(mContext).apply { initController() }) + } + + class MultiLetterboxControllerRobotTest(context: Context) : + LetterboxControllerRobotTest() { + + private val letterboxConfiguration: LetterboxConfiguration + private val surfaceBuilder: LetterboxSurfaceBuilder + + init { + letterboxConfiguration = LetterboxConfiguration(context) + surfaceBuilder = LetterboxSurfaceBuilder(letterboxConfiguration) + spyOn(surfaceBuilder) + } + + override fun buildController(): LetterboxController = + MultiSurfaceLetterboxController(surfaceBuilder) + + fun checkSurfaceBuilderInvoked(times: Int = 1, name: String = "", callSite: String = "") { + verify(surfaceBuilder, times(times)).createSurface( + eq(transaction), + eq(parentLeash), + name.asAnyMode(), + callSite.asAnyMode(), + any() + ) + } } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxControllerTest.kt index 125e700bcd42..e6ffe98875ed 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxControllerTest.kt @@ -16,13 +16,20 @@ package com.android.wm.shell.compatui.letterbox +import android.content.Context import android.graphics.Rect import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest +import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.compatui.letterbox.LetterboxMatchers.asAnyMode import java.util.function.Consumer import org.junit.Test import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.times +import org.mockito.kotlin.verify /** * Tests for [SingleSurfaceLetterboxController]. @@ -120,9 +127,33 @@ class SingleSurfaceLetterboxControllerTest : ShellTestCase() { /** * Runs a test scenario providing a Robot. */ - fun runTestScenario(consumer: Consumer<LetterboxControllerRobotTest>) { - val robot = - LetterboxControllerRobotTest(mContext, { sb -> SingleSurfaceLetterboxController(sb) }) - consumer.accept(robot) + fun runTestScenario(consumer: Consumer<SingleLetterboxControllerRobotTest>) { + consumer.accept(SingleLetterboxControllerRobotTest(mContext).apply { initController() }) + } + + class SingleLetterboxControllerRobotTest(context: Context) : + LetterboxControllerRobotTest() { + + private val letterboxConfiguration: LetterboxConfiguration + private val surfaceBuilder: LetterboxSurfaceBuilder + + init { + letterboxConfiguration = LetterboxConfiguration(context) + surfaceBuilder = LetterboxSurfaceBuilder(letterboxConfiguration) + spyOn(surfaceBuilder) + } + + override fun buildController(): LetterboxController = + SingleSurfaceLetterboxController(surfaceBuilder) + + fun checkSurfaceBuilderInvoked(times: Int = 1, name: String = "", callSite: String = "") { + verify(surfaceBuilder, times(times)).createSurface( + eq(transaction), + eq(parentLeash), + name.asAnyMode(), + callSite.asAnyMode(), + any() + ) + } } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt index db4c7465ae48..447da8799e35 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt @@ -42,6 +42,7 @@ import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout +import com.android.wm.shell.desktopmode.DesktopImmersiveController.Direction import com.android.wm.shell.desktopmode.DesktopImmersiveController.ExitReason.USER_INTERACTION import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask import com.android.wm.shell.sysui.ShellInit @@ -95,6 +96,8 @@ class DesktopImmersiveControllerTest : ShellTestCase() { whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { invocation -> (invocation.getArgument(0) as Rect).set(STABLE_BOUNDS) } + whenever(mockDisplayLayout.width()).thenReturn(DISPLAY_BOUNDS.width()) + whenever(mockDisplayLayout.height()).thenReturn(DISPLAY_BOUNDS.height()) controller = DesktopImmersiveController( shellInit = mock(), transitions = mockTransitions, @@ -277,10 +280,12 @@ class DesktopImmersiveControllerTest : ShellTestCase() { controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION) - assertThat(controller.pendingExternalExitTransitions.any { exit -> - exit.transition == transition && exit.displayId == DEFAULT_DISPLAY - && exit.taskId == task.taskId - }).isTrue() + assertTransitionPending( + transition = transition, + taskId = task.taskId, + direction = Direction.EXIT, + animate = false + ) } @Test @@ -298,10 +303,12 @@ class DesktopImmersiveControllerTest : ShellTestCase() { controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION) - assertThat(controller.pendingExternalExitTransitions.any { exit -> - exit.transition == transition && exit.displayId == DEFAULT_DISPLAY - && exit.taskId == task.taskId - }).isFalse() + assertTransitionNotPending( + transition = transition, + taskId = task.taskId, + direction = Direction.EXIT, + animate = false + ) } @Test @@ -360,10 +367,12 @@ class DesktopImmersiveControllerTest : ShellTestCase() { reason = USER_INTERACTION, ).asExit()?.runOnTransitionStart?.invoke(transition) - assertThat(controller.pendingExternalExitTransitions.any { exit -> - exit.transition == transition && exit.displayId == DEFAULT_DISPLAY - && exit.taskId == task.taskId - }).isFalse() + assertTransitionNotPending( + transition = transition, + taskId = task.taskId, + animate = false, + direction = Direction.EXIT + ) } @Test @@ -416,10 +425,12 @@ class DesktopImmersiveControllerTest : ShellTestCase() { controller.exitImmersiveIfApplicable(wct, task, USER_INTERACTION) .asExit()?.runOnTransitionStart?.invoke(transition) - assertThat(controller.pendingExternalExitTransitions.any { exit -> - exit.transition == transition && exit.displayId == DEFAULT_DISPLAY - && exit.taskId == task.taskId - }).isTrue() + assertTransitionPending( + transition = transition, + taskId = task.taskId, + direction = Direction.EXIT, + animate = false + ) } @Test @@ -481,10 +492,12 @@ class DesktopImmersiveControllerTest : ShellTestCase() { ) controller.onTransitionFinished(transition, aborted = false) - assertThat(controller.pendingExternalExitTransitions.any { exit -> - exit.transition == transition && exit.displayId == DEFAULT_DISPLAY - && exit.taskId == task.taskId - }).isFalse() + assertTransitionNotPending( + transition = transition, + taskId = task.taskId, + direction = Direction.EXIT, + animate = false + ) } @Test @@ -513,14 +526,18 @@ class DesktopImmersiveControllerTest : ShellTestCase() { controller.onTransitionMerged(transition, mergedToTransition) controller.onTransitionFinished(mergedToTransition, aborted = false) - assertThat(controller.pendingExternalExitTransitions.any { exit -> - exit.transition == transition && exit.displayId == DEFAULT_DISPLAY - && exit.taskId == task.taskId - }).isFalse() - assertThat(controller.pendingExternalExitTransitions.any { exit -> - exit.transition == mergedToTransition && exit.displayId == DEFAULT_DISPLAY - && exit.taskId == task.taskId - }).isFalse() + assertTransitionNotPending( + transition = transition, + taskId = task.taskId, + animate = false, + direction = Direction.EXIT + ) + assertTransitionNotPending( + transition = mergedToTransition, + taskId = task.taskId, + animate = false, + direction = Direction.EXIT + ) } @Test @@ -686,7 +703,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) - fun externalAnimateResizeChange_doesNotCleanUpPendingTransitionState() { + fun externalAnimateResizeChange_doesNotRemovePendingTransition() { val task = createFreeformTask() val mockBinder = mock(IBinder::class.java) whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(controller))) @@ -709,12 +726,16 @@ class DesktopImmersiveControllerTest : ShellTestCase() { ) animatorTestRule.advanceTimeBy(DesktopImmersiveController.FULL_IMMERSIVE_ANIM_DURATION_MS) - assertThat(controller.state).isNotNull() + assertTransitionPending( + transition = mockBinder, + taskId = task.taskId, + direction = Direction.EXIT + ) } @Test @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) - fun startAnimation_missingChange_clearsState() { + fun startAnimation_missingChange_removesPendingTransition() { val task = createFreeformTask() val mockBinder = mock(IBinder::class.java) whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(controller))) @@ -735,7 +756,42 @@ class DesktopImmersiveControllerTest : ShellTestCase() { finishCallback = {} ) - assertThat(controller.state).isNull() + assertTransitionNotPending( + transition = mockBinder, + taskId = task.taskId, + direction = Direction.ENTER + ) + } + + private fun assertTransitionPending( + transition: IBinder, + taskId: Int, + direction: Direction, + animate: Boolean = true, + displayId: Int = DEFAULT_DISPLAY + ) { + assertThat(controller.pendingImmersiveTransitions.any { pendingTransition -> + pendingTransition.transition == transition + && pendingTransition.displayId == displayId + && pendingTransition.taskId == taskId + && pendingTransition.animate == animate + && pendingTransition.direction == direction + }).isTrue() + } + + private fun assertTransitionNotPending( + transition: IBinder, + taskId: Int, + direction: Direction, + animate: Boolean = true, + displayId: Int = DEFAULT_DISPLAY + ) { + assertThat(controller.pendingImmersiveTransitions.any { pendingTransition -> + pendingTransition.transition == transition + && pendingTransition.displayId == displayId + && pendingTransition.taskId == taskId + && pendingTransition.direction == direction + }).isFalse() } private fun createTransitionInfo( @@ -768,5 +824,6 @@ class DesktopImmersiveControllerTest : ShellTestCase() { companion object { private val STABLE_BOUNDS = Rect(0, 100, 2000, 1900) + private val DISPLAY_BOUNDS = Rect(0, 0, 2000, 2000) } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageOrderOperatorTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageOrderOperatorTests.kt new file mode 100644 index 000000000000..3b4a86a71d90 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageOrderOperatorTests.kt @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.splitscreen + +import android.view.Display.DEFAULT_DISPLAY +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.launcher3.icons.IconProvider +import com.android.wm.shell.Flags.enableFlexibleSplit +import com.android.wm.shell.ShellTaskOrganizer +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.common.SyncTransactionQueue +import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_33_66 +import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50 +import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_66_33 +import com.android.wm.shell.splitscreen.StageTaskListener.StageListenerCallbacks +import com.android.wm.shell.windowdecor.WindowDecorViewModel +import org.junit.Assume.assumeTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import java.util.Optional + +@SmallTest +@RunWith(AndroidJUnit4::class) +class StageOrderOperatorTests : ShellTestCase() { + + @Mock + lateinit var mTaskOrganizer: ShellTaskOrganizer + @Mock + lateinit var mSyncQueue: SyncTransactionQueue + @Mock + lateinit var stageListenerCallbacks: StageListenerCallbacks + @Mock + lateinit var iconProvider: IconProvider + @Mock + lateinit var windowDecorViewModel: Optional<WindowDecorViewModel> + + lateinit var stageOrderOperator: StageOrderOperator + + @Before + fun setup() { + stageOrderOperator = StageOrderOperator( + context, + mTaskOrganizer, + DEFAULT_DISPLAY, + stageListenerCallbacks, + mSyncQueue, + iconProvider, + windowDecorViewModel, + ) + assert(stageOrderOperator.activeStages.size == 0) + } + + @Test + fun activeStages_2_2app_50_50_split() { + assumeTrue(enableFlexibleSplit()) + + stageOrderOperator.onEnteringSplit(SNAP_TO_2_50_50) + assert(stageOrderOperator.activeStages.size == 2) + } + + @Test + fun activeStages_2_2app_33_66_split() { + assumeTrue(enableFlexibleSplit()) + + stageOrderOperator.onEnteringSplit(SNAP_TO_2_33_66) + assert(stageOrderOperator.activeStages.size == 2) + } + + @Test + fun activeStages_2_2app_66_33_split() { + assumeTrue(enableFlexibleSplit()) + + stageOrderOperator.onEnteringSplit(SNAP_TO_2_66_33) + assert(stageOrderOperator.activeStages.size == 2) + } + + @Test + fun activateSameCountStage_noOp() { + assumeTrue(enableFlexibleSplit()) + + stageOrderOperator.onEnteringSplit(SNAP_TO_2_66_33) + stageOrderOperator.onEnteringSplit(SNAP_TO_2_66_33) + assert(stageOrderOperator.activeStages.size == 2) + } + + @Test + fun deactivate_emptyActiveStages() { + assumeTrue(enableFlexibleSplit()) + + stageOrderOperator.onEnteringSplit(SNAP_TO_2_66_33) + stageOrderOperator.onExitingSplit() + assert(stageOrderOperator.activeStages.isEmpty()) + } + + @Test + fun swapDividerPos_twoApps() { + assumeTrue(enableFlexibleSplit()) + + stageOrderOperator.onEnteringSplit(SNAP_TO_2_66_33) + val stageIndex0: StageTaskListener = stageOrderOperator.activeStages[0] + val stageIndex1: StageTaskListener = stageOrderOperator.activeStages[1] + + stageOrderOperator.onDoubleTappedDivider() + val newStageIndex0: StageTaskListener = stageOrderOperator.activeStages[0] + val newStageIndex1: StageTaskListener = stageOrderOperator.activeStages[1] + + assert(stageIndex0 == newStageIndex1) + assert(stageIndex1 == newStageIndex0) + } + + @Test + fun swapDividerPos_twiceNoOp_twoApps() { + assumeTrue(enableFlexibleSplit()) + + stageOrderOperator.onEnteringSplit(SNAP_TO_2_66_33) + val stageIndex0: StageTaskListener = stageOrderOperator.activeStages[0] + val stageIndex1: StageTaskListener = stageOrderOperator.activeStages[1] + + stageOrderOperator.onDoubleTappedDivider() + stageOrderOperator.onDoubleTappedDivider() + val newStageIndex0: StageTaskListener = stageOrderOperator.activeStages[0] + val newStageIndex1: StageTaskListener = stageOrderOperator.activeStages[1] + + assert(stageIndex0 == newStageIndex0) + assert(stageIndex1 == newStageIndex1) + } +} 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 2ffdcf8d094d..0214da4660ad 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 @@ -388,7 +388,7 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest } @Test - fun testOnDecorMaximizedOrRestored_togglesTaskSize() { + fun testOnDecorMaximizedOrRestored_togglesTaskSize_maximize() { val maxOrRestoreListenerCaptor = forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>> val decor = createOpenTaskDecoration( @@ -409,6 +409,52 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest } @Test + fun testOnDecorMaximizedOrRestored_togglesTaskSize_maximizeFromMaximizedSize() { + val maxOrRestoreListenerCaptor = forClass(Function0::class.java) + as ArgumentCaptor<Function0<Unit>> + val decor = createOpenTaskDecoration( + windowingMode = WINDOWING_MODE_FREEFORM, + onMaxOrRestoreListenerCaptor = maxOrRestoreListenerCaptor + ) + val movedMaximizedBounds = Rect(STABLE_BOUNDS) + movedMaximizedBounds.offset(10, 10) + decor.mTaskInfo.configuration.windowConfiguration.bounds.set(movedMaximizedBounds) + + maxOrRestoreListenerCaptor.value.invoke() + + verify(mockDesktopTasksController).toggleDesktopTaskSize( + decor.mTaskInfo, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.MAXIMIZE, + ToggleTaskSizeInteraction.Source.MAXIMIZE_MENU_TO_MAXIMIZE, + InputMethod.UNKNOWN_INPUT_METHOD + ) + ) + } + + @Test + fun testOnDecorMaximizedOrRestored_togglesTaskSize_restore() { + val maxOrRestoreListenerCaptor = forClass(Function0::class.java) + as ArgumentCaptor<Function0<Unit>> + val decor = createOpenTaskDecoration( + windowingMode = WINDOWING_MODE_FREEFORM, + onMaxOrRestoreListenerCaptor = maxOrRestoreListenerCaptor + ) + decor.mTaskInfo.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS) + + maxOrRestoreListenerCaptor.value.invoke() + + verify(mockDesktopTasksController).toggleDesktopTaskSize( + decor.mTaskInfo, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.RESTORE, + ToggleTaskSizeInteraction.Source.MAXIMIZE_MENU_TO_RESTORE, + InputMethod.UNKNOWN_INPUT_METHOD + ) + ) + } + + @Test fun testOnDecorMaximizedOrRestored_closesMenus() { val maxOrRestoreListenerCaptor = forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>> diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt index 6be234ef5ca6..7a37c5eec604 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt @@ -149,7 +149,6 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { protected val mockCaptionHandleRepository = mock<WindowDecorCaptionHandleRepository>() protected val mockDesktopRepository: DesktopRepository = mock<DesktopRepository>() protected val motionEvent = mock<MotionEvent>() - val displayController = mock<DisplayController>() val displayLayout = mock<DisplayLayout>() protected lateinit var spyContext: TestableContext private lateinit var desktopModeEventLogger: DesktopModeEventLogger @@ -255,7 +254,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { argumentCaptor<DesktopModeKeyguardChangeListener>() verify(mockShellController).addKeyguardChangeListener(keyguardChangedCaptor.capture()) desktopModeOnKeyguardChangedListener = keyguardChangedCaptor.firstValue - whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout) + whenever(mockDisplayController.getDisplayLayout(anyInt())).thenReturn(displayLayout) whenever(displayLayout.getStableBounds(any())).thenAnswer { i -> (i.arguments.first() as Rect).set(STABLE_BOUNDS) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java index 5d5d1f220ae0..db7b1f22768f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java @@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.app.assist.AssistContent.EXTRA_SESSION_TRANSFER_WEB_URI; import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; import static android.view.InsetsSource.FLAG_FORCE_CONSUMING; import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR; @@ -157,6 +158,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { private static final Uri TEST_URI1 = Uri.parse("https://www.google.com/"); private static final Uri TEST_URI2 = Uri.parse("https://docs.google.com/"); private static final Uri TEST_URI3 = Uri.parse("https://slides.google.com/"); + private static final Uri TEST_URI4 = Uri.parse("https://calendar.google.com/"); @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); @@ -1322,11 +1324,11 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB) - public void capturedLink_handleMenuBrowserLinkSetToCapturedLinkIfValid() { + public void capturedLink_CapturedLinkUsedIfValidAndWebUriUnavailable() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); final DesktopModeWindowDecoration decor = createWindowDecoration( - taskInfo, TEST_URI1 /* captured link */, TEST_URI2 /* web uri */, - TEST_URI3 /* generic link */); + taskInfo, TEST_URI1 /* captured link */, null /* web uri */, + null /* session transfer uri */, TEST_URI4 /* generic link */); // Verify handle menu's browser link set as captured link createHandleMenu(decor); @@ -1339,7 +1341,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); final DesktopModeWindowDecoration decor = createWindowDecoration( taskInfo, TEST_URI1 /* captured link */, null /* web uri */, - null /* generic link */); + null /* session transfer uri */, null /* generic link */); final ArgumentCaptor<Function1<Intent, Unit>> openInBrowserCaptor = ArgumentCaptor.forClass(Function1.class); @@ -1373,7 +1375,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); final DesktopModeWindowDecoration decor = createWindowDecoration( taskInfo, TEST_URI1 /* captured link */, null /* web uri */, - null /* generic link */); + null /* session transfer uri */, null /* generic link */); final ArgumentCaptor<Function1<Intent, Unit>> openInBrowserCaptor = ArgumentCaptor.forClass(Function1.class); @@ -1406,7 +1408,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); final DesktopModeWindowDecoration decor = createWindowDecoration( taskInfo, TEST_URI1 /* captured link */, null /* web uri */, - null /* generic link */); + null /* session transfer uri */, null /* generic link */); final ArgumentCaptor<Function1<Intent, Unit>> openInBrowserCaptor = ArgumentCaptor.forClass(Function1.class); createHandleMenu(decor); @@ -1432,11 +1434,23 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB) - public void webUriLink_webUriLinkUsedWhenCapturedLinkUnavailable() { + public void webUriLink_webUriLinkUsedWhenWhenAvailable() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); final DesktopModeWindowDecoration decor = createWindowDecoration( - taskInfo, null /* captured link */, TEST_URI2 /* web uri */, - TEST_URI3 /* generic link */); + taskInfo, TEST_URI1 /* captured link */, TEST_URI2 /* web uri */, + TEST_URI3 /* session transfer uri */, TEST_URI4 /* generic link */); + // Verify handle menu's browser link set as web uri link when captured link is unavailable + createHandleMenu(decor); + verifyHandleMenuCreated(TEST_URI3); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB) + public void webUriLink_webUriLinkUsedWhenSessionTransferUriUnavailable() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); + final DesktopModeWindowDecoration decor = createWindowDecoration( + taskInfo, TEST_URI1 /* captured link */, TEST_URI2 /* web uri */, + null /* session transfer uri */, TEST_URI4 /* generic link */); // Verify handle menu's browser link set as web uri link when captured link is unavailable createHandleMenu(decor); verifyHandleMenuCreated(TEST_URI2); @@ -1448,12 +1462,12 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); final DesktopModeWindowDecoration decor = createWindowDecoration( taskInfo, null /* captured link */, null /* web uri */, - TEST_URI3 /* generic link */); + null /* session transfer uri */, TEST_URI4 /* generic link */); // Verify handle menu's browser link set as generic link when captured link and web uri link // are unavailable createHandleMenu(decor); - verifyHandleMenuCreated(TEST_URI3); + verifyHandleMenuCreated(TEST_URI4); } @Test @@ -1637,7 +1651,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB) - public void browserApp_webUriUsedForBrowserApp() { + public void browserApp_transferSessionUriUsedForBrowserAppWhenAvailable() { // Make {@link AppToWebUtils#isBrowserApp} return true ResolveInfo resolveInfo = new ResolveInfo(); resolveInfo.handleAllWebDataURI = true; @@ -1648,7 +1662,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); final DesktopModeWindowDecoration decor = createWindowDecoration( taskInfo, TEST_URI1 /* captured link */, TEST_URI2 /* web uri */, - TEST_URI3 /* generic link */); + null /* transfer session uri */, TEST_URI4 /* generic link */); // Verify web uri used for browser applications createHandleMenu(decor); @@ -1656,6 +1670,27 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB) + public void browserApp_webUriUsedForBrowserAppWhenTransferSessionUriUnavailable() { + // Make {@link AppToWebUtils#isBrowserApp} return true + ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.handleAllWebDataURI = true; + resolveInfo.activityInfo = createActivityInfo(); + when(mMockPackageManager.queryIntentActivitiesAsUser(any(), anyInt(), anyInt())) + .thenReturn(List.of(resolveInfo)); + + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); + final DesktopModeWindowDecoration decor = createWindowDecoration( + taskInfo, TEST_URI1 /* captured link */, TEST_URI2 /* web uri */, + TEST_URI3 /* transfer session uri */, TEST_URI4 /* generic link */); + + // Verify web uri used for browser applications + createHandleMenu(decor); + verifyHandleMenuCreated(TEST_URI3); + } + + private void verifyHandleMenuCreated(@Nullable Uri uri) { verify(mMockHandleMenuFactory).create(any(), any(), anyInt(), any(), any(), any(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), @@ -1692,10 +1727,11 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { private DesktopModeWindowDecoration createWindowDecoration( ActivityManager.RunningTaskInfo taskInfo, @Nullable Uri capturedLink, - @Nullable Uri webUri, @Nullable Uri genericLink) { + @Nullable Uri webUri, @Nullable Uri sessionTransferUri, @Nullable Uri genericLink) { taskInfo.capturedLink = capturedLink; taskInfo.capturedLinkTimestamp = System.currentTimeMillis(); mAssistContent.setWebUri(webUri); + mAssistContent.getExtras().putObject(EXTRA_SESSION_TRANSFER_WEB_URI, sessionTransferUri); final String genericLinkString = genericLink == null ? null : genericLink.toString(); doReturn(genericLinkString).when(mMockGenericLinksParser).getGenericLink(any()); // Relayout to set captured link diff --git a/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java index 0237fe7f4ab4..9ae0f03b7f60 100644 --- a/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java +++ b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java @@ -194,20 +194,6 @@ public class OnDeviceIntelligenceManagerService extends SystemService { } } - @Override - public void onUserUnlocked(@NonNull TargetUser user) { - Slog.d(TAG, "onUserUnlocked: " + user.getUserHandle()); - //connect to remote services(if available) during boot. - if (user.getUserHandle().equals(UserHandle.SYSTEM)) { - try { - ensureRemoteInferenceServiceInitialized(/* throwServiceIfInvalid */ false); - ensureRemoteIntelligenceServiceInitialized(/* throwServiceIfInvalid */ false); - } catch (Exception e) { - Slog.w(TAG, "Couldn't pre-start remote ondeviceintelligence services.", e); - } - } - } - private void onDeviceConfigChange(@NonNull Set<String> keys) { if (keys.contains(KEY_SERVICE_ENABLED)) { mIsServiceEnabled = isServiceEnabled(); diff --git a/packages/SettingsLib/ActionButtonsPreference/res/values-v35/styles_expressive.xml b/packages/SettingsLib/ActionButtonsPreference/res/values-v35/styles_expressive.xml index cc948a670382..fd8cecb8536e 100644 --- a/packages/SettingsLib/ActionButtonsPreference/res/values-v35/styles_expressive.xml +++ b/packages/SettingsLib/ActionButtonsPreference/res/values-v35/styles_expressive.xml @@ -22,12 +22,13 @@ <item name="iconGravity">textTop</item> </style> - <style name="SettingsLibActionButton.Expressive.Label" parent="SettingsLibTextAppearance.Emphasized.Title.Small"> + <style name="SettingsLibActionButton.Expressive.Label" parent=""> <item name="android:layout_width">wrap_content</item> <item name="android:layout_height">wrap_content</item> <item name="android:minWidth">@dimen/settingslib_expressive_space_small3</item> <item name="android:minHeight">@dimen/settingslib_expressive_space_small3</item> - <item name="android:textColor">@color/settingslib_materialColorOnSurface</item> + <item name="android:textAppearance">@style/TextAppearance.SettingsLib.TitleSmall.Emphasized</item> + <item name="android:textColor">@color/settingslib_text_color_primary</item> <item name="android:layout_gravity">center</item> </style> diff --git a/packages/SettingsLib/CardPreference/res/layout/settingslib_expressive_preference_card.xml b/packages/SettingsLib/CardPreference/res/layout/settingslib_expressive_preference_card.xml index 716ed412eb5c..9018baca79e7 100644 --- a/packages/SettingsLib/CardPreference/res/layout/settingslib_expressive_preference_card.xml +++ b/packages/SettingsLib/CardPreference/res/layout/settingslib_expressive_preference_card.xml @@ -40,7 +40,6 @@ <ImageView android:id="@android:id/icon" - android:src="@drawable/settingslib_arrow_drop_down" android:layout_width="@dimen/settingslib_expressive_space_medium3" android:layout_height="@dimen/settingslib_expressive_space_medium3" android:scaleType="centerInside"/> @@ -60,16 +59,12 @@ android:id="@android:id/title" android:layout_width="match_parent" android:layout_height="wrap_content" - android:hyphenationFrequency="normalFast" - android:lineBreakWordStyle="phrase" android:textAppearance="@style/TextAppearance.CardTitle.SettingsLib"/> <TextView android:id="@android:id/summary" android:layout_width="match_parent" android:layout_height="wrap_content" - android:hyphenationFrequency="normalFast" - android:lineBreakWordStyle="phrase" android:textAppearance="@style/TextAppearance.CardSummary.SettingsLib"/> </LinearLayout> diff --git a/packages/SettingsLib/CardPreference/res/values/styles_expressive.xml b/packages/SettingsLib/CardPreference/res/values/styles_expressive.xml index 4cbdea52d439..287b13fa0d50 100644 --- a/packages/SettingsLib/CardPreference/res/values/styles_expressive.xml +++ b/packages/SettingsLib/CardPreference/res/values/styles_expressive.xml @@ -17,14 +17,12 @@ <resources> <style name="TextAppearance.CardTitle.SettingsLib" - parent="@style/TextAppearance.PreferenceTitle.SettingsLib"> + parent="@style/TextAppearance.SettingsLib.TitleMedium.Emphasized"> <item name="android:textColor">@color/settingslib_materialColorOnPrimary</item> - <item name="android:textSize">20sp</item> </style> <style name="TextAppearance.CardSummary.SettingsLib" - parent="@style/TextAppearance.PreferenceSummary.SettingsLib"> + parent="@style/TextAppearance.SettingsLib.LabelMedium"> <item name="android:textColor">@color/settingslib_materialColorOnSecondary</item> - <item name="android:textSize">14sp</item> </style> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/drawable-v35/settingslib_expressive_icon_back.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/drawable-v35/settingslib_expressive_icon_back.xml index ccbe20e1c61f..9986a60250fe 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/drawable-v35/settingslib_expressive_icon_back.xml +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/drawable-v35/settingslib_expressive_icon_back.xml @@ -17,12 +17,9 @@ <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item - android:start="16dp" - android:end="16dp" - android:top="4dp" - android:bottom="4dp"> + android:start="16dp"> <shape> - <size android:width="32dp" android:height="40dp" /> + <size android:width="40dp" android:height="40dp" /> <solid android:color="@color/settingslib_materialColorSurfaceContainerHighest" /> <corners android:radius="100dp" /> @@ -30,23 +27,21 @@ </item> <item - android:width="24dp" - android:height="24dp" + android:width="16dp" + android:height="16dp" android:gravity="center" android:start="16dp" - android:end="16dp" - android:top="4dp" - android:bottom="4dp"> +> <vector - android:width="24dp" - android:height="24dp" - android:viewportWidth="960" - android:viewportHeight="960" + android:width="16dp" + android:height="16dp" + android:viewportWidth="16" + android:viewportHeight="16" android:tint="@color/settingslib_materialColorOnSurfaceVariant" android:autoMirrored="true"> <path android:fillColor="@android:color/white" - android:pathData="M313,520L537,744L480,800L160,480L480,160L537,216L313,440L800,440L800,520L313,520Z"/> + android:pathData="M3.626,9L8.526,13.9C8.726,14.1 8.817,14.333 8.801,14.6C8.801,14.867 8.701,15.1 8.501,15.3C8.301,15.483 8.067,15.583 7.801,15.6C7.534,15.6 7.301,15.5 7.101,15.3L0.501,8.7C0.401,8.6 0.326,8.492 0.276,8.375C0.242,8.258 0.226,8.133 0.226,8C0.226,7.867 0.242,7.742 0.276,7.625C0.326,7.508 0.401,7.4 0.501,7.3L7.101,0.7C7.284,0.517 7.509,0.425 7.776,0.425C8.059,0.425 8.301,0.517 8.501,0.7C8.701,0.9 8.801,1.142 8.801,1.425C8.801,1.692 8.701,1.925 8.501,2.125L3.626,7H14.801C15.084,7 15.317,7.1 15.501,7.3C15.701,7.483 15.801,7.717 15.801,8C15.801,8.283 15.701,8.525 15.501,8.725C15.317,8.908 15.084,9 14.801,9H3.626Z"/> </vector> </item> </layer-list>
\ No newline at end of file diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v35/themes.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v35/themes.xml index e68253e2200d..fadcf7ba8699 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v35/themes.xml +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v35/themes.xml @@ -18,7 +18,7 @@ <style name="Theme.CollapsingToolbar.Settings" parent="@style/Theme.MaterialComponents.DayNight"> <item name="elevationOverlayEnabled">true</item> <item name="elevationOverlayColor">?attr/colorPrimary</item> - <item name="colorPrimary">@color/settingslib_materialColorInverseOnSurface</item> + <item name="colorPrimary">@color/settingslib_materialColorOnSurfaceInverse</item> <item name="colorAccent">@color/settingslib_materialColorPrimaryFixed</item> </style> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/styles_expressive.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/styles_expressive.xml index d58c2c2eeb23..37a78101cc4e 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/styles_expressive.xml +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/styles_expressive.xml @@ -33,12 +33,12 @@ <item name="contentScrim">@color/settingslib_materialColorSurfaceContainer</item> </style> - <style name="SettingsLibCollapsingToolbarTitle.Collapsed" parent="@android:style/TextAppearance.DeviceDefault.Headline"> + <style name="SettingsLibCollapsingToolbarTitle.Collapsed" parent="@style/TextAppearance.SettingsLib.TitleLarge.Emphasized"> <!--set dp because we don't want size adjust when font size change--> - <item name="android:textSize">20dp</item> + <item name="android:textSize">22dp</item> </style> - <style name="SettingsLibCollapsingToolbarTitle.Expanded" parent="CollapsingToolbarTitle.Collapsed"> + <style name="SettingsLibCollapsingToolbarTitle.Expanded" parent="@style/TextAppearance.SettingsLib.DisplaySmall.Emphasized"> <item name="android:textSize">36dp</item> </style> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes.xml index f7c9aac68629..7c9d1a47b7ef 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes.xml +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes.xml @@ -18,7 +18,7 @@ <style name="Theme.CollapsingToolbar.Settings" parent="@style/Theme.MaterialComponents.DayNight"> <item name="elevationOverlayEnabled">true</item> <item name="elevationOverlayColor">?attr/colorPrimary</item> - <item name="colorPrimary">@color/settingslib_materialColorInverseOnSurface</item> + <item name="colorPrimary">@color/settingslib_materialColorOnSurfaceInverse</item> <item name="colorAccent">@color/settingslib_materialColorPrimary</item> </style> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml b/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml index 2edc001ccc3f..43cf6aa09109 100644 --- a/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml +++ b/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml @@ -26,7 +26,6 @@ <ImageView android:id="@android:id/icon" - android:src="@drawable/settingslib_arrow_drop_down" style="@style/SettingsLibEntityHeaderIcon"/> <TextView diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java index 106802e9d1d1..73728bcd1ff7 100644 --- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java +++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java @@ -32,6 +32,7 @@ import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.ColorInt; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.android.settingslib.widget.mainswitch.R; @@ -42,7 +43,7 @@ import java.util.List; /** * MainSwitchBar is a View with a customized Switch. * This component is used as the main switch of the page - * to enable or disable the prefereces on the page. + * to enable or disable the preferences on the page. */ public class MainSwitchBar extends LinearLayout implements OnCheckedChangeListener { @@ -58,6 +59,8 @@ public class MainSwitchBar extends LinearLayout implements OnCheckedChangeListen protected CompoundButton mSwitch; private final View mFrameView; + private @Nullable PreChangeListener mPreChangeListener; + public MainSwitchBar(Context context) { this(context, null); } @@ -138,10 +141,20 @@ public class MainSwitchBar extends LinearLayout implements OnCheckedChangeListen @Override public boolean performClick() { - mSwitch.performClick(); + if (callPreChangeListener()) { + mSwitch.performClick(); + } return super.performClick(); } + protected boolean callPreChangeListener() { + return mPreChangeListener == null || mPreChangeListener.preChange(!mSwitch.isChecked()); + } + + public void setPreChangeListener(@Nullable PreChangeListener preChangeListener) { + mPreChangeListener = preChangeListener; + } + /** * Update the switch status */ @@ -271,7 +284,7 @@ public class MainSwitchBar extends LinearLayout implements OnCheckedChangeListen } } - static class SavedState extends BaseSavedState { + public static class SavedState extends BaseSavedState { boolean mChecked; boolean mVisible; @@ -341,4 +354,16 @@ public class MainSwitchBar extends LinearLayout implements OnCheckedChangeListen requestLayout(); } + + /** + * Listener callback before switch is toggled. + */ + public interface PreChangeListener { + /** + * Returns if the new value can be set. + * + * When false is return, the switch toggle is not triggered at all. + */ + boolean preChange(boolean isCheck); + } } diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt index 6e86fa7312cf..c1edbdc20361 100644 --- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt +++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt @@ -107,20 +107,11 @@ interface PreferenceMetadata { * * UI framework normally does not allow user to interact with the preference widget when it is * disabled. - * - * [dependencyOfEnabledState] is provided to support dependency, the [shouldDisableDependents] - * value of dependent preference is used to decide enabled state. */ - fun isEnabled(context: Context): Boolean { - val dependency = dependencyOfEnabledState(context) ?: return true - return !dependency.shouldDisableDependents(context) - } - - /** Returns the key of depended preference to decide the enabled state. */ - fun dependencyOfEnabledState(context: Context): PreferenceMetadata? = null + fun isEnabled(context: Context): Boolean = true - /** Returns whether this preference's dependents should be disabled. */ - fun shouldDisableDependents(context: Context): Boolean = !isEnabled(context) + /** Returns the keys of depended preferences. */ + fun dependencies(context: Context): Array<String> = arrayOf() /** Returns if the preference is persistent in datastore. */ fun isPersistent(context: Context): Boolean = this is PersistentPreference<*> @@ -174,13 +165,11 @@ interface PreferenceMetadata { } /** Metadata of preference group. */ -@AnyThread -interface PreferenceGroup : PreferenceMetadata +@AnyThread interface PreferenceGroup : PreferenceMetadata /** Metadata of preference category. */ @AnyThread -open class PreferenceCategory(override val key: String, override val title: Int) : - PreferenceGroup +open class PreferenceCategory(override val key: String, override val title: Int) : PreferenceGroup /** Metadata of preference screen. */ @AnyThread diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt index 512ea3d874bb..87bd261bf4bd 100644 --- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt +++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt @@ -16,18 +16,10 @@ package com.android.settingslib.metadata -import android.content.Context import androidx.annotation.StringRes -/** - * Common base class for preferences that have two selectable states, save a boolean value, and may - * have dependent preferences that are enabled/disabled based on the current state. - */ -interface TwoStatePreference : PreferenceMetadata, PersistentPreference<Boolean>, BooleanValue { - - override fun shouldDisableDependents(context: Context) = - storage(context).getBoolean(key) != true || super.shouldDisableDependents(context) -} +/** Common base class for preferences that have two selectable states and save a boolean value. */ +interface TwoStatePreference : PreferenceMetadata, PersistentPreference<Boolean>, BooleanValue /** A preference that provides a two-state toggleable option. */ open class SwitchPreference diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt index 31bb62d93d82..a9e20f284a61 100644 --- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt +++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt @@ -97,14 +97,14 @@ class PreferenceScreenBindingHelper( val preferencesBuilder = ImmutableMap.builder<String, PreferenceHierarchyNode>() val dependenciesBuilder = ImmutableMultimap.builder<String, String>() val lifecycleAwarePreferences = mutableListOf<PreferenceLifecycleProvider>() - fun PreferenceMetadata.addDependency(dependency: PreferenceMetadata) { - dependenciesBuilder.put(key, dependency.key) - } fun PreferenceHierarchyNode.addNode() { metadata.let { - preferencesBuilder.put(it.key, this) - it.dependencyOfEnabledState(context)?.addDependency(it) + val key = it.key + preferencesBuilder.put(key, this) + for (dependency in it.dependencies(context)) { + dependenciesBuilder.put(dependency, key) + } if (it is PreferenceLifecycleProvider) lifecycleAwarePreferences.add(it) } } diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant12.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant12.xml new file mode 100644 index 000000000000..f125425d1ec9 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant12.xml @@ -0,0 +1,20 @@ +<?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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@android:color/system_neutral2_600" android:lStar="12"/> +</selector>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant17.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant17.xml new file mode 100644 index 000000000000..36a781954e42 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant17.xml @@ -0,0 +1,20 @@ +<?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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@android:color/system_neutral2_600" android:lStar="17"/> +</selector>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant22.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant22.xml new file mode 100644 index 000000000000..0ef31d014aa2 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant22.xml @@ -0,0 +1,20 @@ +<?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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@android:color/system_neutral2_600" android:lStar="22"/> +</selector>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant24.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant24.xml new file mode 100644 index 000000000000..6797f82e4250 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant24.xml @@ -0,0 +1,20 @@ +<?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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@android:color/system_neutral2_600" android:lStar="24"/> +</selector>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant4.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant4.xml new file mode 100644 index 000000000000..ff7df5543a40 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant4.xml @@ -0,0 +1,20 @@ +<?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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@android:color/system_neutral2_600" android:lStar="4"/> +</selector>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant6.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant6.xml new file mode 100644 index 000000000000..8da5dafea567 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant6.xml @@ -0,0 +1,20 @@ +<?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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@android:color/system_neutral2_600" android:lStar="6"/> +</selector>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant87.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant87.xml new file mode 100644 index 000000000000..227baeedd99e --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant87.xml @@ -0,0 +1,20 @@ +<?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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@android:color/system_neutral2_600" android:lStar="87"/> +</selector>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant92.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant92.xml new file mode 100644 index 000000000000..f4564381eb33 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant92.xml @@ -0,0 +1,20 @@ +<?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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@android:color/system_neutral2_600" android:lStar="92"/> +</selector>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant94.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant94.xml new file mode 100644 index 000000000000..bb4e03d64307 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant94.xml @@ -0,0 +1,20 @@ +<?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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@android:color/system_neutral2_600" android:lStar="94"/> +</selector>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant96.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant96.xml new file mode 100644 index 000000000000..949b1961099f --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant96.xml @@ -0,0 +1,20 @@ +<?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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@android:color/system_neutral2_600" android:lStar="96"/> +</selector>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant98.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant98.xml new file mode 100644 index 000000000000..7e5ee241ffbd --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant98.xml @@ -0,0 +1,20 @@ +<?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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@android:color/system_neutral2_600" android:lStar="98"/> +</selector>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_check.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_check.xml index 309dbdf1ea96..309dbdf1ea96 100644 --- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_check.xml +++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_check.xml diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_close.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_close.xml index e6df8a416922..e6df8a416922 100644 --- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_close.xml +++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_close.xml diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_switch_thumb_icon.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_switch_thumb_icon.xml index 342729d7ee5a..342729d7ee5a 100644 --- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_switch_thumb_icon.xml +++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_switch_thumb_icon.xml diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml index ea7baa42a2c7..2261e58a961e 100644 --- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml +++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml @@ -40,7 +40,7 @@ android:longClickable="false" android:maxLines="10" android:ellipsize="end" - android:textAppearance="@style/TextAppearance.TopIntroText"/> + android:textAppearance="@style/TextAppearance.SettingsLib.BodyLarge"/> <com.android.settingslib.widget.LinkableTextView android:id="@+id/settingslib_expressive_learn_more" diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml b/packages/SettingsLib/SettingsTheme/res/layout/settingslib_expressive_preference_text_frame.xml index c837ff43e46b..db357f8ae13f 100644 --- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml +++ b/packages/SettingsLib/SettingsTheme/res/layout/settingslib_expressive_preference_text_frame.xml @@ -32,8 +32,6 @@ android:layout_gravity="start" android:textAlignment="viewStart" android:maxLines="2" - android:hyphenationFrequency="normalFast" - android:lineBreakWordStyle="phrase" android:textAppearance="?android:attr/textAppearanceListItem" android:ellipsize="marquee"/> @@ -47,7 +45,5 @@ android:textAlignment="viewStart" android:textAppearance="?android:attr/textAppearanceListItemSecondary" android:textColor="?android:attr/textColorSecondary" - android:maxLines="10" - android:hyphenationFrequency="normalFast" - android:lineBreakWordStyle="phrase"/> + android:maxLines="10"/> </RelativeLayout>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_preference_category_no_title.xml b/packages/SettingsLib/SettingsTheme/res/layout/settingslib_preference_category_no_title.xml index f69fcd270919..f69fcd270919 100644 --- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_preference_category_no_title.xml +++ b/packages/SettingsLib/SettingsTheme/res/layout/settingslib_preference_category_no_title.xml diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml index 46ec62e7a5ef..8873116be306 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml @@ -58,4 +58,39 @@ <color name="settingslib_colorSurface">@color/settingslib_surface_dark</color> <color name="settingslib_list_divider_color">@android:color/system_neutral1_700</color> + + <color name="settingslib_materialColorPrimary">@android:color/system_accent1_200</color> + <color name="settingslib_materialColorOnPrimary">@android:color/system_accent1_800</color> + <color name="settingslib_materialColorPrimaryContainer">@android:color/system_accent1_700</color> + <color name="settingslib_materialColorOnPrimaryContainer">@android:color/system_accent1_100</color> + <color name="settingslib_materialColorPrimaryInverse">@android:color/system_accent1_600</color> + <color name="settingslib_materialColorSecondary">@android:color/system_accent2_200</color> + <color name="settingslib_materialColorOnSecondary">@android:color/system_accent2_800</color> + <color name="settingslib_materialColorSecondaryContainer">@android:color/system_accent2_700</color> + <color name="settingslib_materialColorOnSecondaryContainer">@android:color/system_accent2_100</color> + <color name="settingslib_materialColorTertiary">@android:color/system_accent3_200</color> + <color name="settingslib_materialColorOnTertiary">@android:color/system_accent3_800</color> + <color name="settingslib_materialColorTertiaryContainer">@android:color/system_accent3_700</color> + <color name="settingslib_materialColorOnTertiaryContainer">@android:color/system_accent3_100</color> + <color name="settingslib_materialColorError">@color/settingslib_error_200</color> + <color name="settingslib_materialColorOnError">@color/settingslib_error_800</color> + <color name="settingslib_materialColorErrorContainer">@color/settingslib_error_700</color> + <color name="settingslib_materialColorOnErrorContainer">@color/settingslib_error_100</color> + <color name="settingslib_materialColorOutline">@android:color/system_neutral2_400</color> + <color name="settingslib_materialColorOutlineVariant">@android:color/system_neutral2_700</color> + <color name="settingslib_materialColorBackground">@color/settingslib_neutral_variant6</color> + <color name="settingslib_materialColorOnBackground">@android:color/system_neutral1_100</color> + <color name="settingslib_materialColorSurface">@color/settingslib_neutral_variant6</color> + <color name="settingslib_materialColorOnSurface">@android:color/system_neutral1_100</color> + <color name="settingslib_materialColorSurfaceVariant">@android:color/system_neutral2_700</color> + <color name="settingslib_materialColorOnSurfaceVariant">@android:color/system_neutral2_200</color> + <color name="settingslib_materialColorSurfaceInverse">@android:color/system_neutral1_100</color> + <color name="settingslib_materialColorOnSurfaceInverse">@android:color/system_neutral1_800</color> + <color name="settingslib_materialColorSurfaceBright">@color/settingslib_neutral_variant24</color> + <color name="settingslib_materialColorSurfaceDim">@color/settingslib_neutral_variant6</color> + <color name="settingslib_materialColorSurfaceContainer">@color/settingslib_neutral_variant12</color> + <color name="settingslib_materialColorSurfaceContainerLowest">@color/settingslib_neutral_variant4</color> + <color name="settingslib_materialColorSurfaceContainerLow">@android:color/system_neutral2_900</color> + <color name="settingslib_materialColorSurfaceContainerHigh">@color/settingslib_neutral_variant17</color> + <color name="settingslib_materialColorSurfaceContainerHighest">@color/settingslib_neutral_variant22</color> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v34/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v34/colors.xml index 8cfe54f44fe5..00a1f27c162a 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-night-v34/colors.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-night-v34/colors.xml @@ -42,4 +42,39 @@ <color name="settingslib_text_color_primary_device_default">@android:color/system_on_surface_dark</color> <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurfaceVariant--> <color name="settingslib_text_color_secondary_device_default">@android:color/system_on_surface_variant_dark</color> + + <color name="settingslib_materialColorPrimary">@android:color/system_primary_dark</color> + <color name="settingslib_materialColorOnPrimary">@android:color/system_on_primary_dark</color> + <color name="settingslib_materialColorPrimaryContainer">@android:color/system_primary_container_dark</color> + <color name="settingslib_materialColorOnPrimaryContainer">@android:color/system_on_primary_container_dark</color> + <color name="settingslib_materialColorPrimaryInverse">@android:color/system_primary_light</color> + <color name="settingslib_materialColorSecondary">@android:color/system_secondary_dark</color> + <color name="settingslib_materialColorOnSecondary">@android:color/system_on_secondary_dark</color> + <color name="settingslib_materialColorSecondaryContainer">@android:color/system_secondary_container_dark</color> + <color name="settingslib_materialColorOnSecondaryContainer">@android:color/system_on_secondary_container_dark</color> + <color name="settingslib_materialColorTertiary">@android:color/system_tertiary_dark</color> + <color name="settingslib_materialColorOnTertiary">@android:color/system_on_tertiary_dark</color> + <color name="settingslib_materialColorTertiaryContainer">@android:color/system_tertiary_container_dark</color> + <color name="settingslib_materialColorOnTertiaryContainer">@android:color/system_on_tertiary_container_dark</color> + <color name="settingslib_materialColorError">@android:color/system_error_dark</color> + <color name="settingslib_materialColorOnError">@android:color/system_on_error_dark</color> + <color name="settingslib_materialColorErrorContainer">@android:color/system_error_container_dark</color> + <color name="settingslib_materialColorOnErrorContainer">@android:color/system_on_error_container_dark</color> + <color name="settingslib_materialColorOutline">@android:color/system_outline_dark</color> + <color name="settingslib_materialColorOutlineVariant">@android:color/system_outline_variant_dark</color> + <color name="settingslib_materialColorBackground">@android:color/system_background_dark</color> + <color name="settingslib_materialColorOnBackground">@android:color/system_on_background_dark</color> + <color name="settingslib_materialColorSurface">@android:color/system_surface_dark</color> + <color name="settingslib_materialColorOnSurface">@android:color/system_on_surface_dark</color> + <color name="settingslib_materialColorSurfaceVariant">@android:color/system_surface_variant_dark</color> + <color name="settingslib_materialColorOnSurfaceVariant">@android:color/system_on_surface_variant_dark</color> + <color name="settingslib_materialColorSurfaceInverse">@android:color/system_surface_light</color> + <color name="settingslib_materialColorOnSurfaceInverse">@android:color/system_on_surface_light</color> + <color name="settingslib_materialColorSurfaceBright">@android:color/system_surface_bright_dark</color> + <color name="settingslib_materialColorSurfaceDim">@android:color/system_surface_dim_dark</color> + <color name="settingslib_materialColorSurfaceContainer">@android:color/system_surface_container_dark</color> + <color name="settingslib_materialColorSurfaceContainerLow">@android:color/system_surface_container_low_dark</color> + <color name="settingslib_materialColorSurfaceContainerLowest">@android:color/system_surface_container_lowest_dark</color> + <color name="settingslib_materialColorSurfaceContainerHigh">@android:color/system_surface_container_high_dark</color> + <color name="settingslib_materialColorSurfaceContainerHighest">@android:color/system_surface_container_highest_dark</color> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml index 84a3ed68af01..e31e80176625 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml @@ -46,37 +46,4 @@ <color name="settingslib_colorSurfaceHeader">@color/settingslib_materialColorSurfaceVariant</color> <color name="settingslib_text_color_preference_category_title">@color/settingslib_materialColorPrimary</color> - - <color name="settingslib_materialColorSurfaceContainerLowest">@android:color/system_surface_container_lowest_dark</color> - <color name="settingslib_materialColorOnSecondaryContainer">@android:color/system_on_secondary_container_dark</color> - <color name="settingslib_materialColorOnTertiaryContainer">@android:color/system_on_tertiary_container_dark</color> - <color name="settingslib_materialColorSurfaceContainerLow">@android:color/system_surface_container_low_dark</color> - <color name="settingslib_materialColorOnPrimaryContainer">@android:color/system_on_primary_container_dark</color> - <color name="settingslib_materialColorOnErrorContainer">@android:color/system_on_error_container_dark</color> - <color name="settingslib_materialColorInverseOnSurface">@android:color/system_on_surface_light</color> - <color name="settingslib_materialColorSecondaryContainer">@android:color/system_secondary_container_dark</color> - <color name="settingslib_materialColorErrorContainer">@android:color/system_error_container_dark</color> - <color name="settingslib_materialColorInversePrimary">@android:color/system_primary_light</color> - <color name="settingslib_materialColorInverseSurface">@android:color/system_surface_light</color> - <color name="settingslib_materialColorSurfaceVariant">@android:color/system_surface_variant_dark</color> - <color name="settingslib_materialColorTertiaryContainer">@android:color/system_tertiary_container_dark</color> - <color name="settingslib_materialColorPrimaryContainer">@android:color/system_primary_container_dark</color> - <color name="settingslib_materialColorOnBackground">@android:color/system_on_background_dark</color> - <color name="settingslib_materialColorOnSecondary">@android:color/system_on_secondary_dark</color> - <color name="settingslib_materialColorOnTertiary">@android:color/system_on_tertiary_dark</color> - <color name="settingslib_materialColorSurfaceDim">@android:color/system_surface_dim_dark</color> - <color name="settingslib_materialColorSurfaceBright">@android:color/system_surface_bright_dark</color> - <color name="settingslib_materialColorOnError">@android:color/system_on_error_dark</color> - <color name="settingslib_materialColorSurface">@android:color/system_surface_dark</color> - <color name="settingslib_materialColorSurfaceContainerHigh">@android:color/system_surface_container_high_dark</color> - <color name="settingslib_materialColorSurfaceContainerHighest">@android:color/system_surface_container_highest_dark</color> - <color name="settingslib_materialColorOnSurfaceVariant">@android:color/system_on_surface_variant_dark</color> - <color name="settingslib_materialColorOutline">@android:color/system_outline_dark</color> - <color name="settingslib_materialColorOutlineVariant">@android:color/system_outline_variant_dark</color> - <color name="settingslib_materialColorOnPrimary">@android:color/system_on_primary_dark</color> - <color name="settingslib_materialColorOnSurface">@android:color/system_on_surface_dark</color> - <color name="settingslib_materialColorSurfaceContainer">@android:color/system_surface_container_dark</color> - <color name="settingslib_materialColorPrimary">@android:color/system_primary_dark</color> - <color name="settingslib_materialColorSecondary">@android:color/system_secondary_dark</color> - <color name="settingslib_materialColorTertiary">@android:color/system_tertiary_dark</color> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-night/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night/colors.xml new file mode 100644 index 000000000000..e57fe4f512fe --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/values-night/colors.xml @@ -0,0 +1,53 @@ +<?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. +--> + +<resources> + <color name="settingslib_materialColorPrimary">#83D6C7</color> + <color name="settingslib_materialColorOnPrimary">#003730</color> + <color name="settingslib_materialColorPrimaryContainer">#005047</color> + <color name="settingslib_materialColorOnPrimaryContainer">#A1F1E2</color> + <color name="settingslib_materialColorPrimaryInverse">#A1F1E2</color> + <color name="settingslib_materialColorSecondary">#B1CCC6</color> + <color name="settingslib_materialColorOnSecondary">#1C342F</color> + <color name="settingslib_materialColorSecondaryContainer">#334C47</color> + <color name="settingslib_materialColorOnSecondaryContainer">#CCE8E2</color> + <color name="settingslib_materialColorTertiary">#ADCAE5</color> + <color name="settingslib_materialColorOnTertiary">#123349</color> + <color name="settingslib_materialColorTertiaryContainer">#2D4960</color> + <color name="settingslib_materialColorOnTertiaryContainer">#CEE7FF</color> + <color name="settingslib_materialColorError">#F2B8B5</color> + <color name="settingslib_materialColorOnError">#601410</color> + <color name="settingslib_materialColorErrorContainer">#8C1D18</color> + <color name="settingslib_materialColorOnErrorContainer">#F9DEDC</color> + <color name="settingslib_materialColorOutline">#919191</color> + <color name="settingslib_materialColorOutlineVariant">#474747</color> + <color name="settingslib_materialColorBackground">#131313</color> + <color name="settingslib_materialColorOnBackground">#E5E2E1</color> + <color name="settingslib_materialColorSurface">#131313</color> + <color name="settingslib_materialColorOnSurface">#E5E2E1</color> + <color name="settingslib_materialColorSurfaceVariant">#474747</color> + <color name="settingslib_materialColorOnSurfaceVariant">#C7C7C7</color> + <color name="settingslib_materialColorSurfaceInverse">#E5E2E1</color> + <color name="settingslib_materialColorOnSurfaceInverse">#303030</color> + <color name="settingslib_materialColorSurfaceBright">#393939</color> + <color name="settingslib_materialColorSurfaceDim">#131313</color> + <color name="settingslib_materialColorSurfaceContainer">#1F1F1F</color> + <color name="settingslib_materialColorSurfaceContainerLowest">#1B1B1B</color> + <color name="settingslib_materialColorSurfaceContainerLow">#0E0E0E</color> + <color name="settingslib_materialColorSurfaceContainerHigh">#2A2A2A</color> + <color name="settingslib_materialColorSurfaceContainerHighest">#343434</color> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml index fef92b792bec..e000423784c6 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml @@ -92,4 +92,51 @@ <color name="settingslib_spinner_dropdown_color">@android:color/system_neutral2_700</color> <color name="settingslib_list_divider_color">@android:color/system_neutral1_200</color> + + <color name="settingslib_materialColorPrimary">@android:color/system_accent1_600</color> + <color name="settingslib_materialColorOnPrimary">@android:color/system_accent1_0</color> + <color name="settingslib_materialColorPrimaryContainer">@android:color/system_accent1_100</color> + <color name="settingslib_materialColorOnPrimaryContainer">@android:color/system_accent1_900</color> + <color name="settingslib_materialColorPrimaryInverse">@android:color/system_accent1_200</color> + <color name="settingslib_materialColorPrimaryFixed">@android:color/system_accent1_100</color> + <color name="settingslib_materialColorPrimaryFixedDim">@android:color/system_accent1_200</color> + <color name="settingslib_materialColorOnPrimaryFixed">@android:color/system_accent1_900</color> + <color name="settingslib_materialColorOnPrimaryFixedVariant">@android:color/system_accent1_700</color> + <color name="settingslib_materialColorSecondary">@android:color/system_accent2_600</color> + <color name="settingslib_materialColorOnSecondary">@android:color/system_accent2_0</color> + <color name="settingslib_materialColorSecondaryContainer">@android:color/system_accent2_100</color> + <color name="settingslib_materialColorOnSecondaryContainer">@android:color/system_accent2_900</color> + <color name="settingslib_materialColorSecondaryFixed">@android:color/system_accent2_100</color> + <color name="settingslib_materialColorSecondaryFixedDim">@android:color/system_accent2_200</color> + <color name="settingslib_materialColorOnSecondaryFixed">@android:color/system_accent2_900</color> + <color name="settingslib_materialColorOnSecondaryFixedVariant">@android:color/system_accent2_700</color> + <color name="settingslib_materialColorTertiary">@android:color/system_accent3_600</color> + <color name="settingslib_materialColorOnTertiary">@android:color/system_accent3_0</color> + <color name="settingslib_materialColorTertiaryContainer">@android:color/system_accent3_100</color> + <color name="settingslib_materialColorOnTertiaryContainer">@android:color/system_accent3_900</color> + <color name="settingslib_materialColorTertiaryFixed">@android:color/system_accent3_100</color> + <color name="settingslib_materialColorTertiaryFixedDim">@android:color/system_accent3_200</color> + <color name="settingslib_materialColorOnTertiaryFixed">@android:color/system_accent3_900</color> + <color name="settingslib_materialColorOnTertiaryFixedVariant">@android:color/system_accent3_700</color> + <color name="settingslib_materialColorError">@color/settingslib_error_600</color> + <color name="settingslib_materialColorOnError">@android:color/white</color> + <color name="settingslib_materialColorErrorContainer">@color/settingslib_error_100</color> + <color name="settingslib_materialColorOnErrorContainer">@color/settingslib_error_900</color> + <color name="settingslib_materialColorOutline">@android:color/system_neutral2_500</color> + <color name="settingslib_materialColorOutlineVariant">@android:color/system_neutral2_200</color> + <color name="settingslib_materialColorBackground">@android:color/white</color> + <color name="settingslib_materialColorOnBackground">@android:color/system_neutral1_900</color> + <color name="settingslib_materialColorSurface">@color/settingslib_neutral_variant98</color> + <color name="settingslib_materialColorOnSurface">@android:color/system_neutral1_900</color> + <color name="settingslib_materialColorSurfaceVariant">@android:color/system_neutral2_100</color> + <color name="settingslib_materialColorOnSurfaceVariant">@android:color/system_neutral2_700</color> + <color name="settingslib_materialColorSurfaceInverse">@android:color/system_neutral1_800</color> + <color name="settingslib_materialColorOnSurfaceInverse">@android:color/system_neutral1_50</color> + <color name="settingslib_materialColorSurfaceBright">@color/settingslib_neutral_variant98</color> + <color name="settingslib_materialColorSurfaceDim">@color/settingslib_neutral_variant87</color> + <color name="settingslib_materialColorSurfaceContainer">@color/settingslib_neutral_variant94</color> + <color name="settingslib_materialColorSurfaceContainerLow">@color/settingslib_neutral_variant96</color> + <color name="settingslib_materialColorSurfaceContainerLowest">@android:color/system_neutral2_0</color> + <color name="settingslib_materialColorSurfaceContainerHigh">@color/settingslib_neutral_variant92</color> + <color name="settingslib_materialColorSurfaceContainerHighest">@android:color/system_neutral2_100</color> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/config.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/config.xml index 4860ad361744..8993d0fc71f7 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v31/config.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v31/config.xml @@ -17,6 +17,4 @@ <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <bool name="settingslib_config_icon_space_reserved">false</bool> <bool name="settingslib_config_allow_divider">false</bool> - <!-- Name of a font family to use for headlines in SettingsLib. --> - <string name="settingslib_config_headlineFontFamily" translatable="false"></string> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/styles_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/styles_expressive.xml new file mode 100644 index 000000000000..9d3d70b366aa --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/values-v31/styles_expressive.xml @@ -0,0 +1,129 @@ +<?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. + --> + +<resources> + <style name="SettingsLibButtonStyle.Expressive.Filled" + parent="@style/Widget.Material3.Button"> + <item name="android:theme">@style/Theme.Material3.DynamicColors.DayNight</item> + <item name="android:layout_width">wrap_content</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:gravity">center</item> + <item name="android:minWidth">@dimen/settingslib_expressive_space_medium4</item> + <item name="android:minHeight">@dimen/settingslib_expressive_space_medium4</item> + <item name="android:paddingVertical">@dimen/settingslib_expressive_space_extrasmall5</item> + <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small1</item> + <item name="android:backgroundTint">@color/settingslib_materialColorPrimary</item> + <item name="android:textAppearance">@style/TextAppearance.SettingsLib.LabelLarge</item> + <item name="android:textColor">@color/settingslib_materialColorOnPrimary</item> + <item name="iconGravity">textStart</item> + <item name="iconTint">@color/settingslib_materialColorOnPrimary</item> + <item name="iconSize">@dimen/settingslib_expressive_space_small4</item> + </style> + + <style name="SettingsLibButtonStyle.Expressive.Filled.Large"> + <item name="android:paddingVertical">@dimen/settingslib_expressive_space_small1</item> + <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small4</item> + <item name="android:textAppearance">@style/TextAppearance.SettingsLib.TitleMedium</item> + </style> + + <style name="SettingsLibButtonStyle.Expressive.Filled.Extra" + parent="@style/SettingsLibButtonStyle.Expressive.Filled.Large"> + <item name="android:layout_width">match_parent</item> + </style> + + <style name="SettingsLibButtonStyle.Expressive.Tonal" + parent="@style/Widget.Material3.Button.TonalButton"> + <item name="android:theme">@style/Theme.Material3.DynamicColors.DayNight</item> + <item name="android:layout_width">wrap_content</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:gravity">center</item> + <item name="android:minWidth">@dimen/settingslib_expressive_space_medium4</item> + <item name="android:minHeight">@dimen/settingslib_expressive_space_medium4</item> + <item name="android:paddingVertical">@dimen/settingslib_expressive_space_extrasmall5</item> + <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small1</item> + <item name="android:backgroundTint">@color/settingslib_materialColorSecondaryContainer</item> + <item name="android:textAppearance">@style/TextAppearance.SettingsLib.LabelLarge</item> + <item name="android:textColor">@color/settingslib_materialColorOnSecondaryContainer</item> + <item name="iconGravity">textStart</item> + <item name="iconTint">@color/settingslib_materialColorOnSecondaryContainer</item> + <item name="iconSize">@dimen/settingslib_expressive_space_small4</item> + </style> + + <style name="SettingsLibButtonStyle.Expressive.Tonal.Large"> + <item name="android:paddingVertical">@dimen/settingslib_expressive_space_small1</item> + <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small4</item> + <item name="android:textAppearance">@style/TextAppearance.SettingsLib.TitleMedium</item> + </style> + + <style name="SettingsLibButtonStyle.Expressive.Tonal.Extra" + parent="@style/SettingsLibButtonStyle.Expressive.Tonal.Large"> + <item name="android:layout_width">match_parent</item> + </style> + + <style name="SettingsLibButtonStyle.Expressive.Outline" + parent="@style/Widget.Material3.Button.OutlinedButton.Icon"> + <item name="android:theme">@style/Theme.Material3.DynamicColors.DayNight</item> + <item name="android:layout_width">wrap_content</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:gravity">center</item> + <item name="android:minWidth">@dimen/settingslib_expressive_space_medium4</item> + <item name="android:minHeight">@dimen/settingslib_expressive_space_medium4</item> + <item name="android:paddingVertical">@dimen/settingslib_expressive_space_extrasmall5</item> + <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small1</item> + <item name="android:textAppearance">@style/TextAppearance.SettingsLib.LabelLarge</item> + <item name="android:textColor">@color/settingslib_materialColorPrimary</item> + <item name="iconTint">@color/settingslib_materialColorPrimary</item> + <item name="iconGravity">textStart</item> + <item name="iconSize">@dimen/settingslib_expressive_space_small4</item> + <item name="iconPadding">@dimen/settingslib_expressive_space_extrasmall4</item> + <item name="strokeColor">@color/settingslib_materialColorOutlineVariant</item> + </style> + + <style name="SettingsLibButtonStyle.Expressive.Outline.Large"> + <item name="android:paddingVertical">@dimen/settingslib_expressive_space_small1</item> + <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small4</item> + <item name="android:textAppearance">@style/TextAppearance.SettingsLib.TitleMedium</item> + </style> + + <style name="SettingsLibButtonStyle.Expressive.Outline.Extra" + parent="@style/SettingsLibButtonStyle.Expressive.Outline.Large"> + <item name="android:layout_width">match_parent</item> + </style> + + <style name="SettingslibTextButtonStyle.Expressive" + parent="@style/Widget.Material3.Button.TextButton.Icon"> + <item name="android:theme">@style/Theme.Material3.DynamicColors.DayNight</item> + <item name="android:layout_width">wrap_content</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:textAppearance">@style/TextAppearance.SettingsLib.BodyLarge.Emphasized</item> + <item name="android:textColor">@color/settingslib_materialColorOnSurface</item> + <item name="iconTint">@null</item> + <item name="iconPadding">@dimen/settingslib_expressive_space_extrasmall4</item> + <item name="rippleColor">?android:attr/colorControlHighlight</item> + </style> + + <style name="SettingsLibCardStyle" parent=""> + <item name="android:layout_width">match_parent</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:layout_marginHorizontal">?android:attr/listPreferredItemPaddingStart</item> + <item name="android:layout_marginVertical">@dimen/settingslib_expressive_space_extrasmall4</item> + <item name="cardBackgroundColor">@color/settingslib_materialColorPrimary</item> + <item name="cardCornerRadius">@dimen/settingslib_expressive_radius_extralarge3</item> + <item name="cardElevation">0dp</item> + <item name="rippleColor">?android:attr/colorControlHighlight</item> + </style> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values-v33/styles_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v33/styles_expressive.xml new file mode 100644 index 000000000000..74bf55a8a625 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/values-v33/styles_expressive.xml @@ -0,0 +1,306 @@ +<?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. + --> + +<resources> + <style name="TextAppearance.SettingsLib.DisplayLarge" + parent="@android:style/TextAppearance.DeviceDefault.Headline"> + <item name="android:textSize">57sp</item> + <item name="android:letterSpacing">-0.00438596</item> + <item name="android:lineHeight">64sp</item> + <item name="android:hyphenationFrequency">normalFast</item> + <item name="android:lineBreakWordStyle">phrase</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.DisplayMedium" + parent="@android:style/TextAppearance.DeviceDefault.Headline"> + <item name="android:textSize">45sp</item> + <item name="android:letterSpacing">0</item> + <item name="android:lineHeight">52sp</item> + <item name="android:hyphenationFrequency">normalFast</item> + <item name="android:lineBreakWordStyle">phrase</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.DisplaySmall" + parent="@android:style/TextAppearance.DeviceDefault.Headline"> + <item name="android:textSize">36sp</item> + <item name="android:letterSpacing">0</item> + <item name="android:lineHeight">44sp</item> + <item name="android:hyphenationFrequency">normalFast</item> + <item name="android:lineBreakWordStyle">phrase</item> + <item name="android:textAllCaps">false</item> + </style> + + <style name="TextAppearance.SettingsLib.HeadlineLarge" + parent="@android:style/TextAppearance.DeviceDefault.Headline"> + <item name="android:textSize">32sp</item> + <item name="android:letterSpacing">0</item> + <item name="android:lineHeight">40sp</item> + <item name="android:hyphenationFrequency">normalFast</item> + <item name="android:lineBreakWordStyle">phrase</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.HeadlineMedium" + parent="@android:style/TextAppearance.DeviceDefault.Headline"> + <item name="android:textSize">28sp</item> + <item name="android:letterSpacing">0</item> + <item name="android:lineHeight">36sp</item> + <item name="android:hyphenationFrequency">normalFast</item> + <item name="android:lineBreakWordStyle">phrase</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.HeadlineSmall" + parent="@android:style/TextAppearance.DeviceDefault.Headline"> + <item name="android:textSize">24sp</item> + <item name="android:letterSpacing">0</item> + <item name="android:lineHeight">32sp</item> + <item name="android:hyphenationFrequency">normalFast</item> + <item name="android:lineBreakWordStyle">phrase</item> + <item name="android:textAllCaps">false</item> + </style> + + <style name="TextAppearance.SettingsLib.TitleLarge" + parent="@android:style/TextAppearance.DeviceDefault.Headline"> + <item name="android:textSize">22sp</item> + <item name="android:letterSpacing">0</item> + <item name="android:lineHeight">28sp</item> + <item name="android:hyphenationFrequency">normalFast</item> + <item name="android:lineBreakWordStyle">phrase</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.TitleMedium" + parent="@android:style/TextAppearance.DeviceDefault.Medium"> + <item name="android:textSize">16sp</item> + <item name="android:letterSpacing">0.009375</item> + <item name="android:lineHeight">24sp</item> + <item name="android:hyphenationFrequency">normalFast</item> + <item name="android:lineBreakWordStyle">phrase</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.TitleSmall" + parent="@android:style/TextAppearance.DeviceDefault.Medium"> + <item name="android:textSize">14sp</item> + <item name="android:letterSpacing">0.00714286</item> + <item name="android:lineHeight">20sp</item> + <item name="android:hyphenationFrequency">normalFast</item> + <item name="android:lineBreakWordStyle">phrase</item> + <item name="android:textAllCaps">false</item> + </style> + + <style name="TextAppearance.SettingsLib.LabelLarge" + parent="@android:style/TextAppearance.DeviceDefault.Medium"> + <item name="android:textSize">14sp</item> + <item name="android:letterSpacing">0.00714286</item> + <item name="android:lineHeight">20sp</item> + <item name="android:hyphenationFrequency">normalFast</item> + <item name="android:lineBreakWordStyle">phrase</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.LabelMedium" + parent="@android:style/TextAppearance.DeviceDefault.Medium"> + <item name="android:textSize">12sp</item> + <item name="android:letterSpacing">0.04166667</item> + <item name="android:lineHeight">16sp</item> + <item name="android:hyphenationFrequency">normalFast</item> + <item name="android:lineBreakWordStyle">phrase</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.LabelSmall" + parent="@android:style/TextAppearance.DeviceDefault.Medium"> + <item name="android:textSize">11sp</item> + <item name="android:letterSpacing">0.04545455</item> + <item name="android:lineHeight">16sp</item> + <item name="android:hyphenationFrequency">normalFast</item> + <item name="android:lineBreakWordStyle">phrase</item> + <item name="android:textAllCaps">false</item> + </style> + + <style name="TextAppearance.SettingsLib.BodyLarge" + parent="@android:style/TextAppearance.DeviceDefault"> + <item name="android:textSize">16sp</item> + <item name="android:letterSpacing">0.03125</item> + <item name="android:lineHeight">24sp</item> + <item name="android:hyphenationFrequency">normalFast</item> + <item name="android:lineBreakWordStyle">phrase</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.BodyMedium" + parent="@android:style/TextAppearance.DeviceDefault"> + <item name="android:textSize">14sp</item> + <item name="android:letterSpacing">0.01785714</item> + <item name="android:lineHeight">20sp</item> + <item name="android:hyphenationFrequency">normalFast</item> + <item name="android:lineBreakWordStyle">phrase</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.BodySmall" + parent="@android:style/TextAppearance.DeviceDefault"> + <item name="android:textSize">12sp</item> + <item name="android:letterSpacing">0.03333333</item> + <item name="android:lineHeight">16sp</item> + <item name="android:hyphenationFrequency">normalFast</item> + <item name="android:lineBreakWordStyle">phrase</item> + <item name="android:textAllCaps">false</item> + </style> + + <style name="TextAppearance.SettingsLib.DisplayLarge.Emphasized" + parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title"> + <item name="android:textSize">57sp</item> + <item name="android:letterSpacing">0</item> + <item name="android:lineHeight">64sp</item> + <item name="android:hyphenationFrequency">normalFast</item> + <item name="android:lineBreakWordStyle">phrase</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.DisplayMedium.Emphasized" + parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title"> + <item name="android:textSize">45sp</item> + <item name="android:letterSpacing">0</item> + <item name="android:lineHeight">52sp</item> + <item name="android:hyphenationFrequency">normalFast</item> + <item name="android:lineBreakWordStyle">phrase</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.DisplaySmall.Emphasized" + parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title"> + <item name="android:textSize">36sp</item> + <item name="android:letterSpacing">0</item> + <item name="android:lineHeight">44sp</item> + <item name="android:hyphenationFrequency">normalFast</item> + <item name="android:lineBreakWordStyle">phrase</item> + <item name="android:textAllCaps">false</item> + </style> + + <style name="TextAppearance.SettingsLib.HeadlineLarge.Emphasized" + parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title"> + <item name="android:textSize">32sp</item> + <item name="android:letterSpacing">0</item> + <item name="android:lineHeight">40sp</item> + <item name="android:hyphenationFrequency">normalFast</item> + <item name="android:lineBreakWordStyle">phrase</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.HeadlineMedium.Emphasized" + parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title"> + <item name="android:textSize">28sp</item> + <item name="android:letterSpacing">0</item> + <item name="android:lineHeight">36sp</item> + <item name="android:hyphenationFrequency">normalFast</item> + <item name="android:lineBreakWordStyle">phrase</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.HeadlineSmall.Emphasized" + parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title"> + <item name="android:textSize">24sp</item> + <item name="android:letterSpacing">0</item> + <item name="android:lineHeight">32sp</item> + <item name="android:hyphenationFrequency">normalFast</item> + <item name="android:lineBreakWordStyle">phrase</item> + <item name="android:textAllCaps">false</item> + </style> + + <style name="TextAppearance.SettingsLib.TitleLarge.Emphasized" + parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title"> + <item name="android:textSize">22sp</item> + <item name="android:letterSpacing">0</item> + <item name="android:lineHeight">28sp</item> + <item name="android:hyphenationFrequency">normalFast</item> + <item name="android:lineBreakWordStyle">phrase</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.TitleMedium.Emphasized" + parent="@android:style/TextAppearance.DeviceDefault"> + <item name="android:textStyle">bold</item> + <item name="android:textSize">16sp</item> + <item name="android:letterSpacing">0.009375</item> + <item name="android:lineHeight">24sp</item> + <item name="android:hyphenationFrequency">normalFast</item> + <item name="android:lineBreakWordStyle">phrase</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.TitleSmall.Emphasized" + parent="@android:style/TextAppearance.DeviceDefault"> + <item name="android:textStyle">bold</item> + <item name="android:textSize">14sp</item> + <item name="android:letterSpacing">0.00714286</item> + <item name="android:lineHeight">20sp</item> + <item name="android:hyphenationFrequency">normalFast</item> + <item name="android:lineBreakWordStyle">phrase</item> + <item name="android:textAllCaps">false</item> + </style> + + <style name="TextAppearance.SettingsLib.LabelLarge.Emphasized" + parent="@android:style/TextAppearance.DeviceDefault"> + <item name="android:textStyle">bold</item> + <item name="android:textSize">14sp</item> + <item name="android:letterSpacing">0.00714286</item> + <item name="android:lineHeight">20sp</item> + <item name="android:hyphenationFrequency">normalFast</item> + <item name="android:lineBreakWordStyle">phrase</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.LabelMedium.Emphasized" + parent="@android:style/TextAppearance.DeviceDefault"> + <item name="android:textStyle">bold</item> + <item name="android:textSize">12sp</item> + <item name="android:letterSpacing">0.04166667</item> + <item name="android:lineHeight">16sp</item> + <item name="android:hyphenationFrequency">normalFast</item> + <item name="android:lineBreakWordStyle">phrase</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.LabelSmall.Emphasized" + parent="@android:style/TextAppearance.DeviceDefault"> + <item name="android:textStyle">bold</item> + <item name="android:textSize">11sp</item> + <item name="android:letterSpacing">0.04545455</item> + <item name="android:lineHeight">16sp</item> + <item name="android:hyphenationFrequency">normalFast</item> + <item name="android:lineBreakWordStyle">phrase</item> + <item name="android:textAllCaps">false</item> + </style> + + <style name="TextAppearance.SettingsLib.BodyLarge.Emphasized" + parent="@android:style/TextAppearance.DeviceDefault.Medium"> + <item name="android:textStyle">normal</item> + <item name="android:textSize">16sp</item> + <item name="android:letterSpacing">0.009375</item> + <item name="android:lineHeight">24sp</item> + <item name="android:hyphenationFrequency">normalFast</item> + <item name="android:lineBreakWordStyle">phrase</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.BodyMedium.Emphasized" + parent="@android:style/TextAppearance.DeviceDefault.Medium"> + <item name="android:textStyle">normal</item> + <item name="android:textSize">14sp</item> + <item name="android:letterSpacing">0.01785714</item> + <item name="android:lineHeight">20sp</item> + <item name="android:hyphenationFrequency">normalFast</item> + <item name="android:lineBreakWordStyle">phrase</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.BodySmall.Emphasized" + parent="@android:style/TextAppearance.DeviceDefault.Medium"> + <item name="android:textStyle">normal</item> + <item name="android:textSize">12sp</item> + <item name="android:letterSpacing">0.03333333</item> + <item name="android:lineHeight">16sp</item> + <item name="android:hyphenationFrequency">normalFast</item> + <item name="android:lineBreakWordStyle">phrase</item> + <item name="android:textAllCaps">false</item> + </style> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values-v34/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v34/colors.xml index 185ac3e1fe73..60642e617a81 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v34/colors.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v34/colors.xml @@ -43,4 +43,51 @@ <color name="settingslib_text_color_primary_device_default">@android:color/system_on_surface_light</color> <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurfaceVariant--> <color name="settingslib_text_color_secondary_device_default">@android:color/system_on_surface_variant_light</color> + + <color name="settingslib_materialColorPrimary">@android:color/system_primary_light</color> + <color name="settingslib_materialColorOnPrimary">@android:color/system_on_primary_light</color> + <color name="settingslib_materialColorPrimaryContainer">@android:color/system_primary_container_light</color> + <color name="settingslib_materialColorOnPrimaryContainer">@android:color/system_on_primary_container_light</color> + <color name="settingslib_materialColorPrimaryInverse">@android:color/system_primary_dark</color> + <color name="settingslib_materialColorPrimaryFixed">@android:color/system_primary_fixed</color> + <color name="settingslib_materialColorPrimaryFixedDim">@android:color/system_primary_fixed_dim</color> + <color name="settingslib_materialColorOnPrimaryFixed">@android:color/system_on_primary_fixed</color> + <color name="settingslib_materialColorOnPrimaryFixedVariant">@android:color/system_on_primary_fixed_variant</color> + <color name="settingslib_materialColorSecondary">@android:color/system_secondary_light</color> + <color name="settingslib_materialColorOnSecondary">@android:color/system_on_secondary_light</color> + <color name="settingslib_materialColorSecondaryContainer">@android:color/system_secondary_container_light</color> + <color name="settingslib_materialColorOnSecondaryContainer">@android:color/system_on_secondary_container_light</color> + <color name="settingslib_materialColorSecondaryFixed">@android:color/system_secondary_fixed</color> + <color name="settingslib_materialColorSecondaryFixedDim">@android:color/system_secondary_fixed_dim</color> + <color name="settingslib_materialColorOnSecondaryFixed">@android:color/system_on_secondary_fixed</color> + <color name="settingslib_materialColorOnSecondaryFixedVariant">@android:color/system_on_secondary_fixed_variant</color> + <color name="settingslib_materialColorTertiary">@android:color/system_tertiary_light</color> + <color name="settingslib_materialColorOnTertiary">@android:color/system_on_tertiary_light</color> + <color name="settingslib_materialColorTertiaryContainer">@android:color/system_tertiary_container_light</color> + <color name="settingslib_materialColorOnTertiaryContainer">@android:color/system_on_tertiary_container_light</color> + <color name="settingslib_materialColorTertiaryFixed">@android:color/system_tertiary_fixed</color> + <color name="settingslib_materialColorTertiaryFixedDim">@android:color/system_tertiary_fixed_dim</color> + <color name="settingslib_materialColorOnTertiaryFixed">@android:color/system_on_tertiary_fixed</color> + <color name="settingslib_materialColorOnTertiaryFixedVariant">@android:color/system_on_tertiary_fixed_variant</color> + <color name="settingslib_materialColorError">@android:color/system_error_light</color> + <color name="settingslib_materialColorOnError">@android:color/system_on_error_light</color> + <color name="settingslib_materialColorErrorContainer">@android:color/system_error_container_light</color> + <color name="settingslib_materialColorOnErrorContainer">@android:color/system_on_error_container_light</color> + <color name="settingslib_materialColorOutline">@android:color/system_outline_light</color> + <color name="settingslib_materialColorOutlineVariant">@android:color/system_outline_variant_light</color> + <color name="settingslib_materialColorBackground">@android:color/system_background_light</color> + <color name="settingslib_materialColorOnBackground">@android:color/system_on_background_light</color> + <color name="settingslib_materialColorSurface">@android:color/system_surface_light</color> + <color name="settingslib_materialColorOnSurface">@android:color/system_on_surface_light</color> + <color name="settingslib_materialColorSurfaceVariant">@android:color/system_surface_variant_light</color> + <color name="settingslib_materialColorOnSurfaceVariant">@android:color/system_on_surface_variant_light</color> + <color name="settingslib_materialColorSurfaceInverse">@android:color/system_surface_dark</color> + <color name="settingslib_materialColorOnSurfaceInverse">@android:color/system_on_surface_dark</color> + <color name="settingslib_materialColorSurfaceBright">@android:color/system_surface_bright_light</color> + <color name="settingslib_materialColorSurfaceDim">@android:color/system_surface_dim_light</color> + <color name="settingslib_materialColorSurfaceContainer">@android:color/system_surface_container_light</color> + <color name="settingslib_materialColorSurfaceContainerLow">@android:color/system_surface_container_low_light</color> + <color name="settingslib_materialColorSurfaceContainerLowest">@android:color/system_surface_container_lowest_light</color> + <color name="settingslib_materialColorSurfaceContainerHigh">@android:color/system_surface_container_high_light</color> + <color name="settingslib_materialColorSurfaceContainerHighest">@android:color/system_surface_container_highest_light</color> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml index 90c19e1aa676..b1b37b12c572 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml @@ -54,49 +54,4 @@ <color name="settingslib_spinner_title_color">@color/settingslib_materialColorOnPrimaryContainer</color> <!-- The text color of dropdown item title --> <color name="settingslib_spinner_dropdown_color">@color/settingslib_materialColorOnPrimaryContainer</color> - - <color name="settingslib_materialColorOnSecondaryFixedVariant">@android:color/system_on_secondary_fixed_variant</color> - <color name="settingslib_materialColorOnTertiaryFixedVariant">@android:color/system_on_tertiary_fixed_variant</color> - <color name="settingslib_materialColorSurfaceContainerLowest">@android:color/system_surface_container_lowest_light</color> - <color name="settingslib_materialColorOnPrimaryFixedVariant">@android:color/system_on_primary_fixed_variant</color> - <color name="settingslib_materialColorOnSecondaryContainer">@android:color/system_on_secondary_container_light</color> - <color name="settingslib_materialColorOnTertiaryContainer">@android:color/system_on_tertiary_container_light</color> - <color name="settingslib_materialColorSurfaceContainerLow">@android:color/system_surface_container_low_light</color> - <color name="settingslib_materialColorOnPrimaryContainer">@android:color/system_on_primary_container_light</color> - <color name="settingslib_materialColorSecondaryFixedDim">@android:color/system_secondary_fixed_dim</color> - <color name="settingslib_materialColorOnErrorContainer">@android:color/system_on_error_container_light</color> - <color name="settingslib_materialColorOnSecondaryFixed">@android:color/system_on_secondary_fixed</color> - <color name="settingslib_materialColorInverseOnSurface">@android:color/system_on_surface_dark</color> - <color name="settingslib_materialColorTertiaryFixedDim">@android:color/system_tertiary_fixed_dim</color> - <color name="settingslib_materialColorOnTertiaryFixed">@android:color/system_on_tertiary_fixed</color> - <color name="settingslib_materialColorPrimaryFixedDim">@android:color/system_primary_fixed_dim</color> - <color name="settingslib_materialColorSecondaryContainer">@android:color/system_secondary_container_light</color> - <color name="settingslib_materialColorErrorContainer">@android:color/system_error_container_light</color> - <color name="settingslib_materialColorOnPrimaryFixed">@android:color/system_on_primary_fixed</color> - <color name="settingslib_materialColorInversePrimary">@android:color/system_primary_dark</color> - <color name="settingslib_materialColorSecondaryFixed">@android:color/system_secondary_fixed</color> - <color name="settingslib_materialColorInverseSurface">@android:color/system_surface_dark</color> - <color name="settingslib_materialColorSurfaceVariant">@android:color/system_surface_variant_light</color> - <color name="settingslib_materialColorTertiaryContainer">@android:color/system_tertiary_container_light</color> - <color name="settingslib_materialColorTertiaryFixed">@android:color/system_tertiary_fixed</color> - <color name="settingslib_materialColorPrimaryContainer">@android:color/system_primary_container_light</color> - <color name="settingslib_materialColorOnBackground">@android:color/system_on_background_light</color> - <color name="settingslib_materialColorPrimaryFixed">@android:color/system_primary_fixed</color> - <color name="settingslib_materialColorOnSecondary">@android:color/system_on_secondary_light</color> - <color name="settingslib_materialColorOnTertiary">@android:color/system_on_tertiary_light</color> - <color name="settingslib_materialColorSurfaceDim">@android:color/system_surface_dim_light</color> - <color name="settingslib_materialColorSurfaceBright">@android:color/system_surface_bright_light</color> - <color name="settingslib_materialColorOnError">@android:color/system_on_error_light</color> - <color name="settingslib_materialColorSurface">@android:color/system_surface_light</color> - <color name="settingslib_materialColorSurfaceContainerHigh">@android:color/system_surface_container_high_light</color> - <color name="settingslib_materialColorSurfaceContainerHighest">@android:color/system_surface_container_highest_light</color> - <color name="settingslib_materialColorOnSurfaceVariant">@android:color/system_on_surface_variant_light</color> - <color name="settingslib_materialColorOutline">@android:color/system_outline_light</color> - <color name="settingslib_materialColorOutlineVariant">@android:color/system_outline_variant_light</color> - <color name="settingslib_materialColorOnPrimary">@android:color/system_on_primary_light</color> - <color name="settingslib_materialColorOnSurface">@android:color/system_on_surface_light</color> - <color name="settingslib_materialColorSurfaceContainer">@android:color/system_surface_container_light</color> - <color name="settingslib_materialColorPrimary">@android:color/system_primary_light</color> - <color name="settingslib_materialColorSecondary">@android:color/system_secondary_light</color> - <color name="settingslib_materialColorTertiary">@android:color/system_tertiary_light</color> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml index 05a1ceacdb65..1a085681864a 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml @@ -16,150 +16,6 @@ --> <resources> - <style name="SettingsLibTextAppearance" parent="@android:style/TextAppearance.DeviceDefault"> - <!--item name="android:fontFamily"></item--> - <item name="android:hyphenationFrequency">normalFast</item> - <item name="android:lineBreakWordStyle">phrase</item> - </style> - - <style name="SettingsLibTextAppearance.Primary"> - <!--item name="android:fontFamily"></item--> - </style> - - <style name="SettingsLibTextAppearance.Primary.Display"> - <!--item name="android:fontFamily"></item--> - </style> - <style name="SettingsLibTextAppearance.Primary.Display.Large"> - <item name="android:textSize">57sp</item> - </style> - <style name="SettingsLibTextAppearance.Primary.Display.Medium"> - <item name="android:textSize">45sp</item> - </style> - <style name="SettingsLibTextAppearance.Primary.Display.Small"> - <item name="android:textSize">36sp</item> - </style> - - <style name="SettingsLibTextAppearance.Primary.Headline"> - <!--item name="android:fontFamily"></item--> - </style> - <style name="SettingsLibTextAppearance.Primary.Headline.Large"> - <item name="android:textSize">32sp</item> - </style> - <style name="SettingsLibTextAppearance.Primary.Headline.Medium"> - <item name="android:textSize">28sp</item> - </style> - <style name="SettingsLibTextAppearance.Primary.Headline.Small"> - <item name="android:textSize">24sp</item> - </style> - - <style name="SettingsLibTextAppearance.Primary.Title"> - <!--item name="android:fontFamily"></item--> - </style> - <style name="SettingsLibTextAppearance.Primary.Title.Large"> - <item name="android:textSize">22sp</item> - </style> - <style name="SettingsLibTextAppearance.Primary.Title.Medium"> - <item name="android:textSize">16sp</item> - </style> - <style name="SettingsLibTextAppearance.Primary.Title.Small"> - <item name="android:textSize">14sp</item> - </style> - - <style name="SettingsLibTextAppearance.Primary.Label"> - <!--item name="android:fontFamily"></item--> - </style> - <style name="SettingsLibTextAppearance.Primary.Label.Large"> - <item name="android:textSize">14sp</item> - </style> - <style name="SettingsLibTextAppearance.Primary.Label.Medium"> - <item name="android:textSize">12sp</item> - </style> - <style name="SettingsLibTextAppearance.Primary.Label.Small"> - <item name="android:textSize">11sp</item> - </style> - - <style name="SettingsLibTextAppearance.Primary.Body"> - <!--item name="android:fontFamily"></item--> - </style> - <style name="SettingsLibTextAppearance.Primary.Body.Large"> - <item name="android:textSize">16sp</item> - </style> - <style name="SettingsLibTextAppearance.Primary.Body.Medium"> - <item name="android:textSize">14sp</item> - </style> - <style name="SettingsLibTextAppearance.Primary.Body.Small"> - <item name="android:textSize">12sp</item> - </style> - - <style name="SettingsLibTextAppearance.Emphasized"> - <!--item name="android:fontFamily"></item--> - </style> - - <style name="SettingsLibTextAppearance.Emphasized.Display"> - <!--item name="android:fontFamily"></item--> - </style> - <style name="SettingsLibTextAppearance.Emphasized.Display.Large"> - <item name="android:textSize">57sp</item> - </style> - <style name="SettingsLibTextAppearance.Emphasized.Display.Medium"> - <item name="android:textSize">45sp</item> - </style> - <style name="SettingsLibTextAppearance.Emphasized.Display.Small"> - <item name="android:textSize">36sp</item> - </style> - - <style name="SettingsLibTextAppearance.Emphasized.Headline"> - <!--item name="android:fontFamily"></item--> - </style> - <style name="SettingsLibTextAppearance.Emphasized.Headline.Large"> - <item name="android:textSize">32sp</item> - </style> - <style name="SettingsLibTextAppearance.Emphasized.Headline.Medium"> - <item name="android:textSize">28sp</item> - </style> - <style name="SettingsLibTextAppearance.Emphasized.Headline.Small"> - <item name="android:textSize">24sp</item> - </style> - - <style name="SettingsLibTextAppearance.Emphasized.Title"> - <!--item name="android:fontFamily"></item--> - </style> - <style name="SettingsLibTextAppearance.Emphasized.Title.Large"> - <item name="android:textSize">22sp</item> - </style> - <style name="SettingsLibTextAppearance.Emphasized.Title.Medium"> - <item name="android:textSize">16sp</item> - </style> - <style name="SettingsLibTextAppearance.Emphasized.Title.Small"> - <item name="android:textSize">14sp</item> - </style> - - <style name="SettingsLibTextAppearance.Emphasized.Label"> - <!--item name="android:fontFamily"></item--> - </style> - <style name="SettingsLibTextAppearance.Emphasized.Label.Large"> - <item name="android:textSize">14sp</item> - </style> - <style name="SettingsLibTextAppearance.Emphasized.Label.Medium"> - <item name="android:textSize">12sp</item> - </style> - <style name="SettingsLibTextAppearance.Emphasized.Label.Small"> - <item name="android:textSize">11sp</item> - </style> - - <style name="SettingsLibTextAppearance.Emphasized.Body"> - <!--item name="android:fontFamily"></item--> - </style> - <style name="SettingsLibTextAppearance.Emphasized.Body.Large"> - <item name="android:textSize">16sp</item> - </style> - <style name="SettingsLibTextAppearance.Emphasized.Body.Medium"> - <item name="android:textSize">14sp</item> - </style> - <style name="SettingsLibTextAppearance.Emphasized.Body.Small"> - <item name="android:textSize">12sp</item> - </style> - <style name="SettingslibSwitchStyle.Expressive" parent=""> <item name="android:layout_width">wrap_content</item> <item name="android:layout_height">wrap_content</item> @@ -175,122 +31,6 @@ <item name="trackTint">@color/settingslib_expressive_color_main_switch_track</item> </style> - <style name="SettingsLibCardStyle" parent=""> - <item name="android:layout_width">match_parent</item> - <item name="android:layout_height">wrap_content</item> - <item name="android:layout_marginHorizontal">?android:attr/listPreferredItemPaddingStart</item> - <item name="android:layout_marginVertical">@dimen/settingslib_expressive_space_extrasmall4</item> - <item name="cardBackgroundColor">@color/settingslib_materialColorPrimary</item> - <item name="cardCornerRadius">@dimen/settingslib_expressive_radius_extralarge3</item> - <item name="cardElevation">0dp</item> - <item name="rippleColor">?android:attr/colorControlHighlight</item> - </style> - - <style name="SettingsLibButtonStyle.Expressive.Filled" - parent="@style/Widget.Material3.Button"> - <item name="android:theme">@style/Theme.Material3.DynamicColors.DayNight</item> - <item name="android:layout_width">wrap_content</item> - <item name="android:layout_height">wrap_content</item> - <item name="android:gravity">center</item> - <item name="android:minWidth">@dimen/settingslib_expressive_space_medium4</item> - <item name="android:minHeight">@dimen/settingslib_expressive_space_medium4</item> - <item name="android:paddingVertical">@dimen/settingslib_expressive_space_extrasmall5</item> - <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small1</item> - <item name="android:backgroundTint">@color/settingslib_materialColorPrimary</item> - <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item> - <item name="android:textColor">@color/settingslib_materialColorOnPrimary</item> - <item name="android:textSize">14sp</item> - <item name="iconGravity">textStart</item> - <item name="iconTint">@color/settingslib_materialColorOnPrimary</item> - <item name="iconSize">@dimen/settingslib_expressive_space_small4</item> - </style> - - <style name="SettingsLibButtonStyle.Expressive.Filled.Large"> - <item name="android:paddingVertical">@dimen/settingslib_expressive_space_small1</item> - <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small4</item> - <item name="android:textSize">16sp</item> - </style> - - <style name="SettingsLibButtonStyle.Expressive.Filled.Extra" - parent="@style/SettingsLibButtonStyle.Expressive.Filled.Large"> - <item name="android:layout_width">match_parent</item> - </style> - - <style name="SettingsLibButtonStyle.Expressive.Tonal" - parent="@style/Widget.Material3.Button.TonalButton"> - <item name="android:theme">@style/Theme.Material3.DynamicColors.DayNight</item> - <item name="android:layout_width">wrap_content</item> - <item name="android:layout_height">wrap_content</item> - <item name="android:gravity">center</item> - <item name="android:minWidth">@dimen/settingslib_expressive_space_medium4</item> - <item name="android:minHeight">@dimen/settingslib_expressive_space_medium4</item> - <item name="android:paddingVertical">@dimen/settingslib_expressive_space_extrasmall5</item> - <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small1</item> - <item name="android:backgroundTint">@color/settingslib_materialColorSecondaryContainer</item> - <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item> - <item name="android:textColor">@color/settingslib_materialColorOnSecondaryContainer</item> - <item name="android:textSize">14sp</item> - <item name="iconGravity">textStart</item> - <item name="iconTint">@color/settingslib_materialColorOnSecondaryContainer</item> - <item name="iconSize">@dimen/settingslib_expressive_space_small4</item> - </style> - - <style name="SettingsLibButtonStyle.Expressive.Tonal.Large"> - <item name="android:paddingVertical">@dimen/settingslib_expressive_space_small1</item> - <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small4</item> - <item name="android:textSize">16sp</item> - </style> - - <style name="SettingsLibButtonStyle.Expressive.Tonal.Extra" - parent="@style/SettingsLibButtonStyle.Expressive.Tonal.Large"> - <item name="android:layout_width">match_parent</item> - </style> - - <style name="SettingsLibButtonStyle.Expressive.Outline" - parent="@style/Widget.Material3.Button.OutlinedButton.Icon"> - <item name="android:theme">@style/Theme.Material3.DynamicColors.DayNight</item> - <item name="android:layout_width">wrap_content</item> - <item name="android:layout_height">wrap_content</item> - <item name="android:gravity">center</item> - <item name="android:minWidth">@dimen/settingslib_expressive_space_medium4</item> - <item name="android:minHeight">@dimen/settingslib_expressive_space_medium4</item> - <item name="android:paddingVertical">@dimen/settingslib_expressive_space_extrasmall5</item> - <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small1</item> - <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item> - <item name="android:textColor">@color/settingslib_materialColorPrimary</item> - <item name="android:textSize">14sp</item> - <item name="iconTint">@color/settingslib_materialColorPrimary</item> - <item name="iconGravity">textStart</item> - <item name="iconSize">@dimen/settingslib_expressive_space_small4</item> - <item name="iconPadding">@dimen/settingslib_expressive_space_extrasmall4</item> - <item name="strokeColor">@color/settingslib_materialColorOutlineVariant</item> - - </style> - - <style name="SettingsLibButtonStyle.Expressive.Outline.Large"> - <item name="android:paddingVertical">@dimen/settingslib_expressive_space_small1</item> - <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small4</item> - <item name="android:textSize">16sp</item> - </style> - - <style name="SettingsLibButtonStyle.Expressive.Outline.Extra" - parent="@style/SettingsLibButtonStyle.Expressive.Outline.Large"> - <item name="android:layout_width">match_parent</item> - </style> - - <style name="SettingslibTextButtonStyle.Expressive" - parent="@style/Widget.Material3.Button.TextButton.Icon"> - <item name="android:theme">@style/Theme.Material3.DynamicColors.DayNight</item> - <item name="android:layout_width">wrap_content</item> - <item name="android:layout_height">wrap_content</item> - <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item> - <item name="android:textSize">16sp</item> - <item name="android:textColor">@color/settingslib_materialColorOnSurface</item> - <item name="iconTint">@null</item> - <item name="iconPadding">@dimen/settingslib_expressive_space_extrasmall4</item> - <item name="rippleColor">?android:attr/colorControlHighlight</item> - </style> - <style name="EntityHeader"> <item name="android:paddingTop">@dimen/settingslib_expressive_space_small4</item> <item name="android:paddingBottom">@dimen/settingslib_expressive_space_small1</item> @@ -327,12 +67,11 @@ <item name="android:gravity">center</item> <item name="android:ellipsize">marquee</item> <item name="android:textDirection">locale</item> - <item name="android:textAppearance">@style/TextAppearance.EntityHeaderTitle</item> + <item name="android:textAppearance">@style/TextAppearance.SettingsLib.TitleLarge.Emphasized</item> </style> <style name="SettingslibTextAppearance.LinkableTextStyle.Expressive" - parent="@android:style/TextAppearance.DeviceDefault.WindowTitle"> - <item name="android:textSize">14sp</item> + parent="@style/TextAppearance.SettingsLib.LabelLarge"> <item name="android:textColor">?android:attr/colorAccent</item> </style> @@ -346,4 +85,14 @@ <item name="cardElevation">0dp</item> <item name="rippleColor">?android:attr/colorControlHighlight</item> </style> + + <style name="TextAppearance.SettingsLib.PreferenceTitle" + parent="@style/TextAppearance.SettingsLib.TitleMedium"> + <item name="android:textColor">@color/settingslib_text_color_primary</item> + </style> + + <style name="TextAppearance.SettingsLib.PreferenceSummary" + parent="@style/TextAppearance.SettingsLib.BodyMedium"> + <item name="android:textColor">@color/settingslib_text_color_secondary</item> + </style> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/themes_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/themes_expressive.xml index fea8739ab37d..14f214a96435 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v35/themes_expressive.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v35/themes_expressive.xml @@ -18,8 +18,8 @@ <resources> <style name="Theme.SettingsBase.Expressive"> <!-- Set up Preference title text style --> - <!--item name="android:textAppearanceListItem">@style/TextAppearance.PreferenceTitle.SettingsLib</item--> - <!--item name="android:textAppearanceListItemSecondary">@style/textAppearanceListItemSecondary</item--> + <item name="android:textAppearanceListItem">@style/TextAppearance.SettingsLib.PreferenceTitle</item> + <item name="android:textAppearanceListItemSecondary">@style/TextAppearance.SettingsLib.PreferenceSummary</item> <!-- Set up list item padding --> <item name="android:listPreferredItemPaddingStart">@dimen/settingslib_expressive_space_small1</item> diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/attrs_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values/attrs_expressive.xml index 857dd7953234..857dd7953234 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v35/attrs_expressive.xml +++ b/packages/SettingsLib/SettingsTheme/res/values/attrs_expressive.xml diff --git a/packages/SettingsLib/SettingsTheme/res/values/colors.xml b/packages/SettingsLib/SettingsTheme/res/values/colors.xml new file mode 100644 index 000000000000..c5c613b4b329 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/values/colors.xml @@ -0,0 +1,79 @@ +<?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. +--> + +<resources> + <color name="settingslib_error_0">#FFFFFF</color> + <color name="settingslib_error_10">#FFFBFF</color> + <color name="settingslib_error_50">#FFEDEA</color> + <color name="settingslib_error_100">#FFDAD6</color> + <color name="settingslib_error_200">#FFB4AB</color> + <color name="settingslib_error_300">#FF897D</color> + <color name="settingslib_error_400">#FF5449</color> + <color name="settingslib_error_500">#DE3730</color> + <color name="settingslib_error_600">#BA1A1A</color> + <color name="settingslib_error_700">#93000A</color> + <color name="settingslib_error_800">#690005</color> + <color name="settingslib_error_900">#410002</color> + <color name="settingslib_error_1000">#000000</color> + + <color name="settingslib_materialColorPrimary">#006B5F</color> + <color name="settingslib_materialColorOnPrimary">#FFFFFF</color> + <color name="settingslib_materialColorPrimaryContainer">#C5EAE2</color> + <color name="settingslib_materialColorOnPrimaryContainer">#00201C</color> + <color name="settingslib_materialColorPrimaryInverse">#83D6C7</color> + <color name="settingslib_materialColorPrimaryFixed">#C5EAE2</color> + <color name="settingslib_materialColorPrimaryFixedDim">#82D5C6</color> + <color name="settingslib_materialColorOnPrimaryFixed">#00201C</color> + <color name="settingslib_materialColorOnPrimaryFixedVariant">#005047</color> + <color name="settingslib_materialColorSecondary">#4A635E</color> + <color name="settingslib_materialColorOnSecondary">#FFFFFF</color> + <color name="settingslib_materialColorSecondaryContainer">#CCE8E2</color> + <color name="settingslib_materialColorOnSecondaryContainer">#051F1B</color> + <color name="settingslib_materialColorSecondaryFixed">#CCE8E2</color> + <color name="settingslib_materialColorSecondaryFixedDim">#B1CCC6</color> + <color name="settingslib_materialColorOnSecondaryFixed">#051F1B</color> + <color name="settingslib_materialColorOnSecondaryFixedVariant">#334C47</color> + <color name="settingslib_materialColorTertiary">#456179</color> + <color name="settingslib_materialColorOnTertiary">#FFFFFF</color> + <color name="settingslib_materialColorTertiaryContainer">#CBE6FF</color> + <color name="settingslib_materialColorOnTertiaryContainer">#001E31</color> + <color name="settingslib_materialColorTertiaryFixed">#CBE5FF</color> + <color name="settingslib_materialColorTertiaryFixedDim">#ADCAE5</color> + <color name="settingslib_materialColorOnTertiaryFixed">#001E31</color> + <color name="settingslib_materialColorOnTertiaryFixedVariant">#2D4A60</color> + <color name="settingslib_materialColorError">#B3261E</color> + <color name="settingslib_materialColorOnError">#FFFFFF</color> + <color name="settingslib_materialColorErrorContainer">#F9DEDC</color> + <color name="settingslib_materialColorOnErrorContainer">#3A0A08</color> + <color name="settingslib_materialColorOutline">#777777</color> + <color name="settingslib_materialColorOutlineVariant">#C7C6C5</color> + <color name="settingslib_materialColorBackground">#F9FAF8</color> + <color name="settingslib_materialColorOnBackground">#1B1B1B</color> + <color name="settingslib_materialColorSurface">#F9FAF8</color> + <color name="settingslib_materialColorOnSurface">#1B1B1B</color> + <color name="settingslib_materialColorSurfaceVariant">#E3E3E3</color> + <color name="settingslib_materialColorOnSurfaceVariant">#474747</color> + <color name="settingslib_materialColorSurfaceInverse">#303030</color> + <color name="settingslib_materialColorOnSurfaceInverse">#F1F1F1</color> + <color name="settingslib_materialColorSurfaceBright">#F9FAF8</color> + <color name="settingslib_materialColorSurfaceDim">#DADADA</color> + <color name="settingslib_materialColorSurfaceContainer">#EEEEEE</color> + <color name="settingslib_materialColorSurfaceContainerLow">#F4F4F4</color> + <color name="settingslib_materialColorSurfaceContainerLowest">#FFFFFF</color> + <color name="settingslib_materialColorSurfaceContainerHigh">#E8E8E8</color> + <color name="settingslib_materialColorSurfaceContainerHighest">#E3E3E3</color> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values/config.xml b/packages/SettingsLib/SettingsTheme/res/values/config.xml index e73dcc0cc559..53da49180219 100644 --- a/packages/SettingsLib/SettingsTheme/res/values/config.xml +++ b/packages/SettingsLib/SettingsTheme/res/values/config.xml @@ -16,4 +16,7 @@ --> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <bool name="settingslib_config_icon_space_reserved">true</bool> + + <!-- Name of a font family to use for headlines in SettingsLib. --> + <string name="settingslib_config_headlineFontFamily" translatable="false"></string> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/dimens_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values/dimens_expressive.xml index 0542c510fa63..0542c510fa63 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v35/dimens_expressive.xml +++ b/packages/SettingsLib/SettingsTheme/res/values/dimens_expressive.xml diff --git a/packages/SettingsLib/SettingsTheme/res/values/styles_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values/styles_expressive.xml new file mode 100644 index 000000000000..f73e100906c8 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/values/styles_expressive.xml @@ -0,0 +1,253 @@ +<?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. + --> + +<resources xmlns:tools="http://schemas.android.com/tools"> + <style name="TextAppearance.SettingsLib.DisplayLarge" + parent="@android:style/TextAppearance.DeviceDefault"> + <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item> + <item name="android:textSize">57sp</item> + <item name="android:letterSpacing">-0.00438596</item> + <item name="android:lineHeight" tools:targetApi="28">64sp</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.DisplayMedium" + parent="@android:style/TextAppearance.DeviceDefault"> + <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item> + <item name="android:textSize">45sp</item> + <item name="android:letterSpacing">0</item> + <item name="android:lineHeight" tools:targetApi="28">52sp</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.DisplaySmall" + parent="@android:style/TextAppearance.DeviceDefault"> + <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item> + <item name="android:textSize">36sp</item> + <item name="android:letterSpacing">0</item> + <item name="android:lineHeight" tools:targetApi="28">44sp</item> + <item name="android:textAllCaps">false</item> + </style> + + <style name="TextAppearance.SettingsLib.HeadlineLarge" + parent="@android:style/TextAppearance.DeviceDefault"> + <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item> + <item name="android:textSize">32sp</item> + <item name="android:letterSpacing">0</item> + <item name="android:lineHeight" tools:targetApi="28">40sp</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.HeadlineMedium" + parent="@android:style/TextAppearance.DeviceDefault"> + <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item> + <item name="android:textSize">28sp</item> + <item name="android:letterSpacing">0</item> + <item name="android:lineHeight" tools:targetApi="28">36sp</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.HeadlineSmall" + parent="@android:style/TextAppearance.DeviceDefault"> + <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item> + <item name="android:textSize">24sp</item> + <item name="android:letterSpacing">0</item> + <item name="android:lineHeight" tools:targetApi="28">32sp</item> + <item name="android:textAllCaps">false</item> + </style> + + <style name="TextAppearance.SettingsLib.TitleLarge" + parent="@android:style/TextAppearance.DeviceDefault"> + <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item> + <item name="android:textSize">22sp</item> + <item name="android:letterSpacing">0</item> + <item name="android:lineHeight" tools:targetApi="28">28sp</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.TitleMedium" + parent="@android:style/TextAppearance.DeviceDefault.Medium"> + <item name="android:textSize">16sp</item> + <item name="android:letterSpacing">0.009375</item> + <item name="android:lineHeight" tools:targetApi="28">24sp</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.TitleSmall" + parent="@android:style/TextAppearance.DeviceDefault.Medium"> + <item name="android:textSize">14sp</item> + <item name="android:letterSpacing">0.00714286</item> + <item name="android:lineHeight" tools:targetApi="28">20sp</item> + <item name="android:textAllCaps">false</item> + </style> + + <style name="TextAppearance.SettingsLib.LabelLarge" + parent="@android:style/TextAppearance.DeviceDefault.Medium"> + <item name="android:textSize">14sp</item> + <item name="android:letterSpacing">0.00714286</item> + <item name="android:lineHeight" tools:targetApi="28">20sp</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.LabelMedium" + parent="@android:style/TextAppearance.DeviceDefault.Medium"> + <item name="android:textSize">12sp</item> + <item name="android:letterSpacing">0.04166667</item> + <item name="android:lineHeight" tools:targetApi="28">16sp</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.LabelSmall" + parent="@android:style/TextAppearance.DeviceDefault.Medium"> + <item name="android:textSize">11sp</item> + <item name="android:letterSpacing">0.04545455</item> + <item name="android:lineHeight" tools:targetApi="28">16sp</item> + <item name="android:textAllCaps">false</item> + </style> + + <style name="TextAppearance.SettingsLib.BodyLarge" + parent="@android:style/TextAppearance.DeviceDefault"> + <item name="android:textSize">16sp</item> + <item name="android:letterSpacing">0.03125</item> + <item name="android:lineHeight" tools:targetApi="28">24sp</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.BodyMedium" + parent="@android:style/TextAppearance.DeviceDefault"> + <item name="android:textSize">14sp</item> + <item name="android:letterSpacing">0.01785714</item> + <item name="android:lineHeight" tools:targetApi="28">20sp</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.BodySmall" + parent="@android:style/TextAppearance.DeviceDefault"> + <item name="android:textSize">12sp</item> + <item name="android:letterSpacing">0.03333333</item> + <item name="android:lineHeight" tools:targetApi="28">16sp</item> + <item name="android:textAllCaps">false</item> + </style> + + <style name="TextAppearance.SettingsLib.DisplayLarge.Emphasized" + parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title"> + <item name="android:textSize">57sp</item> + <item name="android:letterSpacing">0</item> + <item name="android:lineHeight" tools:targetApi="28">64sp</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.DisplayMedium.Emphasized" + parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title"> + <item name="android:textSize">45sp</item> + <item name="android:letterSpacing">0</item> + <item name="android:lineHeight" tools:targetApi="28">52sp</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.DisplaySmall.Emphasized" + parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title"> + <item name="android:textSize">36sp</item> + <item name="android:letterSpacing">0</item> + <item name="android:lineHeight" tools:targetApi="28">44sp</item> + <item name="android:textAllCaps">false</item> + </style> + + <style name="TextAppearance.SettingsLib.HeadlineLarge.Emphasized" + parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title"> + <item name="android:textSize">32sp</item> + <item name="android:letterSpacing">0</item> + <item name="android:lineHeight" tools:targetApi="28">40sp</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.HeadlineMedium.Emphasized" + parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title"> + <item name="android:textSize">28sp</item> + <item name="android:letterSpacing">0</item> + <item name="android:lineHeight" tools:targetApi="28">36sp</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.HeadlineSmall.Emphasized" + parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title"> + <item name="android:textSize">24sp</item> + <item name="android:letterSpacing">0</item> + <item name="android:lineHeight" tools:targetApi="28">32sp</item> + <item name="android:textAllCaps">false</item> + </style> + + <style name="TextAppearance.SettingsLib.TitleLarge.Emphasized" + parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title"> + <item name="android:textSize">22sp</item> + <item name="android:letterSpacing">0</item> + <item name="android:lineHeight" tools:targetApi="28">28sp</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.TitleMedium.Emphasized" + parent="@android:style/TextAppearance.DeviceDefault"> + <item name="android:textStyle">bold</item> + <item name="android:textSize">16sp</item> + <item name="android:letterSpacing">0.009375</item> + <item name="android:lineHeight" tools:targetApi="28">24sp</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.TitleSmall.Emphasized" + parent="@android:style/TextAppearance.DeviceDefault"> + <item name="android:textStyle">bold</item> + <item name="android:textSize">14sp</item> + <item name="android:letterSpacing">0.00714286</item> + <item name="android:lineHeight" tools:targetApi="28">20sp</item> + <item name="android:textAllCaps">false</item> + </style> + + <style name="TextAppearance.SettingsLib.LabelLarge.Emphasized" + parent="@android:style/TextAppearance.DeviceDefault"> + <item name="android:textStyle">bold</item> + <item name="android:textSize">14sp</item> + <item name="android:letterSpacing">0.00714286</item> + <item name="android:lineHeight" tools:targetApi="28">20sp</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.LabelMedium.Emphasized" + parent="@android:style/TextAppearance.DeviceDefault"> + <item name="android:textStyle">bold</item> + <item name="android:textSize">12sp</item> + <item name="android:letterSpacing">0.04166667</item> + <item name="android:lineHeight" tools:targetApi="28">16sp</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.LabelSmall.Emphasized" + parent="@android:style/TextAppearance.DeviceDefault"> + <item name="android:textStyle">bold</item> + <item name="android:textSize">11sp</item> + <item name="android:letterSpacing">0.04545455</item> + <item name="android:lineHeight" tools:targetApi="28">16sp</item> + <item name="android:textAllCaps">false</item> + </style> + + <style name="TextAppearance.SettingsLib.BodyLarge.Emphasized" + parent="@android:style/TextAppearance.DeviceDefault.Medium"> + <item name="android:textStyle">normal</item> + <item name="android:textSize">16sp</item> + <item name="android:letterSpacing">0.009375</item> + <item name="android:lineHeight" tools:targetApi="28">24sp</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.BodyMedium.Emphasized" + parent="@android:style/TextAppearance.DeviceDefault.Medium"> + <item name="android:textStyle">normal</item> + <item name="android:textSize">14sp</item> + <item name="android:letterSpacing">0.01785714</item> + <item name="android:lineHeight" tools:targetApi="28">20sp</item> + <item name="android:textAllCaps">false</item> + </style> + <style name="TextAppearance.SettingsLib.BodySmall.Emphasized" + parent="@android:style/TextAppearance.DeviceDefault.Medium"> + <item name="android:textStyle">normal</item> + <item name="android:textSize">12sp</item> + <item name="android:letterSpacing">0.03333333</item> + <item name="android:lineHeight" tools:targetApi="28">16sp</item> + <item name="android:textAllCaps">false</item> + </style> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/StatusBannerPreference/res/layout/settingslib_expressive_preference_statusbanner.xml b/packages/SettingsLib/StatusBannerPreference/res/layout/settingslib_expressive_preference_statusbanner.xml index 9a3e5b9e1e50..083b862e8a5c 100644 --- a/packages/SettingsLib/StatusBannerPreference/res/layout/settingslib_expressive_preference_statusbanner.xml +++ b/packages/SettingsLib/StatusBannerPreference/res/layout/settingslib_expressive_preference_statusbanner.xml @@ -72,7 +72,7 @@ android:layout_height="wrap_content" android:hyphenationFrequency="normalFast" android:lineBreakWordStyle="phrase" - android:textAppearance="@style/SettingsLibTextAppearance.Emphasized.Title.Large"/> + android:textAppearance="@style/TextAppearance.SettingsLib.TitleLarge.Emphasized"/> <TextView android:id="@android:id/summary" @@ -81,7 +81,7 @@ android:hyphenationFrequency="normalFast" android:lineBreakWordStyle="phrase" android:maxLines="3" - android:textAppearance="@style/SettingsLibTextAppearance.Primary.Body.Medium"/> + android:textAppearance="@style/TextAppearance.SettingsLib.BodyMedium"/> </LinearLayout> </LinearLayout> diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/AmbientVolumeController.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/AmbientVolumeController.java new file mode 100644 index 000000000000..7f0c1263570e --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/AmbientVolumeController.java @@ -0,0 +1,415 @@ +/* + * 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.bluetooth; + +import static android.bluetooth.AudioInputControl.MUTE_DISABLED; +import static android.bluetooth.AudioInputControl.MUTE_MUTED; +import static android.bluetooth.AudioInputControl.MUTE_NOT_MUTED; + +import static com.android.settingslib.bluetooth.HearingDeviceLocalDataManager.Data.INVALID_VOLUME; + +import android.bluetooth.AudioInputControl; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.util.ArrayMap; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executor; + +/** + * AmbientVolumeController manages the {@link AudioInputControl}s of + * {@link AudioInputControl#AUDIO_INPUT_TYPE_AMBIENT} on the remote device. + */ +public class AmbientVolumeController implements LocalBluetoothProfileManager.ServiceListener { + + private static final boolean DEBUG = true; + private static final String TAG = "AmbientController"; + + private final LocalBluetoothProfileManager mProfileManager; + private final VolumeControlProfile mVolumeControlProfile; + private final Map<BluetoothDevice, List<AudioInputControl>> mDeviceAmbientControlsMap = + new ArrayMap<>(); + private final Map<BluetoothDevice, AmbientCallback> mDeviceCallbackMap = new ArrayMap<>(); + final Map<BluetoothDevice, RemoteAmbientState> mDeviceAmbientStateMap = + new ArrayMap<>(); + @Nullable + private final AmbientVolumeControlCallback mCallback; + + public AmbientVolumeController( + @NonNull LocalBluetoothProfileManager profileManager, + @Nullable AmbientVolumeControlCallback callback) { + mProfileManager = profileManager; + mVolumeControlProfile = profileManager.getVolumeControlProfile(); + if (mVolumeControlProfile != null && !mVolumeControlProfile.isProfileReady()) { + mProfileManager.addServiceListener(this); + } + mCallback = callback; + } + + @Override + public void onServiceConnected() { + if (mVolumeControlProfile != null && mVolumeControlProfile.isProfileReady()) { + mProfileManager.removeServiceListener(this); + if (mCallback != null) { + mCallback.onVolumeControlServiceConnected(); + } + } + } + + @Override + public void onServiceDisconnected() { + // Do nothing + } + + /** + * Registers the same {@link AmbientCallback} on all ambient control points of the remote + * device. The {@link AmbientCallback} will pass the event to registered + * {@link AmbientVolumeControlCallback} if exists. + * + * @param executor the executor to run the callback + * @param device the remote device + */ + public void registerCallback(@NonNull Executor executor, @NonNull BluetoothDevice device) { + AmbientCallback ambientCallback = new AmbientCallback(device, mCallback); + synchronized (mDeviceCallbackMap) { + mDeviceCallbackMap.put(device, ambientCallback); + } + + // register callback on all ambient input control points of this device + List<AudioInputControl> controls = getAmbientControls(device); + controls.forEach((control) -> { + try { + control.registerCallback(executor, ambientCallback); + } catch (IllegalArgumentException e) { + // The callback was already registered + Log.i(TAG, "Skip registering the callback, " + e.getMessage()); + } + }); + } + + /** + * Unregisters the {@link AmbientCallback} on all ambient control points of the remote + * device which is previously registered with {@link #registerCallback}. + * + * @param device the remote device + */ + public void unregisterCallback(@NonNull BluetoothDevice device) { + AmbientCallback ambientCallback; + synchronized (mDeviceCallbackMap) { + ambientCallback = mDeviceCallbackMap.remove(device); + } + if (ambientCallback == null) { + // callback not found, no need to unregister + return; + } + + // unregister callback on all ambient input control points of this device + List<AudioInputControl> controls = getAmbientControls(device); + controls.forEach(control -> { + try { + control.unregisterCallback(ambientCallback); + } catch (IllegalArgumentException e) { + // The callback was never registered or was already unregistered + Log.i(TAG, "Skip unregistering the callback, " + e.getMessage()); + } + }); + } + + /** + * Gets the gain setting max value from first ambient control point of the remote device. + * + * @param device the remote device + */ + public int getAmbientMax(@NonNull BluetoothDevice device) { + List<AudioInputControl> ambientControls = getAmbientControls(device); + int value = INVALID_VOLUME; + if (!ambientControls.isEmpty()) { + value = ambientControls.getFirst().getGainSettingMax(); + } + return value; + } + + /** + * Gets the gain setting min value from first ambient control point of the remote device. + * + * @param device the remote device + */ + public int getAmbientMin(@NonNull BluetoothDevice device) { + List<AudioInputControl> ambientControls = getAmbientControls(device); + int value = INVALID_VOLUME; + if (!ambientControls.isEmpty()) { + value = ambientControls.getFirst().getGainSettingMin(); + } + return value; + } + + /** + * Gets the latest values in {@link RemoteAmbientState}. + * + * @param device the remote device + * @return the {@link RemoteAmbientState} represents current remote ambient control point state + */ + @Nullable + public RemoteAmbientState refreshAmbientState(@Nullable BluetoothDevice device) { + if (device == null || !device.isConnected()) { + return null; + } + int gainSetting = getAmbient(device); + int mute = getMute(device); + return new RemoteAmbientState(gainSetting, mute); + } + + /** + * Gets the gain setting value from first ambient control point of the remote device and + * stores it in cached {@link RemoteAmbientState}. + * + * When any audio input point receives {@link AmbientCallback#onGainSettingChanged(int)} + * callback, only the changed value which is different from the value stored in the cached + * state will be notified to the {@link AmbientVolumeControlCallback} of this controller. + * + * @param device the remote device + */ + public int getAmbient(@NonNull BluetoothDevice device) { + List<AudioInputControl> ambientControls = getAmbientControls(device); + int value = INVALID_VOLUME; + if (!ambientControls.isEmpty()) { + synchronized (mDeviceAmbientStateMap) { + value = ambientControls.getFirst().getGainSetting(); + RemoteAmbientState state = mDeviceAmbientStateMap.getOrDefault(device, + new RemoteAmbientState(INVALID_VOLUME, MUTE_DISABLED)); + RemoteAmbientState updatedState = new RemoteAmbientState(value, state.mute); + mDeviceAmbientStateMap.put(device, updatedState); + } + } + return value; + } + + /** + * Sets the gain setting value to all ambient control points of the remote device. + * + * @param device the remote device + * @param value the gain setting value to be updated + */ + public void setAmbient(@NonNull BluetoothDevice device, int value) { + if (DEBUG) { + Log.d(TAG, "setAmbient, value:" + value + ", device:" + device); + } + List<AudioInputControl> ambientControls = getAmbientControls(device); + ambientControls.forEach(control -> control.setGainSetting(value)); + } + + /** + * Gets the mute state from first ambient control point of the remote device and + * stores it in cached {@link RemoteAmbientState}. The value will be one of + * {@link AudioInputControl.Mute}. + * + * When any audio input point receives {@link AmbientCallback#onMuteChanged(int)} callback, + * only the changed value which is different from the value stored in the cached state will + * be notified to the {@link AmbientVolumeControlCallback} of this controller. + * + * @param device the remote device + */ + public int getMute(@NonNull BluetoothDevice device) { + List<AudioInputControl> ambientControls = getAmbientControls(device); + int value = MUTE_DISABLED; + if (!ambientControls.isEmpty()) { + synchronized (mDeviceAmbientStateMap) { + value = ambientControls.getFirst().getMute(); + RemoteAmbientState state = mDeviceAmbientStateMap.getOrDefault(device, + new RemoteAmbientState(INVALID_VOLUME, MUTE_DISABLED)); + RemoteAmbientState updatedState = new RemoteAmbientState(state.gainSetting, value); + mDeviceAmbientStateMap.put(device, updatedState); + } + } + return value; + } + + /** + * Sets the mute state to all ambient control points of the remote device. + * + * @param device the remote device + * @param muted the mute state to be updated + */ + public void setMuted(@NonNull BluetoothDevice device, boolean muted) { + if (DEBUG) { + Log.d(TAG, "setMuted, muted:" + muted + ", device:" + device); + } + List<AudioInputControl> ambientControls = getAmbientControls(device); + ambientControls.forEach(control -> { + try { + control.setMute(muted ? MUTE_MUTED : MUTE_NOT_MUTED); + } catch (IllegalStateException e) { + // Sometimes remote will throw this exception due to initialization not done + // yet. Catch it to prevent crashes on UI. + Log.w(TAG, "Remote mute state is currently disabled."); + } + }); + } + + /** + * Checks if there's any valid ambient control point exists on the remote device + * + * @param device the remote device + */ + public boolean isAmbientControlAvailable(@NonNull BluetoothDevice device) { + final boolean hasAmbientControlPoint = !getAmbientControls(device).isEmpty(); + final boolean connectedToVcp = mVolumeControlProfile.getConnectionStatus(device) + == BluetoothProfile.STATE_CONNECTED; + return hasAmbientControlPoint && connectedToVcp; + } + + @NonNull + private List<AudioInputControl> getAmbientControls(@NonNull BluetoothDevice device) { + if (mVolumeControlProfile == null) { + return Collections.emptyList(); + } + synchronized (mDeviceAmbientControlsMap) { + if (mDeviceAmbientControlsMap.containsKey(device)) { + return mDeviceAmbientControlsMap.get(device); + } + List<AudioInputControl> ambientControls = + mVolumeControlProfile.getAudioInputControlServices(device).stream().filter( + this::isValidAmbientControl).toList(); + if (!ambientControls.isEmpty()) { + mDeviceAmbientControlsMap.put(device, ambientControls); + } + return ambientControls; + } + } + + private boolean isValidAmbientControl(AudioInputControl control) { + boolean isAmbientControl = + control.getAudioInputType() == AudioInputControl.AUDIO_INPUT_TYPE_AMBIENT; + boolean isManual = control.getGainMode() == AudioInputControl.GAIN_MODE_MANUAL + || control.getGainMode() == AudioInputControl.GAIN_MODE_MANUAL_ONLY; + boolean isActive = + control.getAudioInputStatus() == AudioInputControl.AUDIO_INPUT_STATUS_ACTIVE; + + return isAmbientControl && isManual && isActive; + } + + /** + * Callback providing information about the status and received events of + * {@link AmbientVolumeController}. + */ + public interface AmbientVolumeControlCallback { + + /** This method is called when the Volume Control Service is connected */ + default void onVolumeControlServiceConnected() { + } + + /** + * This method is called when one of the remote device's ambient control point's gain + * settings value is changed. + * + * @param device the remote device + * @param gainSettings the new gain setting value + */ + default void onAmbientChanged(@NonNull BluetoothDevice device, int gainSettings) { + } + + /** + * This method is called when one of the remote device's ambient control point's mute + * state is changed. + * + * @param device the remote device + * @param mute the new mute state + */ + default void onMuteChanged(@NonNull BluetoothDevice device, int mute) { + } + + /** + * This method is called when any command to the remote device's ambient control point + * is failed. + * + * @param device the remote device. + */ + default void onCommandFailed(@NonNull BluetoothDevice device) { + } + } + + /** + * A wrapper callback that will pass {@link AudioInputControl.AudioInputCallback} with extra + * device information to {@link AmbientVolumeControlCallback}. + */ + class AmbientCallback implements AudioInputControl.AudioInputCallback { + + private final BluetoothDevice mDevice; + private final AmbientVolumeControlCallback mCallback; + + AmbientCallback(@NonNull BluetoothDevice device, + @Nullable AmbientVolumeControlCallback callback) { + mDevice = device; + mCallback = callback; + } + + @Override + public void onGainSettingChanged(int gainSetting) { + if (mCallback != null) { + synchronized (mDeviceAmbientStateMap) { + RemoteAmbientState previousState = mDeviceAmbientStateMap.get(mDevice); + if (previousState.gainSetting != gainSetting) { + mCallback.onAmbientChanged(mDevice, gainSetting); + } + } + } + } + + @Override + public void onSetGainSettingFailed() { + Log.w(TAG, "onSetGainSettingFailed, device=" + mDevice); + if (mCallback != null) { + mCallback.onCommandFailed(mDevice); + } + } + + @Override + public void onMuteChanged(int mute) { + if (mCallback != null) { + synchronized (mDeviceAmbientStateMap) { + RemoteAmbientState previousState = mDeviceAmbientStateMap.get(mDevice); + if (previousState.mute != mute) { + mCallback.onMuteChanged(mDevice, mute); + } + } + } + } + + @Override + public void onSetMuteFailed() { + Log.w(TAG, "onSetMuteFailed, device=" + mDevice); + if (mCallback != null) { + mCallback.onCommandFailed(mDevice); + } + } + } + + public record RemoteAmbientState(int gainSetting, int mute) { + public boolean isMutable() { + return mute != MUTE_DISABLED; + } + public boolean isMuted() { + return mute == MUTE_MUTED; + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index b58983ff1ce8..257c93505cfc 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -1591,6 +1591,12 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> private int getHearingDeviceSummaryRes(int leftBattery, int rightBattery, boolean shortSummary) { + if (getDeviceSide() == HearingAidInfo.DeviceSide.SIDE_MONO + || getDeviceSide() == HearingAidInfo.DeviceSide.SIDE_LEFT_AND_RIGHT) { + return !shortSummary && (getBatteryLevel() > BluetoothDevice.BATTERY_LEVEL_UNKNOWN) + ? R.string.bluetooth_active_battery_level + : R.string.bluetooth_active_no_battery_level; + } boolean isLeftDeviceConnected = getConnectedHearingAidSide( HearingAidInfo.DeviceSide.SIDE_LEFT).isPresent(); boolean isRightDeviceConnected = getConnectedHearingAidSide( @@ -1646,8 +1652,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> @HearingAidInfo.DeviceSide int side) { return Stream.concat(Stream.of(this, mSubDevice), mMemberDevices.stream()) .filter(Objects::nonNull) - .filter(device -> device.getDeviceSide() == side - || device.getDeviceSide() == HearingAidInfo.DeviceSide.SIDE_LEFT_AND_RIGHT) + .filter(device -> device.getDeviceSide() == side) .filter(device -> device.getDevice().isConnected()) // For hearing aids, we should expect only one device assign to one side, but if // it happens, we don't care which one. diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java index 7fdb32cb63e9..b754706fb9a1 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java @@ -153,7 +153,7 @@ public class CachedBluetoothDeviceManager { /** * Returns device summary of the pair of the hearing aid / CSIP passed as the parameter. * - * @param CachedBluetoothDevice device + * @param device the remote device * @return Device summary, or if the pair does not exist or if it is not a hearing aid or * a CSIP set member, then {@code null}. */ @@ -394,6 +394,7 @@ public class CachedBluetoothDeviceManager { } public synchronized void onDeviceUnpaired(CachedBluetoothDevice device) { + mHearingAidDeviceManager.clearLocalDataIfNeeded(device); device.setGroupId(BluetoothCsipSetCoordinator.GROUP_ID_INVALID); CachedBluetoothDevice mainDevice = mCsipDeviceManager.findMainDevice(device); // Should iterate through the cloned set to avoid ConcurrentModificationException diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java index fa28cf6c8a76..1ca4c2b39a70 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java @@ -18,7 +18,6 @@ package com.android.settingslib.bluetooth; import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothHapClient; import android.bluetooth.BluetoothHearingAid; -import android.bluetooth.BluetoothLeAudio; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothUuid; import android.bluetooth.le.ScanFilter; @@ -308,6 +307,10 @@ public class HearingAidDeviceManager { } } + void clearLocalDataIfNeeded(CachedBluetoothDevice device) { + HearingDeviceLocalDataManager.clear(mContext, device.getDevice()); + } + private void setAudioRoutingConfig(CachedBluetoothDevice device) { AudioDeviceAttributes hearingDeviceAttributes = mRoutingHelper.getMatchedHearingDeviceAttributes(device); @@ -428,8 +431,7 @@ public class HearingAidDeviceManager { 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) { + if (hearingAidType != HapClientProfile.HearingAidType.TYPE_INVALID) { final HearingAidInfo info = new HearingAidInfo.Builder() .setLeAudioLocation(audioLocation) .setHapDeviceType(hearingAidType) diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidInfo.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidInfo.java index ef08c924b844..8399824f11e9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidInfo.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidInfo.java @@ -34,6 +34,7 @@ public class HearingAidInfo { DeviceSide.SIDE_LEFT, DeviceSide.SIDE_RIGHT, DeviceSide.SIDE_LEFT_AND_RIGHT, + DeviceSide.SIDE_MONO }) /** Side definition for hearing aids. */ @@ -42,6 +43,7 @@ public class HearingAidInfo { int SIDE_LEFT = 0; int SIDE_RIGHT = 1; int SIDE_LEFT_AND_RIGHT = 2; + int SIDE_MONO = 3; } @Retention(java.lang.annotation.RetentionPolicy.SOURCE) @@ -124,6 +126,9 @@ public class HearingAidInfo { @DeviceSide private static int convertLeAudioLocationToInternalSide(int leAudioLocation) { + if (leAudioLocation == BluetoothLeAudio.AUDIO_LOCATION_MONO) { + return DeviceSide.SIDE_MONO; + } boolean isLeft = (leAudioLocation & LE_AUDIO_LOCATION_LEFT) != 0; boolean isRight = (leAudioLocation & LE_AUDIO_LOCATION_RIGHT) != 0; if (isLeft && isRight) { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManager.java index 7a64965334c9..6725558cd2bd 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManager.java @@ -86,6 +86,17 @@ public class HearingDeviceLocalDataManager { mSettingsObserver = new SettingsObserver(ThreadUtils.getUiThreadHandler()); } + /** + * Clears the local data of the device. This method should be called when the device is + * unpaired. + */ + public static void clear(@NonNull Context context, @NonNull BluetoothDevice device) { + HearingDeviceLocalDataManager manager = new HearingDeviceLocalDataManager(context); + manager.getLocalDataFromSettings(); + manager.remove(device); + manager.putAmbientVolumeSettings(); + } + /** Starts the manager. Loads the data from Settings and start observing any changes. */ public synchronized void start() { if (mIsStarted) { @@ -141,6 +152,7 @@ public class HearingDeviceLocalDataManager { * Puts the local data of the corresponding hearing device. * * @param device the device to update the local data + * @param data the local data to be stored */ private void put(BluetoothDevice device, Data data) { if (device == null) { @@ -148,7 +160,11 @@ public class HearingDeviceLocalDataManager { } synchronized (sLock) { final String addr = device.getAnonymizedAddress(); - mAddrToDataMap.put(addr, data); + if (data == null) { + mAddrToDataMap.remove(addr); + } else { + mAddrToDataMap.put(addr, data); + } if (mListener != null && mListenerExecutor != null) { mListenerExecutor.execute(() -> mListener.onDeviceLocalDataChange(addr, data)); } @@ -156,6 +172,24 @@ public class HearingDeviceLocalDataManager { } /** + * Removes the local data of the corresponding hearing device. + * + * @param device the device to remove the local data + */ + private void remove(BluetoothDevice device) { + if (device == null) { + return; + } + synchronized (sLock) { + final String addr = device.getAnonymizedAddress(); + mAddrToDataMap.remove(addr); + if (mListener != null && mListenerExecutor != null) { + mListenerExecutor.execute(() -> mListener.onDeviceLocalDataChange(addr, null)); + } + } + } + + /** * Updates the ambient volume of the corresponding hearing device. This should be called after * {@link #start()} is called(). * diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/AmbientVolumeControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/AmbientVolumeControllerTest.java new file mode 100644 index 000000000000..abc1d226972b --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/AmbientVolumeControllerTest.java @@ -0,0 +1,318 @@ +/* + * 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.bluetooth; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.bluetooth.AudioInputControl; +import android.bluetooth.BluetoothDevice; +import android.content.Context; + +import androidx.test.core.app.ApplicationProvider; + +import org.junit.Before; +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.robolectric.RobolectricTestRunner; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; + +/** Tests for {@link AmbientVolumeController}. */ +@RunWith(RobolectricTestRunner.class) +public class AmbientVolumeControllerTest { + + @Rule + public MockitoRule mMockitoRule = MockitoJUnit.rule(); + + private static final String TEST_ADDRESS = "00:00:00:00:11"; + + @Mock + private LocalBluetoothProfileManager mProfileManager; + @Mock + private VolumeControlProfile mVolumeControlProfile; + @Mock + private AmbientVolumeController.AmbientVolumeControlCallback mCallback; + @Mock + private BluetoothDevice mDevice; + + private final Context mContext = ApplicationProvider.getApplicationContext(); + private AmbientVolumeController mVolumeController; + + @Before + public void setUp() { + when(mProfileManager.getVolumeControlProfile()).thenReturn(mVolumeControlProfile); + when(mDevice.getAddress()).thenReturn(TEST_ADDRESS); + when(mDevice.isConnected()).thenReturn(true); + mVolumeController = new AmbientVolumeController(mProfileManager, mCallback); + } + + @Test + public void onServiceConnected_notifyCallback() { + when(mVolumeControlProfile.isProfileReady()).thenReturn(true); + + mVolumeController.onServiceConnected(); + + verify(mCallback).onVolumeControlServiceConnected(); + } + + @Test + public void isAmbientControlAvailable_validControls_assertTrue() { + prepareValidAmbientControls(); + + assertThat(mVolumeController.isAmbientControlAvailable(mDevice)).isTrue(); + } + + @Test + public void isAmbientControlAvailable_streamingControls_assertFalse() { + prepareStreamingControls(); + + assertThat(mVolumeController.isAmbientControlAvailable(mDevice)).isFalse(); + } + + @Test + public void isAmbientControlAvailable_automaticAmbientControls_assertFalse() { + prepareAutomaticAmbientControls(); + + assertThat(mVolumeController.isAmbientControlAvailable(mDevice)).isFalse(); + } + + @Test + public void isAmbientControlAvailable_inactiveAmbientControls_assertFalse() { + prepareInactiveAmbientControls(); + + assertThat(mVolumeController.isAmbientControlAvailable(mDevice)).isFalse(); + } + + @Test + public void registerCallback_verifyRegisterOnAllControls() { + List<AudioInputControl> controls = prepareValidAmbientControls(); + + mVolumeController.registerCallback(mContext.getMainExecutor(), mDevice); + + for (AudioInputControl control : controls) { + verify(control).registerCallback(any(Executor.class), any()); + } + } + + @Test + public void unregisterCallback_verifyUnregisterOnAllControls() { + List<AudioInputControl> controls = prepareValidAmbientControls(); + + mVolumeController.registerCallback(mContext.getMainExecutor(), mDevice); + mVolumeController.unregisterCallback(mDevice); + + for (AudioInputControl control : controls) { + verify(control).unregisterCallback(any()); + } + } + + @Test + public void getAmbientMax_verifyGetOnFirstControl() { + List<AudioInputControl> controls = prepareValidAmbientControls(); + + mVolumeController.getAmbientMax(mDevice); + + verify(controls.getFirst()).getGainSettingMax(); + } + + @Test + public void getAmbientMin_verifyGetOnFirstControl() { + List<AudioInputControl> controls = prepareValidAmbientControls(); + + mVolumeController.getAmbientMin(mDevice); + + verify(controls.getFirst()).getGainSettingMin(); + } + + @Test + public void getAmbient_verifyGetOnFirstControl() { + List<AudioInputControl> controls = prepareValidAmbientControls(); + + mVolumeController.getAmbient(mDevice); + + verify(controls.getFirst()).getGainSetting(); + } + + @Test + public void setAmbient_verifySetOnAllControls() { + List<AudioInputControl> controls = prepareValidAmbientControls(); + + mVolumeController.setAmbient(mDevice, 10); + + for (AudioInputControl control : controls) { + verify(control).setGainSetting(10); + } + } + + @Test + public void getMute_verifyGetOnFirstControl() { + List<AudioInputControl> controls = prepareValidAmbientControls(); + + mVolumeController.getMute(mDevice); + + verify(controls.getFirst()).getMute(); + } + + @Test + public void setMuted_true_verifySetOnAllControls() { + List<AudioInputControl> controls = prepareValidAmbientControls(); + + mVolumeController.setMuted(mDevice, true); + + for (AudioInputControl control : controls) { + verify(control).setMute(AudioInputControl.MUTE_MUTED); + } + } + + @Test + public void setMuted_false_verifySetOnAllControls() { + List<AudioInputControl> controls = prepareValidAmbientControls(); + + mVolumeController.setMuted(mDevice, false); + + for (AudioInputControl control : controls) { + verify(control).setMute(AudioInputControl.MUTE_NOT_MUTED); + } + } + + @Test + public void ambientCallback_onGainSettingChanged_verifyCallbackIsCalledWhenStateChange() { + AmbientVolumeController.AmbientCallback ambientCallback = + mVolumeController.new AmbientCallback(mDevice, mCallback); + final int testAmbient = 10; + List<AudioInputControl> controls = prepareValidAmbientControls(); + when(controls.getFirst().getGainSetting()).thenReturn(testAmbient); + + mVolumeController.refreshAmbientState(mDevice); + ambientCallback.onGainSettingChanged(testAmbient); + verify(mCallback, never()).onAmbientChanged(mDevice, testAmbient); + + final int updatedTestAmbient = 20; + ambientCallback.onGainSettingChanged(updatedTestAmbient); + verify(mCallback).onAmbientChanged(mDevice, updatedTestAmbient); + } + + + @Test + public void ambientCallback_onSetAmbientFailed_verifyCallbackIsCalled() { + AmbientVolumeController.AmbientCallback ambientCallback = + mVolumeController.new AmbientCallback(mDevice, mCallback); + + ambientCallback.onSetGainSettingFailed(); + + verify(mCallback).onCommandFailed(mDevice); + } + + @Test + public void ambientCallback_onMuteChanged_verifyCallbackIsCalledWhenStateChange() { + AmbientVolumeController.AmbientCallback ambientCallback = + mVolumeController.new AmbientCallback(mDevice, mCallback); + final int testMute = 0; + List<AudioInputControl> controls = prepareValidAmbientControls(); + when(controls.getFirst().getMute()).thenReturn(testMute); + + mVolumeController.refreshAmbientState(mDevice); + ambientCallback.onMuteChanged(testMute); + verify(mCallback, never()).onMuteChanged(mDevice, testMute); + + final int updatedTestMute = 1; + ambientCallback.onMuteChanged(updatedTestMute); + verify(mCallback).onMuteChanged(mDevice, updatedTestMute); + } + + @Test + public void ambientCallback_onSetMuteFailed_verifyCallbackIsCalled() { + AmbientVolumeController.AmbientCallback ambientCallback = + mVolumeController.new AmbientCallback(mDevice, mCallback); + + ambientCallback.onSetMuteFailed(); + + verify(mCallback).onCommandFailed(mDevice); + } + + private List<AudioInputControl> prepareValidAmbientControls() { + List<AudioInputControl> controls = new ArrayList<>(); + final int controlsCount = 2; + for (int i = 0; i < controlsCount; i++) { + controls.add(prepareAudioInputControl( + AudioInputControl.AUDIO_INPUT_TYPE_AMBIENT, + AudioInputControl.GAIN_MODE_MANUAL, + AudioInputControl.AUDIO_INPUT_STATUS_ACTIVE)); + } + when(mVolumeControlProfile.getAudioInputControlServices(mDevice)).thenReturn(controls); + return controls; + } + + private List<AudioInputControl> prepareStreamingControls() { + List<AudioInputControl> controls = new ArrayList<>(); + final int controlsCount = 2; + for (int i = 0; i < controlsCount; i++) { + controls.add(prepareAudioInputControl( + AudioInputControl.AUDIO_INPUT_TYPE_STREAMING, + AudioInputControl.GAIN_MODE_MANUAL, + AudioInputControl.AUDIO_INPUT_STATUS_ACTIVE)); + } + when(mVolumeControlProfile.getAudioInputControlServices(mDevice)).thenReturn(controls); + return controls; + } + + private List<AudioInputControl> prepareAutomaticAmbientControls() { + List<AudioInputControl> controls = new ArrayList<>(); + final int controlsCount = 2; + for (int i = 0; i < controlsCount; i++) { + controls.add(prepareAudioInputControl( + AudioInputControl.AUDIO_INPUT_TYPE_STREAMING, + AudioInputControl.GAIN_MODE_AUTOMATIC, + AudioInputControl.AUDIO_INPUT_STATUS_ACTIVE)); + } + when(mVolumeControlProfile.getAudioInputControlServices(mDevice)).thenReturn(controls); + return controls; + } + + private List<AudioInputControl> prepareInactiveAmbientControls() { + List<AudioInputControl> controls = new ArrayList<>(); + final int controlsCount = 2; + for (int i = 0; i < controlsCount; i++) { + controls.add(prepareAudioInputControl( + AudioInputControl.AUDIO_INPUT_TYPE_STREAMING, + AudioInputControl.GAIN_MODE_AUTOMATIC, + AudioInputControl.AUDIO_INPUT_STATUS_INACTIVE)); + } + when(mVolumeControlProfile.getAudioInputControlServices(mDevice)).thenReturn(controls); + return controls; + } + + private AudioInputControl prepareAudioInputControl(int type, int mode, int status) { + AudioInputControl control = mock(AudioInputControl.class); + when(control.getAudioInputType()).thenReturn(type); + when(control.getGainMode()).thenReturn(mode); + when(control.getAudioInputStatus()).thenReturn(status); + return control; + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java index 8cc997414d70..05f471f62f1d 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java @@ -139,6 +139,11 @@ public class CachedBluetoothDeviceManagerTest { mCachedDevice1 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice1)); mCachedDevice2 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice2)); mCachedDevice3 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice3)); + + mHearingAidDeviceManager = spy(new HearingAidDeviceManager(mContext, mLocalBluetoothManager, + mCachedDeviceManager.mCachedDevices)); + mCachedDeviceManager.mHearingAidDeviceManager = mHearingAidDeviceManager; + doNothing().when(mHearingAidDeviceManager).clearLocalDataIfNeeded(any()); } /** @@ -338,6 +343,8 @@ public class CachedBluetoothDeviceManagerTest { // Call onDeviceUnpaired for the one in mCachedDevices. mCachedDeviceManager.onDeviceUnpaired(cachedDevice2); + + verify(mHearingAidDeviceManager).clearLocalDataIfNeeded(cachedDevice2); verify(mDevice1).removeBond(); } @@ -353,6 +360,8 @@ public class CachedBluetoothDeviceManagerTest { // Call onDeviceUnpaired for the one in mCachedDevices. mCachedDeviceManager.onDeviceUnpaired(cachedDevice1); + + verify(mHearingAidDeviceManager).clearLocalDataIfNeeded(cachedDevice1); verify(mDevice2).removeBond(); } @@ -406,9 +415,6 @@ public class CachedBluetoothDeviceManagerTest { */ @Test public void updateHearingAidDevices_directToHearingAidDeviceManager() { - mHearingAidDeviceManager = spy(new HearingAidDeviceManager(mContext, mLocalBluetoothManager, - mCachedDeviceManager.mCachedDevices)); - mCachedDeviceManager.mHearingAidDeviceManager = mHearingAidDeviceManager; mCachedDeviceManager.updateHearingAidsDevices(); verify(mHearingAidDeviceManager).updateHearingAidsDevices(); @@ -535,6 +541,7 @@ public class CachedBluetoothDeviceManagerTest { // Call onDeviceUnpaired for the one in mCachedDevices. mCachedDeviceManager.onDeviceUnpaired(cachedDevice1); + verify(mHearingAidDeviceManager).clearLocalDataIfNeeded(cachedDevice1); verify(mDevice2).removeBond(); assertThat(cachedDevice1.getGroupId()).isEqualTo( BluetoothCsipSetCoordinator.GROUP_ID_INVALID); @@ -559,6 +566,7 @@ public class CachedBluetoothDeviceManagerTest { // Call onDeviceUnpaired for the one in mCachedDevices. mCachedDeviceManager.onDeviceUnpaired(cachedDevice2); + verify(mHearingAidDeviceManager).clearLocalDataIfNeeded(cachedDevice2); verify(mDevice1).removeBond(); assertThat(cachedDevice2.getGroupId()).isEqualTo( BluetoothCsipSetCoordinator.GROUP_ID_INVALID); @@ -611,10 +619,7 @@ public class CachedBluetoothDeviceManagerTest { @Test public void onActiveDeviceChanged_validHiSyncId_callExpectedFunction() { - mHearingAidDeviceManager = spy(new HearingAidDeviceManager(mContext, mLocalBluetoothManager, - mCachedDeviceManager.mCachedDevices)); doNothing().when(mHearingAidDeviceManager).onActiveDeviceChanged(any()); - mCachedDeviceManager.mHearingAidDeviceManager = mHearingAidDeviceManager; when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1); cachedDevice1.setHearingAidInfo( diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java index bf927a1eb4cc..eb73eee90f0d 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java @@ -17,7 +17,6 @@ package com.android.settingslib.bluetooth; import static android.bluetooth.BluetoothHearingAid.HI_SYNC_ID_INVALID; import static android.bluetooth.BluetoothLeAudio.AUDIO_LOCATION_FRONT_LEFT; -import static android.bluetooth.BluetoothLeAudio.AUDIO_LOCATION_INVALID; import static com.android.settingslib.bluetooth.HapClientProfile.HearingAidType.TYPE_BINAURAL; import static com.android.settingslib.bluetooth.HapClientProfile.HearingAidType.TYPE_INVALID; @@ -272,14 +271,14 @@ public class HearingAidDeviceManagerTest { * * Conditions: * 1) LeAudio hearing aid - * 2) Invalid audio location and device type + * 2) Invalid device type * Result: * Do not set hearing aid info to the device. */ @Test public void initHearingAidDeviceIfNeeded_leAudio_invalidInfo_notToSetHearingAidInfo() { when(mCachedDevice1.getProfiles()).thenReturn(List.of(mLeAudioProfile, mHapClientProfile)); - when(mLeAudioProfile.getAudioLocation(mDevice1)).thenReturn(AUDIO_LOCATION_INVALID); + when(mLeAudioProfile.getAudioLocation(mDevice1)).thenReturn(AUDIO_LOCATION_FRONT_LEFT); when(mHapClientProfile.getHearingAidType(mDevice1)).thenReturn(TYPE_INVALID); mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, null); @@ -506,14 +505,14 @@ public class HearingAidDeviceManagerTest { * * Conditions: * 1) LeAudio hearing aid - * 2) Invalid audio location and device type + * 2) Invalid device type * Result: * Do not set hearing aid info to the device. */ @Test public void updateHearingAidsDevices_leAudio_invalidInfo_notToSetHearingAidInfo() { when(mCachedDevice1.getProfiles()).thenReturn(List.of(mLeAudioProfile, mHapClientProfile)); - when(mLeAudioProfile.getAudioLocation(mDevice1)).thenReturn(AUDIO_LOCATION_INVALID); + when(mLeAudioProfile.getAudioLocation(mDevice1)).thenReturn(AUDIO_LOCATION_FRONT_LEFT); when(mHapClientProfile.getHearingAidType(mDevice1)).thenReturn(TYPE_INVALID); mCachedDeviceManager.mCachedDevices.add(mCachedDevice1); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManagerTest.java index b659c02a2540..6d83588e0f6e 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManagerTest.java @@ -194,6 +194,19 @@ public class HearingDeviceLocalDataManagerTest { verify(mListener).onDeviceLocalDataChange(TEST_ADDRESS, newData); } + @Test + public void clear_dataIsRemoved() { + String settings = Settings.Global.getStringForUser(mContext.getContentResolver(), + Settings.Global.HEARING_DEVICE_LOCAL_AMBIENT_VOLUME, UserHandle.USER_SYSTEM); + assertThat(settings.contains(TEST_ADDRESS)).isTrue(); + + HearingDeviceLocalDataManager.clear(mContext, mDevice); + + settings = Settings.Global.getStringForUser(mContext.getContentResolver(), + Settings.Global.HEARING_DEVICE_LOCAL_AMBIENT_VOLUME, UserHandle.USER_SYSTEM); + assertThat(settings.contains(TEST_ADDRESS)).isFalse(); + } + private void prepareTestDataInSettings() { String data = generateSettingsString(TEST_ADDRESS, TEST_AMBIENT, TEST_GROUP_AMBIENT, TEST_AMBIENT_CONTROL_EXPANDED); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java index aca2c4ef2a49..91ac34ac8233 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java @@ -72,7 +72,6 @@ import java.util.regex.Pattern; public final class DeviceConfigService extends Binder { private static final List<String> sAconfigTextProtoFilesOnDevice = List.of( "/system/etc/aconfig_flags.pb", - "/system_ext/etc/aconfig_flags.pb", "/product/etc/aconfig_flags.pb", "/vendor/etc/aconfig_flags.pb"); @@ -133,12 +132,7 @@ public final class DeviceConfigService extends Binder { } pw.println("DeviceConfig provider: "); - try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(fd)) { - DeviceConfig.dump(pfd, pw, /* prefix= */ " ", args); - } catch (IOException e) { - pw.print("IOException creating ParcelFileDescriptor: "); - pw.println(e); - } + DeviceConfig.dump(pw, /* prefix= */ " ", args); } IContentProvider iprovider = mProvider.getIContentProvider(); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index 7aed61533aac..5cd534e62ea9 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -171,7 +171,6 @@ final class SettingsState { private static final List<String> sAconfigTextProtoFilesOnDevice = List.of( "/system/etc/aconfig_flags.pb", - "/system_ext/etc/aconfig_flags.pb", "/product/etc/aconfig_flags.pb", "/vendor/etc/aconfig_flags.pb"); diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt index 9fe85b7a7070..029b9cde4da9 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt @@ -38,6 +38,8 @@ import androidx.compose.ui.input.pointer.PointerId import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.PointerInputScope import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode +import androidx.compose.ui.input.pointer.changedToDownIgnoreConsumed +import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed import androidx.compose.ui.input.pointer.positionChange import androidx.compose.ui.input.pointer.util.VelocityTracker import androidx.compose.ui.input.pointer.util.addPointerInputChange @@ -50,6 +52,7 @@ import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.Velocity import androidx.compose.ui.util.fastAny +import androidx.compose.ui.util.fastSumBy import com.android.compose.modifiers.thenIf import kotlin.math.sign import kotlinx.coroutines.CoroutineScope @@ -65,9 +68,10 @@ import kotlinx.coroutines.launch interface NestedDraggable { /** * Called when a drag is started in the given [position] (*before* dragging the touch slop) and - * in the direction given by [sign]. + * in the direction given by [sign], with the given number of [pointersDown] when the touch slop + * was detected. */ - fun onDragStarted(position: Offset, sign: Float): Controller + fun onDragStarted(position: Offset, sign: Float, pointersDown: Int): Controller /** * Whether this draggable should consume any scroll amount with the given [sign] coming from a @@ -170,6 +174,9 @@ private class NestedDraggableNode( */ private var lastFirstDown: Offset? = null + /** The number of pointers down. */ + private var pointersDownCount = 0 + init { delegate(nestedScrollModifierNode(this, nestedScrollDispatcher)) } @@ -234,6 +241,11 @@ private class NestedDraggableNode( awaitEachGesture { val down = awaitFirstDown(requireUnconsumed = false) + check(down.position == lastFirstDown) { + "Position from detectDrags() is not the same as position in trackDownPosition()" + } + check(pointersDownCount == 1) { "pointersDownCount is equal to $pointersDownCount" } + var overSlop = 0f val onTouchSlopReached = { change: PointerInputChange, over: Float -> change.consume() @@ -276,10 +288,13 @@ private class NestedDraggableNode( if (drag != null) { velocityTracker.resetTracking() - val sign = (drag.position - down.position).toFloat().sign + check(pointersDownCount > 0) { "pointersDownCount is equal to $pointersDownCount" } val wrappedController = - WrappedController(coroutineScope, draggable.onDragStarted(down.position, sign)) + WrappedController( + coroutineScope, + draggable.onDragStarted(down.position, sign, pointersDownCount), + ) if (overSlop != 0f) { onDrag(wrappedController, drag, overSlop, velocityTracker) } @@ -424,7 +439,22 @@ private class NestedDraggableNode( */ private suspend fun PointerInputScope.trackDownPosition() { - awaitEachGesture { lastFirstDown = awaitFirstDown(requireUnconsumed = false).position } + awaitEachGesture { + val down = awaitFirstDown(requireUnconsumed = false) + lastFirstDown = down.position + pointersDownCount = 1 + + do { + pointersDownCount += + awaitPointerEvent().changes.fastSumBy { change -> + when { + change.changedToDownIgnoreConsumed() -> 1 + change.changedToUpIgnoreConsumed() -> -1 + else -> 0 + } + } + } while (pointersDownCount > 0) + } } override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { @@ -451,8 +481,14 @@ private class NestedDraggableNode( val sign = offset.sign if (nestedScrollController == null && draggable.shouldConsumeNestedScroll(sign)) { val startedPosition = checkNotNull(lastFirstDown) { "lastFirstDown is not set" } + + // TODO(b/382665591): Replace this by check(pointersDownCount > 0). + val pointersDown = pointersDownCount.coerceAtLeast(1) nestedScrollController = - WrappedController(coroutineScope, draggable.onDragStarted(startedPosition, sign)) + WrappedController( + coroutineScope, + draggable.onDragStarted(startedPosition, sign, pointersDown), + ) } val controller = nestedScrollController ?: return Offset.Zero diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt index fd3902fa7dc8..735ab68bc6a6 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt +++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt @@ -41,6 +41,7 @@ import androidx.compose.ui.test.swipeDown import androidx.compose.ui.test.swipeLeft import androidx.compose.ui.unit.Velocity import com.google.common.truth.Truth.assertThat +import kotlin.math.ceil import kotlinx.coroutines.awaitCancellation import org.junit.Ignore import org.junit.Rule @@ -383,6 +384,79 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw assertThat(draggable.onDragStoppedCalled).isTrue() } + @Test + fun pointersDown() { + val draggable = TestDraggable() + val touchSlop = + rule.setContentWithTouchSlop { + Box(Modifier.fillMaxSize().nestedDraggable(draggable, orientation)) + } + + (1..5).forEach { nDown -> + rule.onRoot().performTouchInput { + repeat(nDown) { pointerId -> down(pointerId, center) } + + moveBy(pointerId = 0, touchSlop.toOffset()) + } + + assertThat(draggable.onDragStartedPointersDown).isEqualTo(nDown) + + rule.onRoot().performTouchInput { + repeat(nDown) { pointerId -> up(pointerId = pointerId) } + } + } + } + + @Test + fun pointersDown_nestedScroll() { + val draggable = TestDraggable() + val touchSlop = + rule.setContentWithTouchSlop { + Box( + Modifier.fillMaxSize() + .nestedDraggable(draggable, orientation) + .nestedScrollable(rememberScrollState()) + ) + } + + (1..5).forEach { nDown -> + rule.onRoot().performTouchInput { + repeat(nDown) { pointerId -> down(pointerId, center) } + + moveBy(pointerId = 0, (touchSlop + 1f).toOffset()) + } + + assertThat(draggable.onDragStartedPointersDown).isEqualTo(nDown) + + rule.onRoot().performTouchInput { + repeat(nDown) { pointerId -> up(pointerId = pointerId) } + } + } + } + + @Test + fun pointersDown_downThenUpThenDown() { + val draggable = TestDraggable() + val touchSlop = + rule.setContentWithTouchSlop { + Box(Modifier.fillMaxSize().nestedDraggable(draggable, orientation)) + } + + val slopThird = ceil(touchSlop / 3f).toOffset() + rule.onRoot().performTouchInput { + repeat(5) { down(pointerId = it, center) } // + 5 + moveBy(pointerId = 0, slopThird) + + listOf(2, 3).forEach { up(pointerId = it) } // - 2 + moveBy(pointerId = 0, slopThird) + + listOf(5, 6, 7).forEach { down(pointerId = it, center) } // + 3 + moveBy(pointerId = 0, slopThird) + } + + assertThat(draggable.onDragStartedPointersDown).isEqualTo(6) + } + private fun ComposeContentTestRule.setContentWithTouchSlop( content: @Composable () -> Unit ): Float { @@ -413,12 +487,18 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw var onDragStartedPosition = Offset.Zero var onDragStartedSign = 0f + var onDragStartedPointersDown = 0 var onDragDelta = 0f - override fun onDragStarted(position: Offset, sign: Float): NestedDraggable.Controller { + override fun onDragStarted( + position: Offset, + sign: Float, + pointersDown: Int, + ): NestedDraggable.Controller { onDragStartedCalled = true onDragStartedPosition = position onDragStartedSign = sign + onDragStartedPointersDown = pointersDown onDragDelta = 0f onDragStarted.invoke(position, sign) diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt index 35cdf81e8c14..59d0b55c1db8 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt @@ -337,13 +337,6 @@ internal class SwipeAnimation<T : ContentKey>( check(!isAnimatingOffset()) { "SwipeAnimation.animateOffset() can only be called once" } val initialProgress = progress - // Skip the animation if we have already reached the target content and the overscroll does - // not animate anything. - val hasReachedTargetContent = - (targetContent == toContent && initialProgress >= 1f) || - (targetContent == fromContent && initialProgress <= 0f) - val skipAnimation = - hasReachedTargetContent && !contentTransition.isWithinProgressRange(initialProgress) val targetContent = if (targetContent != currentContent && !canChangeContent(targetContent)) { @@ -352,6 +345,14 @@ internal class SwipeAnimation<T : ContentKey>( targetContent } + // Skip the animation if we have already reached the target content and the overscroll does + // not animate anything. + val hasReachedTargetContent = + (targetContent == toContent && initialProgress >= 1f) || + (targetContent == fromContent && initialProgress <= 0f) + val skipAnimation = + hasReachedTargetContent && !contentTransition.isWithinProgressRange(initialProgress) + val targetOffset = if (targetContent == fromContent) { 0f diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt index 2c8dc3264b7e..b20056d54de1 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt @@ -42,7 +42,6 @@ import com.android.compose.animation.scene.content.state.TransitionState.Transit import com.android.compose.animation.scene.subjects.assertThat import com.android.compose.test.MonotonicClockTestScope import com.android.compose.test.runMonotonicClockTest -import com.android.compose.test.transition import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Deferred @@ -921,6 +920,28 @@ class DraggableHandlerTest { } @Test + fun blockTransition_animated() = runGestureTest { + assertIdle(SceneA) + layoutState.transitions = transitions { overscrollDisabled(SceneB, Orientation.Vertical) } + + // Swipe up to scene B. Overscroll 50%. + val dragController = onDragStarted(overSlop = up(1.5f), expectedConsumedOverSlop = up(1.0f)) + assertTransition(currentScene = SceneA, fromScene = SceneA, toScene = SceneB, progress = 1f) + + // Block the transition when the user release their finger. + canChangeScene = { false } + val velocityConsumed = + dragController.onDragStoppedAnimateLater(velocity = -velocityThreshold) + + // Start an animation: overscroll and from 1f to 0f. + assertTransition(currentScene = SceneA, fromScene = SceneA, toScene = SceneB, progress = 1f) + + val consumed = velocityConsumed.await() + assertThat(consumed).isEqualTo(-velocityThreshold) + assertIdle(SceneA) + } + + @Test fun scrollFromIdleWithNoTargetScene_shouldUseOverscrollSpecIfAvailable() = runGestureTest { layoutState.transitions = transitions { overscroll(SceneC, Orientation.Vertical) { fade(TestElements.Foo) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt index d75c0138bcbf..ab936590de93 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt @@ -1589,7 +1589,8 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo) assertThat(logoInfo).isNotNull() assertThat(logoInfo!!.first).isEqualTo(defaultLogoIconWithBadge) - assertThat(logoInfo!!.second).isEqualTo(defaultLogoDescriptionWithBadge) + // Logo label does not use badge info. + assertThat(logoInfo!!.second).isEqualTo(defaultLogoDescriptionFromAppInfo) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/OWNERS b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/OWNERS new file mode 100644 index 000000000000..2355c48158f7 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/OWNERS @@ -0,0 +1,3 @@ +# Bug component: 1562219 +chrisgollner@google.com +jmokut@google.com
\ No newline at end of file diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt index ae7c44e9b146..8b9ae9a0606d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt @@ -39,7 +39,7 @@ class EditTileListStateTest : SysuiTestCase() { @Test fun startDrag_listHasSpacers() { - underTest.onStarted(TestEditTiles[0]) + underTest.onStarted(TestEditTiles[0], DragType.Add) // [ a ] [ b ] [ c ] [ X ] // [ Large D ] [ e ] [ X ] @@ -51,8 +51,8 @@ class EditTileListStateTest : SysuiTestCase() { @Test fun moveDrag_listChanges() { - underTest.onStarted(TestEditTiles[4]) - underTest.onMoved(3, false) + underTest.onStarted(TestEditTiles[4], DragType.Add) + underTest.onTargeting(3, false) // Tile E goes to index 3 // [ a ] [ b ] [ c ] [ e ] @@ -65,8 +65,8 @@ class EditTileListStateTest : SysuiTestCase() { fun moveDragOnSidesOfLargeTile_listChanges() { val draggedCell = TestEditTiles[4] - underTest.onStarted(draggedCell) - underTest.onMoved(4, true) + underTest.onStarted(draggedCell, DragType.Add) + underTest.onTargeting(4, true) // Tile E goes to the right side of tile D, list is unchanged // [ a ] [ b ] [ c ] [ X ] @@ -74,7 +74,7 @@ class EditTileListStateTest : SysuiTestCase() { assertThat(underTest.tiles.toStrings()) .isEqualTo(listOf("a", "b", "c", "spacer", "d", "e", "spacer")) - underTest.onMoved(4, false) + underTest.onTargeting(4, false) // Tile E goes to the left side of tile D, they swap positions // [ a ] [ b ] [ c ] [ e ] @@ -87,8 +87,8 @@ class EditTileListStateTest : SysuiTestCase() { fun moveNewTile_tileIsAdded() { val newTile = createEditTile("newTile", 2) - underTest.onStarted(newTile) - underTest.onMoved(5, false) + underTest.onStarted(newTile, DragType.Add) + underTest.onTargeting(5, false) // New tile goes to index 5 // [ a ] [ b ] [ c ] [ X ] @@ -102,7 +102,7 @@ class EditTileListStateTest : SysuiTestCase() { @Test fun movedTileOutOfBounds_tileDisappears() { - underTest.onStarted(TestEditTiles[0]) + underTest.onStarted(TestEditTiles[0], DragType.Add) underTest.movedOutOfBounds() assertThat(underTest.tiles.toStrings()).doesNotContain(TestEditTiles[0].tile.tileSpec.spec) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt index a8d5c31873de..e93d0effe742 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt @@ -16,32 +16,26 @@ package com.android.systemui.shade.domain.interactor -import android.content.mockedContext import android.content.res.Configuration import android.content.res.mockResources import android.view.Display -import android.view.mockWindowManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.scene.ui.view.mockShadeRootView import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository import com.android.systemui.testKosmos -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.advanceUntilIdle import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mockito.inOrder +import org.mockito.Mockito.never +import org.mockito.Mockito.verify import org.mockito.kotlin.any import org.mockito.kotlin.eq import org.mockito.kotlin.mock -import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever -@OptIn(ExperimentalCoroutinesApi::class) @RunWith(AndroidJUnit4::class) @SmallTest class ShadeDisplaysInteractorTest : SysuiTestCase() { @@ -49,9 +43,7 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() { private val shadeRootview = kosmos.mockShadeRootView private val positionRepository = kosmos.fakeShadeDisplaysRepository - private val shadeContext = kosmos.mockedContext - private val testScope = kosmos.testScope - private val shadeWm = kosmos.mockWindowManager + private val shadeContext = kosmos.mockedWindowContext private val resources = kosmos.mockResources private val configuration = mock<Configuration>() private val display = mock<Display>() @@ -66,8 +58,8 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() { whenever(resources.configuration).thenReturn(configuration) whenever(shadeContext.displayId).thenReturn(0) - whenever(shadeContext.getSystemService(any())).thenReturn(shadeWm) whenever(shadeContext.resources).thenReturn(resources) + whenever(shadeContext.display).thenReturn(display) } @Test @@ -77,7 +69,7 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() { underTest.start() - verifyNoMoreInteractions(shadeWm) + verify(shadeContext, never()).reparentToDisplay(any()) } @Test @@ -87,24 +79,6 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() { underTest.start() - inOrder(shadeWm).apply { - verify(shadeWm).removeView(eq(shadeRootview)) - verify(shadeWm).addView(eq(shadeRootview), any()) - } - } - - @Test - fun start_shadePositionChanges_removedThenAdded() { - whenever(display.displayId).thenReturn(0) - positionRepository.setDisplayId(0) - underTest.start() - - positionRepository.setDisplayId(1) - testScope.advanceUntilIdle() - - inOrder(shadeWm).apply { - verify(shadeWm).removeView(eq(shadeRootview)) - verify(shadeWm).addView(eq(shadeRootview), any()) - } + verify(shadeContext).reparentToDisplay(eq(1)) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java index c9ca67e6af94..615f4b01df9b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java @@ -78,7 +78,7 @@ public class FooterViewTest extends SysuiTestCase { public void setUp() { if (NotifRedesignFooter.isEnabled()) { mView = (FooterView) LayoutInflater.from(mSpyContext).inflate( - R.layout.status_bar_notification_footer_redesign, null, false); + R.layout.notification_2025_footer, null, false); } else { mView = (FooterView) LayoutInflater.from(mSpyContext).inflate( R.layout.status_bar_notification_footer, null, false); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt index 46c360aecd48..be20bc1bf9d4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt @@ -225,16 +225,12 @@ class NotificationIconContainerStatusBarViewModelTest(flags: FlagsParameterizati val displayId = 123 darkIconRepository.darkState(displayId).value = SysuiDarkIconDispatcher.DarkChange(emptyList(), 0f, 0xAABBCC) - val iconColorsLookup by collectLastValue(underTest.iconColors(displayId)) - assertThat(iconColorsLookup).isNotNull() - - val iconColors = iconColorsLookup?.iconColors(Rect()) + val iconColors by collectLastValue(underTest.iconColors(displayId)) assertThat(iconColors).isNotNull() - iconColors!! - assertThat(iconColors.tint).isEqualTo(0xAABBCC) + assertThat(iconColors!!.tint).isEqualTo(0xAABBCC) - val staticDrawableColor = iconColors.staticDrawableColor(Rect()) + val staticDrawableColor = iconColors!!.staticDrawableColor(Rect()) assertThat(staticDrawableColor).isEqualTo(0xAABBCC) } @@ -245,8 +241,7 @@ class NotificationIconContainerStatusBarViewModelTest(flags: FlagsParameterizati val displayId = 321 darkIconRepository.darkState(displayId).value = SysuiDarkIconDispatcher.DarkChange(listOf(Rect(0, 0, 5, 5)), 0f, 0xAABBCC) - val iconColorsLookup by collectLastValue(underTest.iconColors(displayId)) - val iconColors = iconColorsLookup?.iconColors(Rect(1, 1, 4, 4)) + val iconColors by collectLastValue(underTest.iconColors(displayId)) val staticDrawableColor = iconColors?.staticDrawableColor(Rect(6, 6, 7, 7)) assertThat(staticDrawableColor).isEqualTo(DarkIconDispatcher.DEFAULT_ICON_TINT) } @@ -257,9 +252,9 @@ class NotificationIconContainerStatusBarViewModelTest(flags: FlagsParameterizati val displayId = 987 darkIconRepository.darkState(displayId).value = SysuiDarkIconDispatcher.DarkChange(listOf(Rect(0, 0, 5, 5)), 0f, 0xAABBCC) - val iconColorsLookup by collectLastValue(underTest.iconColors(displayId)) - val iconColors = iconColorsLookup?.iconColors(Rect(6, 6, 7, 7)) - assertThat(iconColors).isNull() + val iconColors by collectLastValue(underTest.iconColors(displayId)) + assertThat(iconColors!!.staticDrawableColor(Rect(6, 6, 7, 7))) + .isEqualTo(DarkIconDispatcher.DEFAULT_ICON_TINT) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/view/ViewUtilTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/view/ViewUtilTest.kt index 3dcb82811408..72527ddf65b1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/view/ViewUtilTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/view/ViewUtilTest.kt @@ -105,6 +105,20 @@ class ViewUtilTest : SysuiTestCase() { assertThat(outRect.top).isEqualTo(VIEW_TOP) assertThat(outRect.bottom).isEqualTo(VIEW_BOTTOM) } + + @Test + fun viewBoundsOnScreen_viewAnchoredAtOriginInWindow() { + // view is anchored at 0,0 in its window + view.setLeftTopRightBottom(0, 0, VIEW_RIGHT - VIEW_LEFT, VIEW_BOTTOM - VIEW_TOP) + + val outRect = Rect() + view.viewBoundsOnScreen(outRect) + + assertThat(outRect.left).isEqualTo(VIEW_LEFT) + assertThat(outRect.right).isEqualTo(VIEW_RIGHT) + assertThat(outRect.top).isEqualTo(VIEW_TOP) + assertThat(outRect.bottom).isEqualTo(VIEW_BOTTOM) + } } private const val VIEW_LEFT = 30 diff --git a/packages/SystemUI/res/layout/status_bar_notification_footer_redesign.xml b/packages/SystemUI/res/layout/notification_2025_footer.xml index 71c77a56b6a8..9b3d67f7b4a2 100644 --- a/packages/SystemUI/res/layout/status_bar_notification_footer_redesign.xml +++ b/packages/SystemUI/res/layout/notification_2025_footer.xml @@ -64,6 +64,8 @@ android:contentDescription="@string/accessibility_clear_all" android:focusable="true" android:text="@string/clear_all_notifications_text" + android:ellipsize="end" + android:maxLines="1" app:layout_constraintEnd_toStartOf="@id/settings_button" app:layout_constraintStart_toEndOf="@id/history_button" /> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index b47aa2c34b6e..12f6e6958b42 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -183,30 +183,9 @@ <item name="android:textColor">?android:attr/textColorPrimary</item> </style> - <style name="TextAppearance.AuthCredential.OldTitle"> - <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> - <item name="android:paddingTop">12dp</item> - <item name="android:paddingHorizontal">24dp</item> - <item name="android:textSize">24sp</item> - </style> - - <style name="TextAppearance.AuthCredential.OldSubtitle"> - <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> - <item name="android:paddingTop">8dp</item> - <item name="android:paddingHorizontal">24dp</item> - <item name="android:textSize">16sp</item> - </style> - - <style name="TextAppearance.AuthCredential.OldDescription"> - <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> - <item name="android:paddingTop">8dp</item> - <item name="android:paddingHorizontal">24dp</item> - <item name="android:textSize">14sp</item> - </style> - <style name="TextAppearance.AuthCredential.LogoDescription" parent="TextAppearance.Material3.LabelLarge" > <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item> - <item name="android:gravity">@integer/biometric_dialog_text_gravity</item> + <item name="android:gravity">center_horizontal</item> <item name="android:maxLines">1</item> <item name="android:textColor">@androidprv:color/materialColorOnSurfaceVariant</item> <item name="android:ellipsize">end</item> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java index 9e8cabf141ed..8576a6ebac42 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java @@ -330,6 +330,11 @@ public class Task { } @Override + public int hashCode() { + return key.hashCode(); + } + + @Override public String toString() { return "[" + key.toString() + "] " + title; } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt index df34952f4f8d..4dcf26808a9e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt @@ -982,8 +982,9 @@ private fun Context.getUserBadgedLogoInfo( activityTaskManager: ActivityTaskManager, ): Pair<Drawable?, String> { // If the app sets customized icon/description, use the passed-in value directly - var icon: Drawable? = - if (prompt.logoBitmap != null) BitmapDrawable(resources, prompt.logoBitmap) else null + val customizedIcon: Drawable? = + prompt.logoBitmap?.let { BitmapDrawable(resources, prompt.logoBitmap) } + var icon = customizedIcon var label = prompt.logoDescription ?: "" if (icon != null && label.isNotEmpty()) { return Pair(icon, label) @@ -1009,12 +1010,11 @@ private fun Context.getUserBadgedLogoInfo( } } - // Add user badge + // Add user badge for non-customized logo icon val userHandle = UserHandle.of(prompt.userInfo.userId) - if (label.isNotEmpty()) { - label = packageManager.getUserBadgedLabel(label, userHandle).toString() + if (icon != null && icon != customizedIcon) { + icon = packageManager.getUserBadgedIcon(icon, userHandle) } - icon = icon?.let { packageManager.getUserBadgedIcon(it, userHandle) } return Pair(icon, label) } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/OWNERS b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/OWNERS new file mode 100644 index 000000000000..2355c48158f7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/OWNERS @@ -0,0 +1,3 @@ +# Bug component: 1562219 +chrisgollner@google.com +jmokut@google.com
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt index 272491850c9c..af6f0cb2ec83 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt @@ -615,7 +615,7 @@ private fun Shortcut( } .focusable(interactionSource = interactionSource) .padding(8.dp) - .semantics { contentDescription = shortcut.contentDescription } + .semantics(mergeDescendants = true) { contentDescription = shortcut.contentDescription } ) { Row( modifier = diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt index 35faa97db2fe..405ce8a8e5e0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt @@ -44,19 +44,28 @@ import com.android.systemui.qs.pipeline.shared.TileSpec /** Holds the [TileSpec] of the tile being moved and receives drag and drop events. */ interface DragAndDropState { val draggedCell: SizedTile<EditTileViewModel>? + val draggedPosition: Offset val dragInProgress: Boolean + val dragType: DragType? fun isMoving(tileSpec: TileSpec): Boolean - fun onStarted(cell: SizedTile<EditTileViewModel>) + fun onStarted(cell: SizedTile<EditTileViewModel>, dragType: DragType) - fun onMoved(target: Int, insertAfter: Boolean) + fun onTargeting(target: Int, insertAfter: Boolean) + + fun onMoved(offset: Offset) fun movedOutOfBounds() fun onDrop() } +enum class DragType { + Add, + Move, +} + /** * Registers a composable as a [DragAndDropTarget] to receive drop events. Use this outside the tile * grid to catch out of bounds drops. @@ -72,6 +81,10 @@ fun Modifier.dragAndDropRemoveZone( val target = remember(dragAndDropState) { object : DragAndDropTarget { + override fun onMoved(event: DragAndDropEvent) { + dragAndDropState.onMoved(event.toOffset()) + } + override fun onDrop(event: DragAndDropEvent): Boolean { return dragAndDropState.draggedCell?.let { onDrop(it.tile.tileSpec) @@ -117,8 +130,11 @@ fun Modifier.dragAndDropTileList( } override fun onMoved(event: DragAndDropEvent) { + val offset = event.toOffset() + dragAndDropState.onMoved(offset) + // Drag offset relative to the list's top left corner - val relativeDragOffset = event.dragOffsetRelativeTo(contentOffset()) + val relativeDragOffset = offset - contentOffset() val targetItem = gridState.layoutInfo.visibleItemsInfo.firstOrNull { item -> // Check if the drag is on this item @@ -126,7 +142,7 @@ fun Modifier.dragAndDropTileList( } targetItem?.let { - dragAndDropState.onMoved(it.index, insertAfter(it, relativeDragOffset)) + dragAndDropState.onTargeting(it.index, insertAfter(it, relativeDragOffset)) } } @@ -147,8 +163,8 @@ fun Modifier.dragAndDropTileList( ) } -private fun DragAndDropEvent.dragOffsetRelativeTo(offset: Offset): Offset { - return toAndroidDragEvent().run { Offset(x, y) } - offset +private fun DragAndDropEvent.toOffset(): Offset { + return toAndroidDragEvent().run { Offset(x, y) } } private fun insertAfter(item: LazyGridItemInfo, offset: Offset): Boolean { @@ -163,6 +179,7 @@ private fun insertAfter(item: LazyGridItemInfo, offset: Offset): Boolean { fun Modifier.dragAndDropTileSource( sizedTile: SizedTile<EditTileViewModel>, dragAndDropState: DragAndDropState, + dragType: DragType, onDragStart: () -> Unit, ): Modifier { val dragState by rememberUpdatedState(dragAndDropState) @@ -172,7 +189,7 @@ fun Modifier.dragAndDropTileSource( detectDragGesturesAfterLongPress( onDrag = { _, _ -> }, onDragStart = { - dragState.onStarted(sizedTile) + dragState.onStarted(sizedTile, dragType) onDragStart() // The tilespec from the ClipData transferred isn't actually needed as we're diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt index 14abfa2313d8..868855840922 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt @@ -17,10 +17,13 @@ package com.android.systemui.qs.panels.ui.compose import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.runtime.toMutableStateList +import androidx.compose.ui.geometry.Offset import com.android.systemui.qs.panels.shared.model.SizedTile import com.android.systemui.qs.panels.ui.model.GridCell import com.android.systemui.qs.panels.ui.model.TileGridCell @@ -48,12 +51,17 @@ class EditTileListState( private val columns: Int, private val largeTilesSpan: Int, ) : DragAndDropState { - private val _draggedCell = mutableStateOf<SizedTile<EditTileViewModel>?>(null) - override val draggedCell - get() = _draggedCell.value + override var draggedCell by mutableStateOf<SizedTile<EditTileViewModel>?>(null) + private set + + override var draggedPosition by mutableStateOf(Offset.Unspecified) + private set + + override var dragType by mutableStateOf<DragType?>(null) + private set override val dragInProgress: Boolean - get() = _draggedCell.value != null + get() = draggedCell != null private val _tiles: SnapshotStateList<GridCell> = tiles.toGridCells(columns).toMutableStateList() @@ -83,18 +91,19 @@ class EditTileListState( } override fun isMoving(tileSpec: TileSpec): Boolean { - return _draggedCell.value?.let { it.tile.tileSpec == tileSpec } ?: false + return draggedCell?.let { it.tile.tileSpec == tileSpec } ?: false } - override fun onStarted(cell: SizedTile<EditTileViewModel>) { - _draggedCell.value = cell + override fun onStarted(cell: SizedTile<EditTileViewModel>, dragType: DragType) { + draggedCell = cell + this.dragType = dragType // Add spacers to the grid to indicate where the user can move a tile regenerateGrid() } - override fun onMoved(target: Int, insertAfter: Boolean) { - val draggedTile = _draggedCell.value ?: return + override fun onTargeting(target: Int, insertAfter: Boolean) { + val draggedTile = draggedCell ?: return val fromIndex = indexOf(draggedTile.tile.tileSpec) if (fromIndex == target) { @@ -115,16 +124,26 @@ class EditTileListState( regenerateGrid() } + override fun onMoved(offset: Offset) { + draggedPosition = offset + } + override fun movedOutOfBounds() { - val draggedTile = _draggedCell.value ?: return + val draggedTile = draggedCell ?: return _tiles.removeIf { cell -> cell is TileGridCell && cell.tile.tileSpec == draggedTile.tile.tileSpec } + draggedPosition = Offset.Unspecified + + // Regenerate spacers without the dragged tile + regenerateGrid() } override fun onDrop() { - _draggedCell.value = null + draggedCell = null + draggedPosition = Offset.Unspecified + dragType = null // Remove the spacers regenerateGrid() diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt index a05747dd3ba2..d975f104d538 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt @@ -20,12 +20,16 @@ package com.android.systemui.qs.panels.ui.compose.infinitegrid import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.animateContentSize +import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.LocalOverscrollFactory +import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clipScrollableContainer @@ -43,6 +47,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredHeightIn import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentSize @@ -69,6 +74,7 @@ import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -80,6 +86,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.isSpecified import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.MeasureScope @@ -111,6 +118,7 @@ import com.android.systemui.qs.panels.shared.model.SizedTile import com.android.systemui.qs.panels.shared.model.SizedTileImpl import com.android.systemui.qs.panels.ui.compose.BounceableInfo import com.android.systemui.qs.panels.ui.compose.DragAndDropState +import com.android.systemui.qs.panels.ui.compose.DragType import com.android.systemui.qs.panels.ui.compose.EditTileListState import com.android.systemui.qs.panels.ui.compose.bounceableInfo import com.android.systemui.qs.panels.ui.compose.dragAndDropRemoveZone @@ -120,6 +128,9 @@ import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TileArrangementPadding import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TileHeight import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.ToggleTargetSize +import com.android.systemui.qs.panels.ui.compose.infinitegrid.EditModeTileDefaults.AUTO_SCROLL_DISTANCE +import com.android.systemui.qs.panels.ui.compose.infinitegrid.EditModeTileDefaults.AUTO_SCROLL_SPEED +import com.android.systemui.qs.panels.ui.compose.infinitegrid.EditModeTileDefaults.AvailableTilesGridMinHeight import com.android.systemui.qs.panels.ui.compose.infinitegrid.EditModeTileDefaults.CurrentTilesGridPadding import com.android.systemui.qs.panels.ui.compose.selection.MutableSelectionState import com.android.systemui.qs.panels.ui.compose.selection.ResizableTileContainer @@ -139,8 +150,10 @@ import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.shared.model.groupAndSort import com.android.systemui.res.R +import kotlin.math.abs import kotlin.math.roundToInt import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay object TileType @@ -201,8 +214,12 @@ fun DefaultEditTileGrid( ) { innerPadding -> CompositionLocalProvider(LocalOverscrollFactory provides null) { val scrollState = rememberScrollState() - LaunchedEffect(listState.dragInProgress) { - if (listState.dragInProgress) { + + AutoScrollGrid(listState, scrollState, innerPadding) + + LaunchedEffect(listState.dragType) { + // Only scroll to the top when adding a new tile, not when reordering existing ones + if (listState.dragInProgress && listState.dragType == DragType.Add) { scrollState.animateScrollTo(0) } } @@ -223,7 +240,7 @@ fun DefaultEditTileGrid( AnimatedContent( targetState = listState.dragInProgress, modifier = Modifier.wrapContentSize(), - label = "", + label = "QSEditHeader", ) { dragIsInProgress -> EditGridHeader(Modifier.dragAndDropRemoveZone(listState, onRemoveTile)) { if (dragIsInProgress) { @@ -243,34 +260,84 @@ fun DefaultEditTileGrid( onSetTiles, ) - // Hide available tiles when dragging - AnimatedVisibility( - visible = !listState.dragInProgress, - enter = fadeIn(), - exit = fadeOut(), + // Sets a minimum height to be used when available tiles are hidden + Box( + Modifier.fillMaxWidth() + .requiredHeightIn(AvailableTilesGridMinHeight) + .animateContentSize() + .dragAndDropRemoveZone(listState, onRemoveTile) ) { - Column( - verticalArrangement = - spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)), - modifier = modifier.fillMaxSize(), + // Using the fully qualified name here as a workaround for AnimatedVisibility + // not being available from a Box + androidx.compose.animation.AnimatedVisibility( + visible = !listState.dragInProgress, + enter = fadeIn(), + exit = fadeOut(), ) { - EditGridHeader { - Text(text = stringResource(id = R.string.drag_to_add_tiles)) - } + // Hide available tiles when dragging + Column( + verticalArrangement = + spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)), + modifier = modifier.fillMaxSize(), + ) { + EditGridHeader { + Text(text = stringResource(id = R.string.drag_to_add_tiles)) + } - AvailableTileGrid(otherTiles, selectionState, columns, listState) + AvailableTileGrid(otherTiles, selectionState, columns, listState) + } } } + } + } + } +} - // Drop zone to remove tiles dragged out of the tile grid - Spacer( - modifier = - Modifier.fillMaxWidth() - .weight(1f) - .dragAndDropRemoveZone(listState, onRemoveTile) - ) +@OptIn(ExperimentalCoroutinesApi::class) +@Composable +private fun AutoScrollGrid( + listState: EditTileListState, + scrollState: ScrollState, + padding: PaddingValues, +) { + val density = LocalDensity.current + val (top, bottom) = + remember(density) { + with(density) { + padding.calculateTopPadding().roundToPx() to + padding.calculateBottomPadding().roundToPx() + } + } + val scrollTarget by + remember(listState, scrollState, top, bottom) { + derivedStateOf { + val position = listState.draggedPosition + if (position.isSpecified) { + // Return the scroll target needed based on the position of the drag movement, + // or null if we don't need to scroll + val y = position.y.roundToInt() + when { + y < AUTO_SCROLL_DISTANCE + top -> 0 + y > scrollState.viewportSize - bottom - AUTO_SCROLL_DISTANCE -> + scrollState.maxValue + else -> null + } + } else { + null + } } } + LaunchedEffect(scrollTarget) { + scrollTarget?.let { + // Change the duration of the animation based on the distance to maintain the + // same scrolling speed + val distance = abs(it - scrollState.value) + scrollState.animateScrollTo( + it, + animationSpec = + tween(durationMillis = distance * AUTO_SCROLL_SPEED, easing = LinearEasing), + ) + } } } @@ -423,7 +490,7 @@ private fun AvailableTileGrid( } fun gridHeight(rows: Int, tileHeight: Dp, tilePadding: Dp, gridPadding: Dp): Dp { - return ((tileHeight + tilePadding) * rows) - tilePadding + gridPadding * 2 + return ((tileHeight + tilePadding) * rows) + gridPadding * 2 } private fun GridCell.key(index: Int, dragAndDropState: DragAndDropState): Any { @@ -596,6 +663,7 @@ private fun TileGridCell( .dragAndDropTileSource( SizedTileImpl(cell.tile, cell.width), dragAndDropState, + DragType.Move, selectionState::unSelect, ) .tileBackground(colors.background) @@ -631,7 +699,11 @@ private fun AvailableTileGridCell( onClick(onClickActionName) { false } this.stateDescription = stateDescription } - .dragAndDropTileSource(SizedTileImpl(cell.tile, cell.width), dragAndDropState) { + .dragAndDropTileSource( + SizedTileImpl(cell.tile, cell.width), + dragAndDropState, + DragType.Add, + ) { selectionState.unSelect() } .tileBackground(colors.background) @@ -739,7 +811,10 @@ private fun Modifier.tileBackground(color: Color): Modifier { private object EditModeTileDefaults { const val PLACEHOLDER_ALPHA = .3f + const val AUTO_SCROLL_DISTANCE = 100 + const val AUTO_SCROLL_SPEED = 2 // 2ms per pixel val CurrentTilesGridPadding = 8.dp + val AvailableTilesGridMinHeight = 200.dp @Composable fun editTileColors(): TileColors = diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java index 30b6892731f1..c241f2165c8f 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java @@ -62,7 +62,7 @@ public class ToggleSeekBar extends SeekBar { } else if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) { setHovered(false); } - return true; + return super.onHoverEvent(event); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java index f5fc1f414f82..bf672be3c8d0 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java @@ -166,6 +166,15 @@ public class NotificationShadeWindowView extends WindowRootView { } @Override + public void onMovedToDisplay(int displayId, Configuration config) { + super.onMovedToDisplay(displayId, config); + ShadeWindowGoesAround.isUnexpectedlyInLegacyMode(); + // When the window is moved we're only receiving a call to this method instead of the + // onConfigurationChange itself. Let's just trigegr a normal config change. + onConfigurationChanged(config); + } + + @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if (mConfigurationForwarder != null) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt index ff39a3ddc17c..a002aa53736a 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt @@ -22,6 +22,7 @@ import android.view.LayoutInflater import android.view.WindowManager import android.view.WindowManager.LayoutParams import android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE +import android.window.WindowContext import com.android.systemui.CoreStartable import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.common.ui.ConfigurationStateImpl @@ -81,6 +82,19 @@ object ShadeDisplayAwareModule { @Provides @ShadeDisplayAware @SysUISingleton + fun provideShadeDisplayAwareWindowContext(@ShadeDisplayAware context: Context): WindowContext { + ShadeWindowGoesAround.isUnexpectedlyInLegacyMode() + // We rely on the fact context is a WindowContext as the API to reparent windows is only + // available there. + return (context as? WindowContext) + ?: error( + "ShadeDisplayAware context must be a window context to allow window reparenting." + ) + } + + @Provides + @ShadeDisplayAware + @SysUISingleton fun provideShadeWindowLayoutParams(@ShadeDisplayAware context: Context): LayoutParams { return ShadeWindowLayoutParams.create(context) } @@ -203,7 +217,9 @@ object ShadeDisplayAwareModule { @Provides @IntoMap @ClassKey(ShadePrimaryDisplayCommand::class) - fun provideShadePrimaryDisplayCommand(impl: Provider<ShadePrimaryDisplayCommand>): CoreStartable { + fun provideShadePrimaryDisplayCommand( + impl: Provider<ShadePrimaryDisplayCommand> + ): CoreStartable { return if (ShadeWindowGoesAround.isEnabled) { impl.get() } else { diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt index 34148671cf2a..08c03e28d596 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt @@ -16,10 +16,8 @@ package com.android.systemui.shade.domain.interactor -import android.content.Context import android.util.Log -import android.view.WindowManager -import android.view.WindowManager.LayoutParams +import android.window.WindowContext import androidx.annotation.UiThread import com.android.app.tracing.coroutines.launchTraced import com.android.app.tracing.traceSection @@ -45,9 +43,7 @@ class ShadeDisplaysInteractor constructor( optionalShadeRootView: Optional<WindowRootView>, private val shadePositionRepository: ShadeDisplaysRepository, - @ShadeDisplayAware private val shadeContext: Context, - @ShadeDisplayAware private val shadeLayoutParams: LayoutParams, - @ShadeDisplayAware private val wm: WindowManager, + @ShadeDisplayAware private val shadeContext: WindowContext, @Background private val bgScope: CoroutineScope, @Main private val mainThreadContext: CoroutineContext, ) : CoreStartable { @@ -72,7 +68,11 @@ constructor( /** Tries to move the shade. If anything wrong happens, fails gracefully without crashing. */ private suspend fun moveShadeWindowTo(destinationId: Int) { Log.d(TAG, "Trying to move shade window to display with id $destinationId") - val currentDisplay = shadeRootView.display + // Why using the shade context here instead of the view's Display? + // The context's display is updated before the view one, so it is a better indicator of + // which display the shade is supposed to be at. The View display is updated after the first + // rendering with the new config. + val currentDisplay = shadeContext.display if (currentDisplay == null) { Log.w(TAG, "Current shade display is null") return @@ -83,7 +83,7 @@ constructor( return } try { - withContext(mainThreadContext) { moveShadeWindow(toId = destinationId) } + withContext(mainThreadContext) { reparentToDisplayId(id = destinationId) } } catch (e: IllegalStateException) { Log.e( TAG, @@ -94,25 +94,8 @@ constructor( } @UiThread - private fun moveShadeWindow(toId: Int) { - traceSection({ "moveShadeWindow to $toId" }) { - removeShadeWindow() - updateContextDisplay(toId) - addShadeWindow() - } - } - - @UiThread - private fun removeShadeWindow(): Unit = - traceSection("removeShadeWindow") { wm.removeView(shadeRootView) } - - @UiThread - private fun addShadeWindow(): Unit = - traceSection("addShadeWindow") { wm.addView(shadeRootView, shadeLayoutParams) } - - @UiThread - private fun updateContextDisplay(newDisplayId: Int) { - traceSection("updateContextDisplay") { shadeContext.updateDisplay(newDisplayId) } + private fun reparentToDisplayId(id: Int) { + traceSection({ "reparentToDisplayId(id=$id)" }) { shadeContext.reparentToDisplay(id) } } private companion object { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt index 6dbb71463602..643ee249e75e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.notification.icon.ui.viewbinder import android.graphics.Color -import android.graphics.Rect import android.util.Log import android.view.View import android.view.ViewGroup @@ -53,7 +52,6 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.stateIn /** Binds a view-model to a [NotificationIconContainer]. */ @@ -71,10 +69,7 @@ object NotificationIconContainerViewBinder { launch { val contrastColorUtil = ContrastColorUtil.getInstance(view.context) val iconColors: StateFlow<NotificationIconColors> = - viewModel - .iconColors(displayId) - .mapNotNull { it.iconColors(view.viewBounds) } - .stateIn(this) + viewModel.iconColors(displayId).stateIn(this) viewModel.icons.bindIcons( logTag = "statusbar", view = view, @@ -374,18 +369,6 @@ fun NotifCollection.iconViewStoreBy(block: (IconPack) -> StatusBarIconView?) = getEntry(key)?.icons?.let(block) } -private val View.viewBounds: Rect - get() { - val tmpArray = intArrayOf(0, 0) - getLocationOnScreen(tmpArray) - return Rect( - /* left = */ tmpArray[0], - /* top = */ tmpArray[1], - /* right = */ left + width, - /* bottom = */ top + height, - ) - } - private suspend inline fun <T> Flow<T>.collectTracingEach( tag: String, crossinline collector: (T) -> Unit, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt index 83f56a092bc6..124bd2eece36 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt @@ -16,8 +16,6 @@ package com.android.systemui.statusbar.notification.icon.ui.viewbinder -import android.graphics.Rect -import android.view.View import com.android.app.tracing.traceSection import com.android.internal.util.ContrastColorUtil import com.android.systemui.res.R @@ -25,6 +23,7 @@ import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.StatusBarIconView.NO_COLOR import com.android.systemui.statusbar.notification.NotificationUtils import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconColors +import com.android.systemui.util.view.viewBoundsOnScreen import kotlinx.coroutines.flow.Flow object StatusBarIconViewBinder { @@ -60,25 +59,13 @@ object StatusBarIconViewBinder { val isPreL = java.lang.Boolean.TRUE == view.getTag(R.id.icon_is_pre_L) val isColorized = !isPreL || NotificationUtils.isGrayscale(view, contrastColorUtil) view.staticDrawableColor = - if (isColorized) colors.staticDrawableColor(view.viewBounds) else NO_COLOR + if (isColorized) colors.staticDrawableColor(view.viewBoundsOnScreen()) else NO_COLOR // Set the color for the overflow dot view.setDecorColor(colors.tint) } } } -private val View.viewBounds: Rect - get() { - val tmpArray = intArrayOf(0, 0) - getLocationOnScreen(tmpArray) - return Rect( - /* left = */ tmpArray[0], - /* top = */ tmpArray[1], - /* right = */ left + width, - /* bottom = */ top + height, - ) - } - private suspend inline fun <T> Flow<T>.collectTracingEach( tag: String, crossinline collector: (T) -> Unit, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconColors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconColors.kt index 2365db451836..a9635dcd2bc9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconColors.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconColors.kt @@ -17,14 +17,6 @@ package com.android.systemui.statusbar.notification.icon.ui.viewmodel import android.graphics.Rect -/** - * Lookup the colors to use for the notification icons based on the bounds of the icon container. A - * result of `null` indicates that no color changes should be applied. - */ -fun interface NotificationIconColorLookup { - fun iconColors(viewBounds: Rect): NotificationIconColors? -} - /** Colors to apply to notification icons. */ interface NotificationIconColors { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt index f0b03065e637..2ba28a660116 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt @@ -68,18 +68,10 @@ constructor( .distinctUntilChanged() /** The colors with which to display the notification icons. */ - fun iconColors(displayId: Int): Flow<NotificationIconColorLookup> = + fun iconColors(displayId: Int): Flow<NotificationIconColors> = darkIconInteractor .darkState(displayId) - .map { (areas: Collection<Rect>, tint: Int) -> - NotificationIconColorLookup { viewBounds: Rect -> - if (DarkIconDispatcher.isInAreas(areas, viewBounds)) { - IconColorsImpl(tint, areas) - } else { - null - } - } - } + .map { (areas: Collection<Rect>, tint: Int) -> IconColorsImpl(tint, areas) } .flowOn(bgContext) .conflate() .distinctUntilChanged() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt index f8aff69f0531..9d13ab53ec71 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt @@ -21,7 +21,6 @@ import android.content.Context import android.graphics.drawable.AnimatedImageDrawable import android.view.View import android.view.ViewGroup -import android.view.ViewGroup.MarginLayoutParams import com.android.internal.widget.CachingIconView import com.android.internal.widget.ConversationLayout import com.android.internal.widget.MessagingGroup @@ -94,13 +93,6 @@ class NotificationConversationTemplateViewWrapper( // Reinspect the notification. Before the super call, because the super call also updates // the transformation types and we need to have our values set by then. resolveViews() - if (Flags.notificationsRedesignAppIcons() && row.isShowingAppIcon) { - // Override the margins to be 2dp instead of 4dp according to the new design if we're - // showing the app icon. - val lp = badgeIconView.layoutParams as MarginLayoutParams - lp.setMargins(2, 2, 2, 2) - badgeIconView.layoutParams = lp - } super.onContentUpdated(row) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt index bffcae99e7f6..b4561686b7b2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt @@ -147,8 +147,7 @@ constructor( // The footer needs to be re-inflated every time the theme or the font size changes. configuration .inflateLayout<FooterView>( - if (NotifRedesignFooter.isEnabled) - R.layout.status_bar_notification_footer_redesign + if (NotifRedesignFooter.isEnabled) R.layout.notification_2025_footer else R.layout.status_bar_notification_footer, parentView, attachToRoot = false, diff --git a/packages/SystemUI/src/com/android/systemui/util/view/ViewUtil.kt b/packages/SystemUI/src/com/android/systemui/util/view/ViewUtil.kt index 6160b00379ef..5b48c1ff628c 100644 --- a/packages/SystemUI/src/com/android/systemui/util/view/ViewUtil.kt +++ b/packages/SystemUI/src/com/android/systemui/util/view/ViewUtil.kt @@ -35,27 +35,21 @@ class ViewUtil @Inject constructor() { fun touchIsWithinView(view: View, x: Float, y: Float): Boolean { val left = view.locationOnScreen[0] val top = view.locationOnScreen[1] - return left <= x && - x <= left + view.width && - top <= y && - y <= top + view.height + return left <= x && x <= left + view.width && top <= y && y <= top + view.height } - /** - * Sets [outRect] to be the view's location within its window. - */ - fun setRectToViewWindowLocation(view: View, outRect: Rect) { - val locInWindow = IntArray(2) - view.getLocationInWindow(locInWindow) - - val x = locInWindow[0] - val y = locInWindow[1] - - outRect.set( - x, - y, - x + view.width, - y + view.height, - ) - } + /** Sets [outRect] to be the view's location within its window. */ + fun setRectToViewWindowLocation(view: View, outRect: Rect) = view.viewBoundsOnScreen(outRect) +} + +fun View.viewBoundsOnScreen(outRect: Rect) { + val locInWindow = IntArray(2) + getLocationInWindow(locInWindow) + + val x = locInWindow[0] + val y = locInWindow[1] + + outRect.set(x, y, x + width, y + height) } + +fun View.viewBoundsOnScreen(): Rect = Rect().also { viewBoundsOnScreen(it) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt index d2317e4f533d..fc720b836f72 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt @@ -87,7 +87,7 @@ class DragAndDropTest : SysuiTestCase() { } composeRule.waitForIdle() - listState.onStarted(TestEditTiles[0]) + listState.onStarted(TestEditTiles[0], DragType.Add) // Tile is being dragged, it should be replaced with a placeholder composeRule.onNodeWithContentDescription("tileA").assertDoesNotExist() @@ -113,8 +113,8 @@ class DragAndDropTest : SysuiTestCase() { } composeRule.waitForIdle() - listState.onStarted(TestEditTiles[0]) - listState.onMoved(1, false) + listState.onStarted(TestEditTiles[0], DragType.Add) + listState.onTargeting(1, false) listState.onDrop() // Available tiles should re-appear @@ -140,7 +140,7 @@ class DragAndDropTest : SysuiTestCase() { } composeRule.waitForIdle() - listState.onStarted(TestEditTiles[0]) + listState.onStarted(TestEditTiles[0], DragType.Add) listState.movedOutOfBounds() listState.onDrop() @@ -165,11 +165,11 @@ class DragAndDropTest : SysuiTestCase() { } composeRule.waitForIdle() - listState.onStarted(createEditTile("newTile")) + listState.onStarted(createEditTile("newTile"), DragType.Add) // Insert after tileD, which is at index 4 // [ a ] [ b ] [ c ] [ empty ] // [ tile d ] [ e ] - listState.onMoved(4, insertAfter = true) + listState.onTargeting(4, insertAfter = true) listState.onDrop() // Available tiles should re-appear diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt index db4df38e038a..f2af619a4ad7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt @@ -17,25 +17,24 @@ package com.android.systemui.shade.domain.interactor import android.content.mockedContext -import android.view.mockWindowManager +import android.window.WindowContext import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.scene.ui.view.mockShadeRootView import com.android.systemui.shade.ShadeWindowLayoutParams import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository import java.util.Optional +import org.mockito.kotlin.mock -val Kosmos.shadeLayoutParams by Kosmos.Fixture { - ShadeWindowLayoutParams.create(mockedContext) -} +val Kosmos.shadeLayoutParams by Kosmos.Fixture { ShadeWindowLayoutParams.create(mockedContext) } + +val Kosmos.mockedWindowContext by Kosmos.Fixture { mock<WindowContext>() } val Kosmos.shadeDisplaysInteractor by Kosmos.Fixture { ShadeDisplaysInteractor( Optional.of(mockShadeRootView), fakeShadeDisplaysRepository, - mockedContext, - shadeLayoutParams, - mockWindowManager, + mockedWindowContext, testScope.backgroundScope, testScope.backgroundScope.coroutineContext, ) diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Combinators.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Combinators.kt index 8bf3a43765ae..ae9b8c85910f 100644 --- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Combinators.kt +++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Combinators.kt @@ -17,6 +17,7 @@ package com.android.systemui.kairos import com.android.systemui.kairos.util.These +import com.android.systemui.kairos.util.WithPrev import com.android.systemui.kairos.util.just import com.android.systemui.kairos.util.none import kotlinx.coroutines.flow.Flow @@ -27,15 +28,18 @@ import kotlinx.coroutines.flow.conflate * Returns a [TFlow] that emits the value sampled from the [Transactional] produced by each emission * of the original [TFlow], within the same transaction of the original emission. */ +@ExperimentalFrpApi fun <A> TFlow<Transactional<A>>.sampleTransactionals(): TFlow<A> = map { it.sample() } /** @see FrpTransactionScope.sample */ +@ExperimentalFrpApi fun <A, B, C> TFlow<A>.sample( state: TState<B>, transform: suspend FrpTransactionScope.(A, B) -> C, ): TFlow<C> = map { transform(it, state.sample()) } /** @see FrpTransactionScope.sample */ +@ExperimentalFrpApi fun <A, B, C> TFlow<A>.sample( transactional: Transactional<B>, transform: suspend FrpTransactionScope.(A, B) -> C, @@ -50,6 +54,7 @@ fun <A, B, C> TFlow<A>.sample( * * @see sample */ +@ExperimentalFrpApi fun <A, B, C> TFlow<A>.samplePromptly( state: TState<B>, transform: suspend FrpTransactionScope.(A, B) -> C, @@ -70,19 +75,10 @@ fun <A, B, C> TFlow<A>.samplePromptly( } /** - * Returns a [TState] containing a map with a snapshot of the current state of each [TState] in the - * original map. - */ -fun <K, A> Map<K, TState<A>>.combineValues(): TState<Map<K, A>> = - asIterable() - .map { (k, state) -> state.map { v -> k to v } } - .combine() - .map { entries -> entries.toMap() } - -/** * Returns a cold [Flow] that, when collected, emits from this [TFlow]. [network] is needed to * transactionally connect to / disconnect from the [TFlow] when collection starts/stops. */ +@ExperimentalFrpApi fun <A> TFlow<A>.toColdConflatedFlow(network: FrpNetwork): Flow<A> = channelFlow { network.activateSpec { observe { trySend(it) } } }.conflate() @@ -90,6 +86,7 @@ fun <A> TFlow<A>.toColdConflatedFlow(network: FrpNetwork): Flow<A> = * Returns a cold [Flow] that, when collected, emits from this [TState]. [network] is needed to * transactionally connect to / disconnect from the [TState] when collection starts/stops. */ +@ExperimentalFrpApi fun <A> TState<A>.toColdConflatedFlow(network: FrpNetwork): Flow<A> = channelFlow { network.activateSpec { observe { trySend(it) } } }.conflate() @@ -99,6 +96,7 @@ fun <A> TState<A>.toColdConflatedFlow(network: FrpNetwork): Flow<A> = * * When collection is cancelled, so is the [FrpSpec]. This means all ongoing work is cleaned up. */ +@ExperimentalFrpApi @JvmName("flowSpecToColdConflatedFlow") fun <A> FrpSpec<TFlow<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<A> = channelFlow { network.activateSpec { applySpec().observe { trySend(it) } } }.conflate() @@ -109,6 +107,7 @@ fun <A> FrpSpec<TFlow<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<A> = * * When collection is cancelled, so is the [FrpSpec]. This means all ongoing work is cleaned up. */ +@ExperimentalFrpApi @JvmName("stateSpecToColdConflatedFlow") fun <A> FrpSpec<TState<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<A> = channelFlow { network.activateSpec { applySpec().observe { trySend(it) } } }.conflate() @@ -117,6 +116,7 @@ fun <A> FrpSpec<TState<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<A> = * Returns a cold [Flow] that, when collected, applies this [Transactional] in a new transaction in * this [network], and then emits from the returned [TFlow]. */ +@ExperimentalFrpApi @JvmName("transactionalFlowToColdConflatedFlow") fun <A> Transactional<TFlow<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<A> = channelFlow { network.activateSpec { sample().observe { trySend(it) } } }.conflate() @@ -125,6 +125,7 @@ fun <A> Transactional<TFlow<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<A * Returns a cold [Flow] that, when collected, applies this [Transactional] in a new transaction in * this [network], and then emits from the returned [TState]. */ +@ExperimentalFrpApi @JvmName("transactionalStateToColdConflatedFlow") fun <A> Transactional<TState<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<A> = channelFlow { network.activateSpec { sample().observe { trySend(it) } } }.conflate() @@ -135,6 +136,7 @@ fun <A> Transactional<TState<A>>.toColdConflatedFlow(network: FrpNetwork): Flow< * * When collection is cancelled, so is the [FrpStateful]. This means all ongoing work is cleaned up. */ +@ExperimentalFrpApi @JvmName("statefulFlowToColdConflatedFlow") fun <A> FrpStateful<TFlow<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<A> = channelFlow { network.activateSpec { applyStateful().observe { trySend(it) } } }.conflate() @@ -145,11 +147,13 @@ fun <A> FrpStateful<TFlow<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<A> * * When collection is cancelled, so is the [FrpStateful]. This means all ongoing work is cleaned up. */ +@ExperimentalFrpApi @JvmName("statefulStateToColdConflatedFlow") fun <A> FrpStateful<TState<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<A> = channelFlow { network.activateSpec { applyStateful().observe { trySend(it) } } }.conflate() /** Return a [TFlow] that emits from the original [TFlow] only when [state] is `true`. */ +@ExperimentalFrpApi fun <A> TFlow<A>.filter(state: TState<Boolean>): TFlow<A> = filter { state.sample() } private fun Iterable<Boolean>.allTrue() = all { it } @@ -157,13 +161,15 @@ private fun Iterable<Boolean>.allTrue() = all { it } private fun Iterable<Boolean>.anyTrue() = any { it } /** Returns a [TState] that is `true` only when all of [states] are `true`. */ +@ExperimentalFrpApi fun allOf(vararg states: TState<Boolean>): TState<Boolean> = combine(*states) { it.allTrue() } /** Returns a [TState] that is `true` when any of [states] are `true`. */ +@ExperimentalFrpApi fun anyOf(vararg states: TState<Boolean>): TState<Boolean> = combine(*states) { it.anyTrue() } /** Returns a [TState] containing the inverse of the Boolean held by the original [TState]. */ -fun not(state: TState<Boolean>): TState<Boolean> = state.mapCheapUnsafe { !it } +@ExperimentalFrpApi fun not(state: TState<Boolean>): TState<Boolean> = state.mapCheapUnsafe { !it } /** * Represents a modal FRP sub-network. @@ -177,6 +183,7 @@ fun not(state: TState<Boolean>): TState<Boolean> = state.mapCheapUnsafe { !it } * * @see FrpStatefulMode */ +@ExperimentalFrpApi fun interface FrpBuildMode<out A> { /** * Invoked when this mode is enabled. Returns a value and a [TFlow] that signals a switch to a @@ -192,6 +199,7 @@ fun interface FrpBuildMode<out A> { * * @see FrpBuildMode */ +@ExperimentalFrpApi val <A> FrpBuildMode<A>.compiledFrpSpec: FrpSpec<TState<A>> get() = frpSpec { var modeChangeEvents by TFlowLoop<FrpBuildMode<A>>() @@ -215,6 +223,7 @@ val <A> FrpBuildMode<A>.compiledFrpSpec: FrpSpec<TState<A>> * * @see FrpBuildMode */ +@ExperimentalFrpApi fun interface FrpStatefulMode<out A> { /** * Invoked when this mode is enabled. Returns a value and a [TFlow] that signals a switch to a @@ -230,6 +239,7 @@ fun interface FrpStatefulMode<out A> { * * @see FrpBuildMode */ +@ExperimentalFrpApi val <A> FrpStatefulMode<A>.compiledStateful: FrpStateful<TState<A>> get() = statefully { var modeChangeEvents by TFlowLoop<FrpStatefulMode<A>>() @@ -246,5 +256,18 @@ val <A> FrpStatefulMode<A>.compiledStateful: FrpStateful<TState<A>> * Runs [spec] in this [FrpBuildScope], and then re-runs it whenever [rebuildSignal] emits. Returns * a [TState] that holds the result of the currently-active [FrpSpec]. */ +@ExperimentalFrpApi fun <A> FrpBuildScope.rebuildOn(rebuildSignal: TFlow<*>, spec: FrpSpec<A>): TState<A> = rebuildSignal.map { spec }.holdLatestSpec(spec) + +/** + * Like [stateChanges] but also includes the old value of this [TState]. + * + * Shorthand for: + * ``` kotlin + * stateChanges.map { WithPrev(previousValue = sample(), newValue = it) } + * ``` + */ +@ExperimentalFrpApi +val <A> TState<A>.transitions: TFlow<WithPrev<A, A>> + get() = stateChanges.map { WithPrev(previousValue = sample(), newValue = it) } diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpBuildScope.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpBuildScope.kt index 4de6deb3dc53..209a402bd629 100644 --- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpBuildScope.kt +++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpBuildScope.kt @@ -38,6 +38,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.dropWhile +import kotlinx.coroutines.flow.scan import kotlinx.coroutines.launch /** A function that modifies the FrpNetwork. */ @@ -596,6 +597,26 @@ interface FrpBuildScope : FrpStateScope { fun <A> Flow<A>.toTState(initialValue: A): TState<A> = toTFlow().hold(initialValue) /** + * Shorthand for: + * ```kotlin + * flow.scan(initialValue, operation).toTFlow().hold(initialValue) + * ``` + */ + @ExperimentalFrpApi + fun <A, B> Flow<A>.scanToTState(initialValue: B, operation: (B, A) -> B): TState<B> = + scan(initialValue, operation).toTFlow().hold(initialValue) + + /** + * Shorthand for: + * ```kotlin + * flow.scan(initialValue) { a, f -> f(a) }.toTFlow().hold(initialValue) + * ``` + */ + @ExperimentalFrpApi + fun <A> Flow<(A) -> A>.scanToTState(initialValue: A): TState<A> = + scanToTState(initialValue) { a, f -> f(a) } + + /** * Invokes [block] whenever this [TFlow] emits a value. [block] receives an [FrpBuildScope] that * can be used to make further modifications to the FRP network, and/or perform side-effects via * [effect]. diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpEffectScope.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpEffectScope.kt index be2eb4312476..b39dcc131b1d 100644 --- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpEffectScope.kt +++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpEffectScope.kt @@ -22,16 +22,17 @@ import kotlinx.coroutines.CoroutineScope /** * Scope for external side-effects triggered by the Frp network. This still occurs within the * context of a transaction, so general suspending calls are disallowed to prevent blocking the - * transaction. You can use [frpCoroutineScope] to [launch] new coroutines to perform long-running - * asynchronous work. This scope is alive for the duration of the containing [FrpBuildScope] that - * this side-effect scope is running in. + * transaction. You can use [frpCoroutineScope] to [launch][kotlinx.coroutines.launch] new + * coroutines to perform long-running asynchronous work. This scope is alive for the duration of the + * containing [FrpBuildScope] that this side-effect scope is running in. */ @RestrictsSuspension @ExperimentalFrpApi interface FrpEffectScope : FrpTransactionScope { /** * A [CoroutineScope] whose lifecycle lives for as long as this [FrpEffectScope] is alive. This - * is generally until the [Job] returned by [FrpBuildScope.effect] is cancelled. + * is generally until the [Job][kotlinx.coroutines.Job] returned by [FrpBuildScope.effect] is + * cancelled. */ @ExperimentalFrpApi val frpCoroutineScope: CoroutineScope diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpNetwork.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpNetwork.kt index b688eafe12e9..97252b4a199a 100644 --- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpNetwork.kt +++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpNetwork.kt @@ -137,7 +137,7 @@ internal class LocalFrpNetwork( override suspend fun <R> transact(block: suspend FrpTransactionScope.() -> R): R { val result = CompletableDeferred<R>(coroutineContext[Job]) @Suppress("DeferredResultUnused") - network.transaction { + network.transaction("FrpNetwork.transact") { val buildScope = BuildScopeImpl( stateScope = StateScopeImpl(evalScope = this, endSignal = endSignal), @@ -151,7 +151,7 @@ internal class LocalFrpNetwork( override suspend fun activateSpec(spec: FrpSpec<*>) { val job = network - .transaction { + .transaction("FrpNetwork.activateSpec") { val buildScope = BuildScopeImpl( stateScope = StateScopeImpl(evalScope = this, endSignal = endSignal), @@ -166,7 +166,8 @@ internal class LocalFrpNetwork( override fun <In, Out> coalescingMutableTFlow( coalesce: (old: Out, new: In) -> Out, getInitialValue: () -> Out, - ): CoalescingMutableTFlow<In, Out> = CoalescingMutableTFlow(coalesce, network, getInitialValue) + ): CoalescingMutableTFlow<In, Out> = + CoalescingMutableTFlow(null, coalesce, network, getInitialValue) override fun <T> mutableTFlow(): MutableTFlow<T> = MutableTFlow(network) diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TFlow.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TFlow.kt index 7ba1aca31eae..a175e2e20e46 100644 --- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TFlow.kt +++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TFlow.kt @@ -467,12 +467,12 @@ fun <A> TState<TFlow<A>>.switchPromptly(): TFlow<A> { @ExperimentalFrpApi class CoalescingMutableTFlow<In, Out> internal constructor( + internal val name: String?, internal val coalesce: (old: Out, new: In) -> Out, internal val network: Network, private val getInitialValue: () -> Out, internal val impl: InputNode<Out> = InputNode(), ) : TFlow<Out>() { - internal val name: String? = null internal val storage = AtomicReference(false to getInitialValue()) override fun toString(): String = "${this::class.simpleName}@$hashString" @@ -490,7 +490,7 @@ internal constructor( val (scheduled, _) = storage.getAndUpdate { (_, old) -> true to coalesce(old, value) } if (!scheduled) { @Suppress("DeferredResultUnused") - network.transaction { + network.transaction("CoalescingMutableTFlow${name?.let { "($name)" }.orEmpty()}.emit") { impl.visit(this, storage.getAndSet(false to getInitialValue()).second) } } @@ -520,16 +520,16 @@ internal constructor(internal val network: Network, internal val impl: InputNode @ExperimentalFrpApi suspend fun emit(value: T) { coroutineScope { + var jobOrNull: Job? = null val newEmit = async(start = CoroutineStart.LAZY) { - network.transaction { impl.visit(this, value) }.await() + jobOrNull?.join() + network + .transaction("MutableTFlow($name).emit") { impl.visit(this, value) } + .await() } - val jobOrNull = storage.getAndSet(newEmit) - if (jobOrNull?.isActive != true) { - newEmit.await() - } else { - jobOrNull.join() - } + jobOrNull = storage.getAndSet(newEmit) + newEmit.await() } } diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TState.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TState.kt index a4c695657f8d..80e74748a375 100644 --- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TState.kt +++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TState.kt @@ -121,7 +121,7 @@ fun <A, B, C> TState<A>.combineWith( /** * Splits a [TState] of pairs into a pair of [TFlows][TState], where each returned [TState] holds - * hald of the original. + * half of the original. * * Shorthand for: * ```kotlin @@ -312,6 +312,57 @@ fun <A, B, C, D, Z> combine( ) } +/** + * Returns a [TState] whose value is generated with [transform] by combining the current values of + * each given [TState]. + * + * @see TState.combineWith + */ +@ExperimentalFrpApi +fun <A, B, C, D, E, Z> combine( + stateA: TState<A>, + stateB: TState<B>, + stateC: TState<C>, + stateD: TState<D>, + stateE: TState<E>, + transform: suspend FrpScope.(A, B, C, D, E) -> Z, +): TState<Z> { + val operatorName = "combine" + val name = operatorName + return TStateInit( + init(name) { + coroutineScope { + val dl1: Deferred<TStateImpl<A>> = async { + stateA.init.connect(evalScope = this@init) + } + val dl2: Deferred<TStateImpl<B>> = async { + stateB.init.connect(evalScope = this@init) + } + val dl3: Deferred<TStateImpl<C>> = async { + stateC.init.connect(evalScope = this@init) + } + val dl4: Deferred<TStateImpl<D>> = async { + stateD.init.connect(evalScope = this@init) + } + val dl5: Deferred<TStateImpl<E>> = async { + stateE.init.connect(evalScope = this@init) + } + zipStates( + name, + operatorName, + dl1.await(), + dl2.await(), + dl3.await(), + dl4.await(), + dl5.await(), + ) { a, b, c, d, e -> + NoScope.runInFrpScope { transform(a, b, c, d, e) } + } + } + } + ) +} + /** Returns a [TState] by applying [transform] to the value held by the original [TState]. */ @ExperimentalFrpApi fun <A, B> TState<A>.flatMap(transform: suspend FrpScope.(A) -> TState<B>): TState<B> { @@ -367,7 +418,7 @@ fun <A> TState<A>.selector(numDistinctValues: Int? = null): TStateSelector<A> = * @see selector */ @ExperimentalFrpApi -class TStateSelector<A> +class TStateSelector<in A> internal constructor( private val upstream: TState<A>, private val groupedChanges: GroupedTFlow<A, Boolean>, @@ -406,6 +457,7 @@ internal constructor(internal val network: Network, initialValue: Deferred<T>) : private val input: CoalescingMutableTFlow<Deferred<T>, Deferred<T>?> = CoalescingMutableTFlow( + name = null, coalesce = { _, new -> new }, network = network, getInitialValue = { null }, @@ -423,7 +475,7 @@ internal constructor(internal val network: Network, initialValue: Deferred<T>) : .cached() state = TStateSource(name, operatorName, initialValue, calm) @Suppress("DeferredResultUnused") - network.transaction { + network.transaction("MutableTState.init") { calm.activate(evalScope = this, downstream = Schedulable.S(state))?.let { (connection, needsEval) -> state.upstreamConnection = connection diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/debug/Debug.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/debug/Debug.kt index 4f302a14ff00..0674a2e75659 100644 --- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/debug/Debug.kt +++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/debug/Debug.kt @@ -31,6 +31,8 @@ import com.android.systemui.kairos.internal.TStateSource import com.android.systemui.kairos.util.Just import com.android.systemui.kairos.util.Maybe import com.android.systemui.kairos.util.None +import com.android.systemui.kairos.util.flatMap +import com.android.systemui.kairos.util.map import com.android.systemui.kairos.util.none import com.android.systemui.kairos.util.orElseGet @@ -178,3 +180,24 @@ private fun <A> TStateImpl<A>.getUnsafe(): Maybe<A> = is TStateSource -> getStorageUnsafe() is DerivedMapCheap<*, *> -> none } + +private fun <A> TStateImpl<A>.getUnsafeWithEpoch(): Maybe<Pair<A, Long>> = + when (this) { + is TStateDerived -> getCachedUnsafe().map { it to invalidatedEpoch } + is TStateSource -> getStorageUnsafe().map { it to writeEpoch } + is DerivedMapCheap<*, *> -> none + } + +/** + * Returns the current value held in this [TState], or [none] if the [TState] has not been + * initialized. + * + * The returned [Long] is the *epoch* at which the internal cache was last updated. This can be used + * to identify values which are out-of-date. + */ +fun <A> TState<A>.sampleUnsafe(): Maybe<Pair<A, Long>> = + when (this) { + is MutableTState -> tState.init.getUnsafe().flatMap { it.getUnsafeWithEpoch() } + is TStateInit -> init.getUnsafe().flatMap { it.getUnsafeWithEpoch() } + is TStateLoop -> this.init.getUnsafe().flatMap { it.getUnsafeWithEpoch() } + } diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/BuildScopeImpl.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/BuildScopeImpl.kt index 90f1aea3e42f..7e6384925f38 100644 --- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/BuildScopeImpl.kt +++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/BuildScopeImpl.kt @@ -34,9 +34,9 @@ import com.android.systemui.kairos.TFlowInit import com.android.systemui.kairos.groupByKey import com.android.systemui.kairos.init import com.android.systemui.kairos.internal.util.childScope -import com.android.systemui.kairos.internal.util.launchOnCancel import com.android.systemui.kairos.internal.util.mapValuesParallel import com.android.systemui.kairos.launchEffect +import com.android.systemui.kairos.mergeLeft import com.android.systemui.kairos.util.Just import com.android.systemui.kairos.util.Maybe import com.android.systemui.kairos.util.None @@ -49,7 +49,6 @@ import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.startCoroutine import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableJob -import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Deferred import kotlinx.coroutines.Job @@ -86,8 +85,7 @@ internal class BuildScopeImpl(val stateScope: StateScopeImpl, val coroutineScope builder: suspend S.() -> Unit, ): TFlow<A> { var job: Job? = null - val stopEmitter = newStopEmitter() - val handle = this.job.invokeOnCompletion { stopEmitter.emit(Unit) } + val stopEmitter = newStopEmitter("buildTFlow") // Create a child scope that will be kept alive beyond the end of this transaction. val childScope = coroutineScope.childScope() lateinit var emitter: Pair<T, S> @@ -99,7 +97,6 @@ internal class BuildScopeImpl(val stateScope: StateScopeImpl, val coroutineScope reenterBuildScope(this@BuildScopeImpl, childScope).runInBuildScope { launchEffect { builder(emitter.second) - handle.dispose() stopEmitter.emit(Unit) } } @@ -110,7 +107,7 @@ internal class BuildScopeImpl(val stateScope: StateScopeImpl, val coroutineScope }, ) emitter = constructFlow(inputNode) - return with(frpScope) { emitter.first.takeUntil(stopEmitter) } + return with(frpScope) { emitter.first.takeUntil(mergeLeft(stopEmitter, endSignal)) } } private fun <T> tFlowInternal(builder: suspend FrpProducerScope<T>.() -> Unit): TFlow<T> = @@ -134,7 +131,8 @@ internal class BuildScopeImpl(val stateScope: StateScopeImpl, val coroutineScope ): TFlow<Out> = buildTFlow( constructFlow = { inputNode -> - val flow = CoalescingMutableTFlow(coalesce, network, getInitialValue, inputNode) + val flow = + CoalescingMutableTFlow(null, coalesce, network, getInitialValue, inputNode) flow to object : FrpCoalescingProducerScope<In> { override fun emit(value: In) { @@ -164,11 +162,13 @@ internal class BuildScopeImpl(val stateScope: StateScopeImpl, val coroutineScope val subRef = AtomicReference<Maybe<Output<A>>>(null) val childScope = coroutineScope.childScope() // When our scope is cancelled, deactivate this observer. - childScope.launchOnCancel(CoroutineName("TFlow.observeEffect")) { + childScope.coroutineContext.job.invokeOnCompletion { subRef.getAndSet(None)?.let { output -> if (output is Just) { @Suppress("DeferredResultUnused") - network.transaction { scheduleDeactivation(output.value) } + network.transaction("observeEffect cancelled") { + scheduleDeactivation(output.value) + } } } } @@ -215,7 +215,7 @@ internal class BuildScopeImpl(val stateScope: StateScopeImpl, val coroutineScope } else if (needsEval) { outputNode.schedule(evalScope = stateScope.evalScope) } - } ?: childScope.cancel() + } ?: run { childScope.cancel() } } return childScope.coroutineContext.job } @@ -229,10 +229,7 @@ internal class BuildScopeImpl(val stateScope: StateScopeImpl, val coroutineScope "mapBuild", mapImpl({ init.connect(evalScope = this) }) { spec -> reenterBuildScope(outerScope = this@BuildScopeImpl, childScope) - .runInBuildScope { - val (result, _) = asyncScope { transform(spec) } - result.get() - } + .runInBuildScope { transform(spec) } } .cached(), ) @@ -272,8 +269,9 @@ internal class BuildScopeImpl(val stateScope: StateScopeImpl, val coroutineScope return changes to FrpDeferredValue(initOut) } - private fun newStopEmitter(): CoalescingMutableTFlow<Unit, Unit> = + private fun newStopEmitter(name: String): CoalescingMutableTFlow<Unit, Unit> = CoalescingMutableTFlow( + name = name, coalesce = { _, _: Unit -> }, network = network, getInitialValue = {}, @@ -299,17 +297,19 @@ internal class BuildScopeImpl(val stateScope: StateScopeImpl, val coroutineScope } private fun mutableChildBuildScope(): BuildScopeImpl { - val stopEmitter = newStopEmitter() + val stopEmitter = newStopEmitter("mutableChildBuildScope") val childScope = coroutineScope.childScope() childScope.coroutineContext.job.invokeOnCompletion { stopEmitter.emit(Unit) } // Ensure that once this transaction is done, the new child scope enters the completing // state (kept alive so long as there are child jobs). - scheduleOutput( - OneShot { - // TODO: don't like this cast - (childScope.coroutineContext.job as CompletableJob).complete() - } - ) + // TODO: need to keep the scope alive if it's used to accumulate state. + // Otherwise, stopEmitter will emit early, due to the call to complete(). + // scheduleOutput( + // OneShot { + // // TODO: don't like this cast + // (childScope.coroutineContext.job as CompletableJob).complete() + // } + // ) return BuildScopeImpl( stateScope = StateScopeImpl(evalScope = stateScope.evalScope, endSignal = stopEmitter), coroutineScope = childScope, diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt index 3aec319881d0..04ce5b6d8785 100644 --- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt +++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt @@ -86,7 +86,7 @@ internal class DepthTracker { @Volatile private var dirty_depthIsDirect = true @Volatile private var dirty_isIndirectRoot = false - suspend fun schedule(scheduler: Scheduler, node: MuxNode<*, *, *>) { + fun schedule(scheduler: Scheduler, node: MuxNode<*, *, *>) { if (dirty_depthIsDirect) { scheduler.schedule(dirty_directDepth, node) } else { diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/InternalScopes.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/InternalScopes.kt index af864e6c3496..69994ba6e866 100644 --- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/InternalScopes.kt +++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/InternalScopes.kt @@ -66,8 +66,6 @@ internal interface NetworkScope : InitScope { fun schedule(state: TStateSource<*>) - suspend fun schedule(node: MuxNode<*, *, *>) - fun scheduleDeactivation(node: PushNode<*>) fun scheduleDeactivation(output: Output<*>) diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Mux.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Mux.kt index f7ff15f0507b..af68a1e3d83c 100644 --- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Mux.kt +++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Mux.kt @@ -188,6 +188,14 @@ internal sealed class MuxNode<K : Any, V, Output>(val lifecycle: MuxLifecycle<Ou } abstract fun hasCurrentValueLocked(transactionStore: TransactionStore): Boolean + + fun schedule(evalScope: EvalScope) { + // TODO: Potential optimization + // Detect if this node is guaranteed to have a single upstream within this transaction, + // then bypass scheduling it. Instead immediately schedule its downstream and treat this + // MuxNode as a Pull (effectively making it a mapCheap). + depthTracker.schedule(evalScope.scheduler, this) + } } /** An input branch of a mux node, associated with a key. */ @@ -202,7 +210,7 @@ internal class MuxBranchNode<K : Any, V>(private val muxNode: MuxNode<K, V, *>, val upstreamResult = upstream.getPushEvent(evalScope) if (upstreamResult is Just) { muxNode.upstreamData[key] = upstreamResult.value - evalScope.schedule(muxNode) + muxNode.schedule(evalScope) } } diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt index 08bee855831a..3b9502a5d812 100644 --- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt +++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt @@ -409,7 +409,7 @@ internal fun <K : Any, A> switchDeferredImpl( // Schedule for evaluation if any switched-in nodes have already emitted within // this transaction. if (muxNode.upstreamData.isNotEmpty()) { - evalScope.schedule(muxNode) + muxNode.schedule(evalScope) } return muxNode.takeUnless { muxNode.switchedIn.isEmpty() && !isIndirect } } diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt index cdfafa943121..b291c879b449 100644 --- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt +++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt @@ -75,7 +75,7 @@ internal class MuxPromptMovingNode<K : Any, V>( if (depthTracker.dirty_depthIncreased()) { depthTracker.schedule(evalScope.compactor, node = this) } - evalScope.schedule(this) + schedule(evalScope) } else { val compactDownstream = depthTracker.isDirty() if (evalResult != null || compactDownstream) { @@ -291,7 +291,7 @@ internal class MuxPromptPatchNode<K : Any, V>(private val muxNode: MuxPromptMovi val upstreamResult = upstream.getPushEvent(evalScope) if (upstreamResult is Just) { muxNode.patchData = upstreamResult.value - evalScope.schedule(muxNode) + muxNode.schedule(evalScope) } } @@ -451,7 +451,7 @@ internal fun <K : Any, A> switchPromptImpl( // Schedule for evaluation if any switched-in nodes or the patches node have // already emitted within this transaction. if (movingNode.patchData != null || movingNode.upstreamData.isNotEmpty()) { - evalScope.schedule(movingNode) + movingNode.schedule(evalScope) } return movingNode.takeUnless { it.patches == null && it.switchedIn.isEmpty() } diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Network.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Network.kt index f0df89d780c9..599b18695034 100644 --- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Network.kt +++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Network.kt @@ -81,11 +81,6 @@ internal class Network(val coroutineScope: CoroutineScope) : NetworkScope { stateWrites.add(state) } - // TODO: weird that we have this *and* scheduler exposed - override suspend fun schedule(node: MuxNode<*, *, *>) { - scheduler.schedule(node.depthTracker.dirty_directDepth, node) - } - override fun scheduleDeactivation(node: PushNode<*>) { deactivations.add(node) } @@ -95,9 +90,7 @@ internal class Network(val coroutineScope: CoroutineScope) : NetworkScope { } /** Listens for external events and starts FRP transactions. Runs forever. */ - suspend fun runInputScheduler() = coroutineScope { - launch { scheduler.activate() } - launch { compactor.activate() } + suspend fun runInputScheduler() { val actions = mutableListOf<ScheduledAction<*>>() for (first in inputScheduleChan) { // Drain and conflate all transaction requests into a single transaction @@ -125,12 +118,12 @@ internal class Network(val coroutineScope: CoroutineScope) : NetworkScope { } /** Evaluates [block] inside of a new transaction when the network is ready. */ - fun <R> transaction(block: suspend EvalScope.() -> R): Deferred<R> = + fun <R> transaction(reason: String, block: suspend EvalScope.() -> R): Deferred<R> = CompletableDeferred<R>(parent = coroutineScope.coroutineContext.job).also { onResult -> val job = coroutineScope.launch { inputScheduleChan.send( - ScheduledAction(onStartTransaction = block, onResult = onResult) + ScheduledAction(reason, onStartTransaction = block, onResult = onResult) ) } onResult.invokeOnCompletion { job.cancel() } @@ -229,6 +222,7 @@ internal class Network(val coroutineScope: CoroutineScope) : NetworkScope { } internal class ScheduledAction<T>( + val reason: String, private val onResult: CompletableDeferred<T>? = null, private val onStartTransaction: suspend EvalScope.() -> T, ) { diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Scheduler.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Scheduler.kt index 872fb7a6cb74..c12ef6ae6a5d 100644 --- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Scheduler.kt +++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Scheduler.kt @@ -21,44 +21,34 @@ package com.android.systemui.kairos.internal import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.PriorityBlockingQueue import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch internal interface Scheduler { - suspend fun schedule(depth: Int, node: MuxNode<*, *, *>) + fun schedule(depth: Int, node: MuxNode<*, *, *>) - suspend fun scheduleIndirect(indirectDepth: Int, node: MuxNode<*, *, *>) + fun scheduleIndirect(indirectDepth: Int, node: MuxNode<*, *, *>) } internal class SchedulerImpl : Scheduler { val enqueued = ConcurrentHashMap<MuxNode<*, *, *>, Any>() val scheduledQ = PriorityBlockingQueue<Pair<Int, MuxNode<*, *, *>>>(16, compareBy { it.first }) - val chan = Channel<Pair<Int, MuxNode<*, *, *>>>(Channel.UNLIMITED) - override suspend fun schedule(depth: Int, node: MuxNode<*, *, *>) { + override fun schedule(depth: Int, node: MuxNode<*, *, *>) { if (enqueued.putIfAbsent(node, node) == null) { - chan.send(Pair(depth, node)) + scheduledQ.add(Pair(depth, node)) } } - override suspend fun scheduleIndirect(indirectDepth: Int, node: MuxNode<*, *, *>) { + override fun scheduleIndirect(indirectDepth: Int, node: MuxNode<*, *, *>) { schedule(Int.MIN_VALUE + indirectDepth, node) } - suspend fun activate() { - for (nodeSchedule in chan) { - scheduledQ.add(nodeSchedule) - drainChan() - } - } - internal suspend fun drainEval(network: Network) { drain { runStep -> runStep { muxNode -> network.evalScope { muxNode.visit(this) } } // If any visited MuxPromptNodes had their depths increased, eagerly propagate those - // depth - // changes now before performing further network evaluation. + // depth changes now before performing further network evaluation. network.compactor.drainCompact() } } @@ -71,19 +61,12 @@ internal class SchedulerImpl : Scheduler { crossinline onStep: suspend (runStep: suspend (visit: suspend (MuxNode<*, *, *>) -> Unit) -> Unit) -> Unit ): Unit = coroutineScope { - while (!chan.isEmpty || scheduledQ.isNotEmpty()) { - drainChan() + while (scheduledQ.isNotEmpty()) { val maxDepth = scheduledQ.peek()?.first ?: error("Unexpected empty scheduler") onStep { visit -> runStep(maxDepth, visit) } } } - private suspend fun drainChan() { - while (!chan.isEmpty) { - scheduledQ.add(chan.receive()) - } - } - private suspend inline fun runStep( maxDepth: Int, crossinline visit: suspend (MuxNode<*, *, *>) -> Unit, diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/TStateImpl.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/TStateImpl.kt index 5cec05c8ef2d..c68b4c366776 100644 --- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/TStateImpl.kt +++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/TStateImpl.kt @@ -314,6 +314,28 @@ internal fun <A, B, C, D, Z> zipStates( @Suppress("UNCHECKED_CAST") transform(a as A, b as B, c as C, d as D) } +internal fun <A, B, C, D, E, Z> zipStates( + name: String?, + operatorName: String, + l1: TStateImpl<A>, + l2: TStateImpl<B>, + l3: TStateImpl<C>, + l4: TStateImpl<D>, + l5: TStateImpl<E>, + transform: suspend EvalScope.(A, B, C, D, E) -> Z, +): TStateImpl<Z> = + zipStates(null, operatorName, mapOf(0 to l1, 1 to l2, 2 to l3, 3 to l4, 4 to l5)).map( + name, + operatorName, + ) { + val a = it.getValue(0) + val b = it.getValue(1) + val c = it.getValue(2) + val d = it.getValue(3) + val e = it.getValue(4) + @Suppress("UNCHECKED_CAST") transform(a as A, b as B, c as C, d as D, e as E) + } + internal fun <K : Any, A> zipStates( name: String?, operatorName: String, diff --git a/packages/SystemUI/utils/kairos/test/com/android/systemui/kairos/KairosTests.kt b/packages/SystemUI/utils/kairos/test/com/android/systemui/kairos/KairosTests.kt index 165230b2aeaf..688adae8fcae 100644 --- a/packages/SystemUI/utils/kairos/test/com/android/systemui/kairos/KairosTests.kt +++ b/packages/SystemUI/utils/kairos/test/com/android/systemui/kairos/KairosTests.kt @@ -1170,12 +1170,12 @@ class KairosTests { mergeIncrementally .onEach { println("patch: $it") } .foldMapIncrementally() - .flatMap { it.combineValues() } + .flatMap { it.combine() } } } } .foldMapIncrementally() - .flatMap { it.combineValues() } + .flatMap { it.combine() } accState.toStateFlow() } @@ -1300,6 +1300,26 @@ class KairosTests { } @Test + fun buildScope_stateAccumulation() = runFrpTest { network -> + val input = network.mutableTFlow<Unit>() + var observedCount: Int? = null + activateSpec(network) { + val (c, j) = asyncScope { input.fold(0) { _, x -> x + 1 } } + deferredBuildScopeAction { c.get().observe { observedCount = it } } + } + runCurrent() + assertEquals(0, observedCount) + + input.emit(Unit) + runCurrent() + assertEquals(1, observedCount) + + input.emit(Unit) + runCurrent() + assertEquals(2, observedCount) + } + + @Test fun effect() = runFrpTest { network -> val input = network.mutableTFlow<Unit>() var effectRunning = false diff --git a/services/appfunctions/Android.bp b/services/appfunctions/Android.bp index eb6e46861898..7337aa26c145 100644 --- a/services/appfunctions/Android.bp +++ b/services/appfunctions/Android.bp @@ -19,6 +19,7 @@ java_library_static { defaults: ["platform_service_defaults"], srcs: [ ":services.appfunctions-sources", + ":statslog-appfunctions-java-gen", "java/**/*.logtags", ], libs: ["services.core"], @@ -26,3 +27,10 @@ java_library_static { baseline_filename: "lint-baseline.xml", }, } + +genrule { + name: "statslog-appfunctions-java-gen", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --java $(out) --module appfunctions --javaPackage com.android.server.appfunctions --javaClass AppFunctionsStatsLog --minApiLevel 35", + out: ["java/com/android/server/appfunctions/AppFunctionsStatsLog.java"], +} diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java index 81e83b563945..eaea4435099c 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java @@ -16,6 +16,8 @@ package com.android.server.appfunctions; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -35,6 +37,11 @@ public final class AppFunctionExecutors { /* workQueue= */ new LinkedBlockingQueue<>(), new NamedThreadFactory("AppFunctionExecutors")); + /** Executor for stats logging. */ + public static final ExecutorService LOGGING_THREAD_EXECUTOR = + Executors.newSingleThreadExecutor( + new NamedThreadFactory("AppFunctionsLoggingExecutors")); + static { THREAD_POOL_EXECUTOR.allowCoreThreadTimeOut(true); } diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java index c17c34061d1b..9cc5a8c97258 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java @@ -30,6 +30,7 @@ import android.app.appfunctions.AppFunctionManagerHelper; import android.app.appfunctions.AppFunctionRuntimeMetadata; import android.app.appfunctions.AppFunctionStaticMetadataHelper; import android.app.appfunctions.ExecuteAppFunctionAidlRequest; +import android.app.appfunctions.ExecuteAppFunctionResponse; import android.app.appfunctions.IAppFunctionEnabledCallback; import android.app.appfunctions.IAppFunctionManager; import android.app.appfunctions.IAppFunctionService; @@ -85,6 +86,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { private final ServiceConfig mServiceConfig; private final Context mContext; private final Map<String, Object> mLocks = new WeakHashMap<>(); + private final AppFunctionsLoggerWrapper mLoggerWrapper; public AppFunctionManagerServiceImpl(@NonNull Context context) { this( @@ -93,7 +95,8 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { context, IAppFunctionService.Stub::asInterface, THREAD_POOL_EXECUTOR), new CallerValidatorImpl(context), new ServiceHelperImpl(context), - new ServiceConfigImpl()); + new ServiceConfigImpl(), + new AppFunctionsLoggerWrapper(context)); } @VisibleForTesting @@ -102,12 +105,14 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { RemoteServiceCaller<IAppFunctionService> remoteServiceCaller, CallerValidator callerValidator, ServiceHelper appFunctionInternalServiceHelper, - ServiceConfig serviceConfig) { + ServiceConfig serviceConfig, + AppFunctionsLoggerWrapper loggerWrapper) { mContext = Objects.requireNonNull(context); mRemoteServiceCaller = Objects.requireNonNull(remoteServiceCaller); mCallerValidator = Objects.requireNonNull(callerValidator); mInternalServiceHelper = Objects.requireNonNull(appFunctionInternalServiceHelper); mServiceConfig = serviceConfig; + mLoggerWrapper = loggerWrapper; } /** Called when the user is unlocked. */ @@ -146,8 +151,25 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { Objects.requireNonNull(requestInternal); Objects.requireNonNull(executeAppFunctionCallback); + int callingUid = Binder.getCallingUid(); + int callingPid = Binder.getCallingPid(); + final SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback = - new SafeOneTimeExecuteAppFunctionCallback(executeAppFunctionCallback); + new SafeOneTimeExecuteAppFunctionCallback(executeAppFunctionCallback, + new SafeOneTimeExecuteAppFunctionCallback.CompletionCallback() { + @Override + public void finalizeOnSuccess( + @NonNull ExecuteAppFunctionResponse result) { + mLoggerWrapper.logAppFunctionSuccess(requestInternal, result, + callingUid); + } + + @Override + public void finalizeOnError(@NonNull AppFunctionException error) { + mLoggerWrapper.logAppFunctionError(requestInternal, + error.getErrorCode(), callingUid); + } + }); String validatedCallingPackage; try { @@ -162,9 +184,6 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { return null; } - int callingUid = Binder.getCallingUid(); - int callingPid = Binder.getCallingPid(); - ICancellationSignal localCancelTransport = CancellationSignal.createTransport(); THREAD_POOL_EXECUTOR.execute( diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionsLoggerWrapper.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionsLoggerWrapper.java new file mode 100644 index 000000000000..b59915aa6343 --- /dev/null +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionsLoggerWrapper.java @@ -0,0 +1,74 @@ +/* + * 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.appfunctions; + +import static com.android.server.appfunctions.AppFunctionExecutors.LOGGING_THREAD_EXECUTOR; + +import android.annotation.NonNull; +import android.app.appfunctions.ExecuteAppFunctionAidlRequest; +import android.app.appfunctions.ExecuteAppFunctionResponse; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.SystemClock; +import android.util.Slog; + +import java.util.Objects; + +/** Wraps AppFunctionsStatsLog. */ +public class AppFunctionsLoggerWrapper { + private static final String TAG = AppFunctionsLoggerWrapper.class.getSimpleName(); + + private static final int SUCCESS_RESPONSE_CODE = -1; + + private final Context mContext; + + public AppFunctionsLoggerWrapper(@NonNull Context context) { + mContext = Objects.requireNonNull(context); + } + + void logAppFunctionSuccess(ExecuteAppFunctionAidlRequest request, + ExecuteAppFunctionResponse response, int callingUid) { + logAppFunctionsRequestReported(request, SUCCESS_RESPONSE_CODE, + response.getResponseDataSize(), callingUid); + } + + void logAppFunctionError(ExecuteAppFunctionAidlRequest request, int errorCode, int callingUid) { + logAppFunctionsRequestReported(request, errorCode, /* responseSizeBytes = */ 0, callingUid); + } + + private void logAppFunctionsRequestReported(ExecuteAppFunctionAidlRequest request, + int errorCode, int responseSizeBytes, int callingUid) { + final long latency = SystemClock.elapsedRealtime() - request.getRequestTime(); + LOGGING_THREAD_EXECUTOR.execute(() -> AppFunctionsStatsLog.write( + AppFunctionsStatsLog.APP_FUNCTIONS_REQUEST_REPORTED, + callingUid, + getPackageUid(request.getClientRequest().getTargetPackageName()), + errorCode, + request.getClientRequest().getRequestDataSize(), responseSizeBytes, + latency) + ); + } + + private int getPackageUid(String packageName) { + try { + return mContext.getPackageManager().getPackageUid(packageName, 0); + } catch (PackageManager.NameNotFoundException e) { + Slog.e(TAG, "Package uid not found for " + packageName); + } + return 0; + } +} diff --git a/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java b/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java index c689bb92f8f7..896c0fc51683 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java +++ b/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java @@ -16,8 +16,8 @@ package com.android.server.appfunctions; import android.annotation.NonNull; -import android.app.appfunctions.ExecuteAppFunctionAidlRequest; import android.app.appfunctions.AppFunctionException; +import android.app.appfunctions.ExecuteAppFunctionAidlRequest; import android.app.appfunctions.ExecuteAppFunctionResponse; import android.app.appfunctions.IAppFunctionService; import android.app.appfunctions.ICancellationCallback; diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index 883e09f53e41..87f87c76725e 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -143,6 +143,7 @@ public class SettingsToPropertiesMapper { "tv_os", "aaos_carframework_triage", "aaos_performance_triage", + "aaos_input_triage", "aaos_user_triage", "aaos_window_triage", "aaos_audio_triage", diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 09b8e212bfad..1799b7715e5c 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -4177,6 +4177,12 @@ public class AudioService extends IAudioService.Stub // Stream mute changed, fire the intent. Intent intent = new Intent(AudioManager.STREAM_MUTE_CHANGED_ACTION); intent.putExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, isMuted); + if (replaceStreamBtSco() && isStreamBluetoothSco(streamType)) { + intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, + AudioSystem.STREAM_BLUETOOTH_SCO); + // in this case broadcast for both sco and voice_call streams the mute status + sendBroadcastToAll(intent, null /* options */); + } intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, streamType); sendBroadcastToAll(intent, null /* options */); } @@ -9670,9 +9676,16 @@ public class AudioService extends IAudioService.Stub mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index); mVolumeChanged.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, oldIndex); - - mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, - mStreamType); + int extraStreamType = mStreamType; + // TODO: remove this when deprecating STREAM_BLUETOOTH_SCO + if (isStreamBluetoothSco(mStreamType)) { + mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, + AudioSystem.STREAM_BLUETOOTH_SCO); + extraStreamType = AudioSystem.STREAM_BLUETOOTH_SCO; + } else { + mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, + mStreamType); + } mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS, streamAlias); @@ -9683,9 +9696,21 @@ public class AudioService extends IAudioService.Stub " aliased streams: " + aliasStreamIndexes; } AudioService.sVolumeLogger.enqueue(new VolChangedBroadcastEvent( - mStreamType, aliasStreamIndexesString, index, oldIndex)); + extraStreamType, aliasStreamIndexesString, index, oldIndex)); + if (extraStreamType != mStreamType) { + AudioService.sVolumeLogger.enqueue(new VolChangedBroadcastEvent( + mStreamType, aliasStreamIndexesString, index, oldIndex)); + } } sendBroadcastToAll(mVolumeChanged, mVolumeChangedOptions); + if (extraStreamType != mStreamType) { + // send multiple intents in case we merged voice call and bt sco streams + mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, + mStreamType); + // do not use the options in thid case which could discard + // the previous intent + sendBroadcastToAll(mVolumeChanged, null); + } } } } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 945365dcf8fe..f48fbea64f65 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -906,6 +906,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mLogicalDisplay.getPowerThrottlingDataIdLocked(); mHandler.postAtTime(() -> { + if (mStopped) { + // DPC has already stopped, don't execute any more. + return; + } + boolean changed = false; if (mIsEnabled != isEnabled || mIsInTransition != isInTransition) { @@ -3306,7 +3311,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call int displayId, SensorManager sensorManager) { return new DisplayPowerProximityStateController(wakelockController, displayDeviceConfig, looper, nudgeUpdatePowerState, - displayId, sensorManager, /* injector= */ null); + displayId, sensorManager); } AutomaticBrightnessController getAutomaticBrightnessController( diff --git a/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java b/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java index 215932ca19be..35455c841c7b 100644 --- a/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java +++ b/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java @@ -16,6 +16,8 @@ package com.android.server.display; +import android.annotation.IntDef; +import android.annotation.Nullable; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; @@ -34,6 +36,8 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.server.display.utils.SensorUtils; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * Maintains the proximity state of the display. @@ -42,18 +46,26 @@ import java.io.PrintWriter; */ public final class DisplayPowerProximityStateController { @VisibleForTesting - static final int MSG_PROXIMITY_SENSOR_DEBOUNCED = 1; - @VisibleForTesting static final int PROXIMITY_UNKNOWN = -1; + private static final int PROXIMITY_NEGATIVE = 0; @VisibleForTesting static final int PROXIMITY_POSITIVE = 1; + + @IntDef(prefix = { "PROXIMITY_" }, value = { + PROXIMITY_UNKNOWN, + PROXIMITY_NEGATIVE, + PROXIMITY_POSITIVE + }) + @Retention(RetentionPolicy.SOURCE) + @interface ProximityState {} + + @VisibleForTesting + static final int MSG_PROXIMITY_SENSOR_DEBOUNCED = 1; @VisibleForTesting static final int PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY = 0; private static final int MSG_IGNORE_PROXIMITY = 2; - private static final int PROXIMITY_NEGATIVE = 0; - private static final boolean DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT = false; // Proximity sensor debounce delay in milliseconds for positive transitions. @@ -73,7 +85,7 @@ public final class DisplayPowerProximityStateController { private final DisplayPowerProximityStateHandler mHandler; // A runnable to execute the utility to update the power state. private final Runnable mNudgeUpdatePowerState; - private Clock mClock; + private final Clock mClock; // A listener which listen's to the events emitted by the proximity sensor. private final SensorEventListener mProximitySensorListener = new SensorEventListener() { @Override @@ -117,9 +129,6 @@ public final class DisplayPowerProximityStateController { // with the sensor manager. private boolean mProximitySensorEnabled; - // The raw non-debounced proximity sensor state. - private int mPendingProximity = PROXIMITY_UNKNOWN; - // -1 if fully debounced. Else, represents the time in ms when the debounce suspend blocker will // be removed. Applies for both positive and negative proximity flips. private long mPendingProximityDebounceTime = -1; @@ -128,8 +137,11 @@ public final class DisplayPowerProximityStateController { // When the screen turns on again, we report user activity to the power manager. private boolean mScreenOffBecauseOfProximity; + // The raw non-debounced proximity sensor state. + private @ProximityState int mPendingProximity = PROXIMITY_UNKNOWN; + // The debounced proximity sensor state. - private int mProximity = PROXIMITY_UNKNOWN; + private @ProximityState int mProximity = PROXIMITY_UNKNOWN; // The actual proximity sensor threshold value. private float mProximityThreshold; @@ -139,7 +151,7 @@ public final class DisplayPowerProximityStateController { private boolean mSkipRampBecauseOfProximityChangeToNegative = false; // The DisplayId of the associated Logical Display. - private int mDisplayId; + private final int mDisplayId; /** * Create a new instance of DisplayPowerProximityStateController. @@ -152,11 +164,18 @@ public final class DisplayPowerProximityStateController { * @param displayId The DisplayId of the associated Logical Display. * @param sensorManager The manager which lets us access the display's ProximitySensor */ - public DisplayPowerProximityStateController( - WakelockController wakeLockController, DisplayDeviceConfig displayDeviceConfig, - Looper looper, + public DisplayPowerProximityStateController(WakelockController wakeLockController, + DisplayDeviceConfig displayDeviceConfig, Looper looper, + Runnable nudgeUpdatePowerState, int displayId, SensorManager sensorManager) { + this(wakeLockController, displayDeviceConfig, looper, nudgeUpdatePowerState, displayId, + sensorManager, new Injector()); + } + + @VisibleForTesting + DisplayPowerProximityStateController(WakelockController wakeLockController, + DisplayDeviceConfig displayDeviceConfig, Looper looper, Runnable nudgeUpdatePowerState, int displayId, SensorManager sensorManager, - Injector injector) { + @Nullable Injector injector) { if (injector == null) { injector = new Injector(); } @@ -437,7 +456,7 @@ public final class DisplayPowerProximityStateController { if (mProximity != mPendingProximity) { // if the status of the sensor changed, stop ignoring. mIgnoreProximityUntilChanged = false; - Slog.i(mTag, "No longer ignoring proximity [" + mPendingProximity + "]"); + Slog.i(mTag, "Applying proximity: " + proximityToString(mPendingProximity)); } // Sensor reading accepted. Apply the change then release the wake lock. mProximity = mPendingProximity; @@ -478,7 +497,7 @@ public final class DisplayPowerProximityStateController { } } - private String proximityToString(int state) { + private String proximityToString(@ProximityState int state) { switch (state) { case PROXIMITY_UNKNOWN: return "Unknown"; @@ -518,12 +537,12 @@ public final class DisplayPowerProximityStateController { } @VisibleForTesting - int getPendingProximity() { + @ProximityState int getPendingProximity() { return mPendingProximity; } @VisibleForTesting - int getProximity() { + @ProximityState int getProximity() { return mProximity; } @@ -550,7 +569,7 @@ public final class DisplayPowerProximityStateController { @VisibleForTesting static class Injector { Clock createClock() { - return () -> SystemClock.uptimeMillis(); + return SystemClock::uptimeMillis; } } } diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index c0903a9bafac..79592a656409 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -506,9 +506,6 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { return; } - Slog.i(TAG, "Requesting Transition to state: " + state.getIdentifier() + ", from state=" - + mDeviceState.getIdentifier() + ", interactive=" + mInteractive - + ", mBootCompleted=" + mBootCompleted); // As part of a state transition, we may need to turn off some displays temporarily so that // the transition is smooth. Plus, on some devices, only one internal displays can be // on at a time. We use LogicalDisplay.setIsInTransition to mark a display that needs to be @@ -522,6 +519,11 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { final boolean sleepDevice = shouldDeviceBePutToSleep(mPendingDeviceState, mDeviceState, mInteractive, mBootCompleted); + Slog.i(TAG, "Requesting Transition to state: " + state.getIdentifier() + ", from state=" + + mDeviceState.getIdentifier() + ", interactive=" + mInteractive + + ", mBootCompleted=" + mBootCompleted + ", wakeDevice=" + wakeDevice + + ", sleepDevice=" + sleepDevice); + // If all displays are off already, we can just transition here, unless we are trying to // wake or sleep the device as part of this transition. In that case defer the final // transition until later once the device is awake/asleep. diff --git a/services/core/java/com/android/server/pm/InstallDependencyHelper.java b/services/core/java/com/android/server/pm/InstallDependencyHelper.java index c0ddebeb9868..837adf004df7 100644 --- a/services/core/java/com/android/server/pm/InstallDependencyHelper.java +++ b/services/core/java/com/android/server/pm/InstallDependencyHelper.java @@ -78,24 +78,22 @@ public class InstallDependencyHelper { mPackageInstallerService = packageInstallerService; } - void resolveLibraryDependenciesIfNeeded(PackageLite pkg, Computer snapshot, int userId, - Handler handler, OutcomeReceiver<Void, PackageManagerException> origCallback) { + void resolveLibraryDependenciesIfNeeded(List<SharedLibraryInfo> missingLibraries, + PackageLite pkg, Computer snapshot, int userId, Handler handler, + OutcomeReceiver<Void, PackageManagerException> origCallback) { CallOnceProxy callback = new CallOnceProxy(handler, origCallback); try { - resolveLibraryDependenciesIfNeededInternal(pkg, snapshot, userId, handler, callback); - } catch (PackageManagerException e) { - callback.onError(e); + resolveLibraryDependenciesIfNeededInternal( + missingLibraries, pkg, snapshot, userId, handler, callback); } catch (Exception e) { onError(callback, e.getMessage()); } } - private void resolveLibraryDependenciesIfNeededInternal(PackageLite pkg, Computer snapshot, - int userId, Handler handler, CallOnceProxy callback) throws PackageManagerException { - final List<SharedLibraryInfo> missing = - mSharedLibraries.collectMissingSharedLibraryInfos(pkg); - + private void resolveLibraryDependenciesIfNeededInternal(List<SharedLibraryInfo> missing, + PackageLite pkg, Computer snapshot, int userId, Handler handler, + CallOnceProxy callback) { if (missing.isEmpty()) { if (DEBUG) { Slog.d(TAG, "No missing dependency for " + pkg.getPackageName()); @@ -129,6 +127,11 @@ public class InstallDependencyHelper { } } + List<SharedLibraryInfo> getMissingSharedLibraries(PackageLite pkg) + throws PackageManagerException { + return mSharedLibraries.collectMissingSharedLibraryInfos(pkg); + } + void notifySessionComplete(int sessionId) { if (DEBUG) { Slog.d(TAG, "Session complete for " + sessionId); diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java index b0fe3a97af6e..c96c160deb0f 100644 --- a/services/core/java/com/android/server/pm/InstallRequest.java +++ b/services/core/java/com/android/server/pm/InstallRequest.java @@ -170,6 +170,8 @@ final class InstallRequest { private final boolean mHasAppMetadataFileFromInstaller; private boolean mKeepArtProfile = false; + private final boolean mDependencyInstallerEnabled; + private final int mMissingSharedLibraryCount; // New install InstallRequest(InstallingSession params) { @@ -190,6 +192,8 @@ final class InstallRequest { mRequireUserAction = params.mRequireUserAction; mPreVerifiedDomains = params.mPreVerifiedDomains; mHasAppMetadataFileFromInstaller = params.mHasAppMetadataFile; + mDependencyInstallerEnabled = params.mDependencyInstallerEnabled; + mMissingSharedLibraryCount = params.mMissingSharedLibraryCount; } // Install existing package as user @@ -209,6 +213,8 @@ final class InstallRequest { mInstallerUidForInstallExisting = installerUid; mSystem = isSystem; mHasAppMetadataFileFromInstaller = false; + mDependencyInstallerEnabled = false; + mMissingSharedLibraryCount = 0; } // addForInit @@ -231,6 +237,8 @@ final class InstallRequest { mRequireUserAction = USER_ACTION_UNSPECIFIED; mDisabledPs = disabledPs; mHasAppMetadataFileFromInstaller = false; + mDependencyInstallerEnabled = false; + mMissingSharedLibraryCount = 0; } @Nullable @@ -1069,4 +1077,12 @@ final class InstallRequest { boolean isKeepArtProfile() { return mKeepArtProfile; } + + int getMissingSharedLibraryCount() { + return mMissingSharedLibraryCount; + } + + boolean isDependencyInstallerEnabled() { + return mDependencyInstallerEnabled; + } } diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java index ccc117566989..6a2bf83ba368 100644 --- a/services/core/java/com/android/server/pm/InstallingSession.java +++ b/services/core/java/com/android/server/pm/InstallingSession.java @@ -103,6 +103,8 @@ class InstallingSession { final DomainSet mPreVerifiedDomains; final boolean mHasAppMetadataFile; @Nullable final String mDexoptCompilerFilter; + final boolean mDependencyInstallerEnabled; + final int mMissingSharedLibraryCount; // For move install InstallingSession(OriginInfo originInfo, MoveInfo moveInfo, IPackageInstallObserver2 observer, @@ -138,13 +140,16 @@ class InstallingSession { mPreVerifiedDomains = null; mHasAppMetadataFile = false; mDexoptCompilerFilter = null; + mDependencyInstallerEnabled = false; + mMissingSharedLibraryCount = 0; } InstallingSession(int sessionId, File stagedDir, IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams, InstallSource installSource, UserHandle user, SigningDetails signingDetails, int installerUid, PackageLite packageLite, DomainSet preVerifiedDomains, PackageManagerService pm, - boolean hasAppMetadatafile) { + boolean hasAppMetadatafile, boolean dependencyInstallerEnabled, + int missingSharedLibraryCount) { mPm = pm; mUser = user; mOriginInfo = OriginInfo.fromStagedFile(stagedDir); @@ -175,6 +180,8 @@ class InstallingSession { mPreVerifiedDomains = preVerifiedDomains; mHasAppMetadataFile = hasAppMetadatafile; mDexoptCompilerFilter = sessionParams.dexoptCompilerFilter; + mDependencyInstallerEnabled = dependencyInstallerEnabled; + mMissingSharedLibraryCount = missingSharedLibraryCount; } @Override diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 891d66a5d238..c6760431116e 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -31,6 +31,7 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STOR import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR; import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; import static android.content.pm.PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE; +import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY; import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SPLIT; import static android.content.pm.PackageManager.INSTALL_FAILED_PRE_APPROVAL_NOT_AVAILABLE; import static android.content.pm.PackageManager.INSTALL_FAILED_SESSION_INVALID; @@ -109,6 +110,7 @@ import android.content.pm.PackageInstaller.UserActionReason; import android.content.pm.PackageManager; import android.content.pm.PackageManager.PackageInfoFlags; import android.content.pm.PackageManagerInternal; +import android.content.pm.SharedLibraryInfo; import android.content.pm.SigningDetails; import android.content.pm.dex.DexMetadataHelper; import android.content.pm.parsing.ApkLite; @@ -540,6 +542,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @GuardedBy("mLock") private DomainSet mPreVerifiedDomains; + private AtomicBoolean mDependencyInstallerEnabled = new AtomicBoolean(); + private AtomicInteger mMissingSharedLibraryCount = new AtomicInteger(); + static class FileEntry { private final int mIndex; private final InstallationFile mFile; @@ -3232,6 +3237,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (Flags.sdkDependencyInstaller() && params.isAutoInstallDependenciesEnabled && !isMultiPackage()) { + mDependencyInstallerEnabled.set(true); resolveLibraryDependenciesIfNeeded(); } else { install(); @@ -3241,8 +3247,20 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private void resolveLibraryDependenciesIfNeeded() { synchronized (mLock) { - mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(mPackageLite, - mPm.snapshotComputer(), userId, mHandler, + List<SharedLibraryInfo> missingLibraries = new ArrayList<>(); + try { + missingLibraries = mInstallDependencyHelper.getMissingSharedLibraries(mPackageLite); + } catch (PackageManagerException e) { + handleDependencyResolutionFailure(e); + } catch (Exception e) { + handleDependencyResolutionFailure( + new PackageManagerException( + INSTALL_FAILED_MISSING_SHARED_LIBRARY, e.getMessage())); + } + + mMissingSharedLibraryCount.set(missingLibraries.size()); + mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(missingLibraries, + mPackageLite, mPm.snapshotComputer(), userId, mHandler, new OutcomeReceiver<>() { @Override @@ -3252,14 +3270,21 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @Override public void onError(@NonNull PackageManagerException e) { - final String completeMsg = ExceptionUtils.getCompleteMessage(e); - setSessionFailed(e.error, completeMsg); - onSessionDependencyResolveFailure(e.error, completeMsg); + handleDependencyResolutionFailure(e); } }); } } + private void handleDependencyResolutionFailure(@NonNull PackageManagerException e) { + final String completeMsg = ExceptionUtils.getCompleteMessage(e); + setSessionFailed(e.error, completeMsg); + onSessionDependencyResolveFailure(e.error, completeMsg); + PackageMetrics.onDependencyInstallationFailure( + sessionId, getPackageName(), e.error, mInstallerUid, params, + mMissingSharedLibraryCount.get()); + } + /** * Stages this session for install and returns a * {@link InstallingSession} representing this new staged state. @@ -3327,7 +3352,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { synchronized (mLock) { return new InstallingSession(sessionId, stageDir, localObserver, params, mInstallSource, user, mSigningDetails, mInstallerUid, mPackageLite, mPreVerifiedDomains, mPm, - mHasAppMetadataFile); + mHasAppMetadataFile, mDependencyInstallerEnabled.get(), + mMissingSharedLibraryCount.get()); } } diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java index 0acadb129f2b..856d6a726da5 100644 --- a/services/core/java/com/android/server/pm/PackageMetrics.java +++ b/services/core/java/com/android/server/pm/PackageMetrics.java @@ -32,7 +32,9 @@ import android.app.admin.SecurityLog; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.content.pm.DataLoaderType; import android.content.pm.Flags; +import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.parsing.ApkLiteParseUtils; @@ -173,7 +175,10 @@ final class PackageMetrics { mInstallRequest.isInstallInherit() /* is_inherit */, mInstallRequest.isInstallForUsers() /* is_installing_existing_as_user */, mInstallRequest.isInstallMove() /* is_move_install */, - false /* is_staged */ + false /* is_staged */, + mInstallRequest + .isDependencyInstallerEnabled() /* is_install_dependencies_enabled */, + mInstallRequest.getMissingSharedLibraryCount() /* missing_dependencies_count */ ); } @@ -323,7 +328,53 @@ final class PackageMetrics { verifyingSession.isInherit() /* is_inherit */, false /* is_installing_existing_as_user */, false /* is_move_install */, - verifyingSession.isStaged() /* is_staged */ + verifyingSession.isStaged() /* is_staged */, + false /* is_install_dependencies_enabled */, + 0 /* missing_dependencies_count */ + ); + } + + static void onDependencyInstallationFailure( + int sessionId, String packageName, int errorCode, int installerPackageUid, + PackageInstaller.SessionParams params, int missingDependenciesCount) { + if (params == null) { + return; + } + int dataLoaderType = DataLoaderType.NONE; + if (params.dataLoaderParams != null) { + dataLoaderType = params.dataLoaderParams.getType(); + } + + FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_INSTALLATION_SESSION_REPORTED, + sessionId /* session_id */, + packageName /* package_name */, + INVALID_UID /* uid */, + null /* user_ids */, + null /* user_types */, + null /* original_user_ids */, + null /* original_user_types */, + errorCode /* public_return_code */, + 0 /* internal_error_code */, + 0 /* apks_size_bytes */, + 0 /* version_code */, + null /* install_steps */, + null /* step_duration_millis */, + 0 /* total_duration_millis */, + 0 /* install_flags */, + installerPackageUid /* installer_package_uid */, + INVALID_UID /* original_installer_package_uid */, + dataLoaderType /* data_loader_type */, + params.requireUserAction /* user_action_required_type */, + (params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0 /* is_instant */, + false /* is_replace */, + false /* is_system */, + params.mode + == PackageInstaller.SessionParams.MODE_INHERIT_EXISTING /* is_inherit */, + false /* is_installing_existing_as_user */, + false /* is_move_install */, + params.isStaged /* is_staged */, + true /* is_install_dependencies_enabled */, + missingDependenciesCount /* missing_dependencies_count */ ); } diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java index 542ae8eb9207..dd60a155f2fb 100644 --- a/services/core/java/com/android/server/pm/VerifyingSession.java +++ b/services/core/java/com/android/server/pm/VerifyingSession.java @@ -16,11 +16,7 @@ package com.android.server.pm; -import static android.content.Intent.EXTRA_LONG_VERSION_CODE; -import static android.content.Intent.EXTRA_PACKAGE_NAME; -import static android.content.Intent.EXTRA_VERSION_CODE; import static android.content.pm.PackageInstaller.SessionParams.MODE_INHERIT_EXISTING; -import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID; import static android.content.pm.PackageManager.INSTALL_SUCCEEDED; import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING; import static android.content.pm.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4; @@ -40,9 +36,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; import android.app.BroadcastOptions; -import android.content.BroadcastReceiver; import android.content.ComponentName; -import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.DataLoaderType; @@ -68,7 +62,6 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.incremental.IncrementalManager; import android.provider.DeviceConfig; -import android.provider.Settings; import android.text.TextUtils; import android.util.Pair; import android.util.Slog; diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 36bc0b93cd7c..ce8dc69e4b26 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -7093,7 +7093,11 @@ public final class PowerManagerService extends SystemService if ((flags & PowerManager.GO_TO_SLEEP_FLAG_SOFT_SLEEP) != 0) { if (mFoldGracePeriodProvider.isEnabled()) { if (!powerGroup.hasWakeLockKeepingScreenOnLocked()) { + Slog.d(TAG, "Showing dismissible keyguard"); mNotifier.showDismissibleKeyguard(); + } else { + Slog.i(TAG, "There is a screen wake lock present: " + + "sleep request will be ignored"); } continue; // never actually goes to sleep for SOFT_SLEEP } else { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 3467f947ece4..5989fc8465c6 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -3212,8 +3212,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * will be ignored. */ boolean isUniversalResizeable() { - final boolean isLargeScreen = mDisplayContent != null && mDisplayContent.getConfiguration() - .smallestScreenWidthDp >= WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP + final boolean isLargeScreen = mDisplayContent != null && mDisplayContent.isLargeScreen() && mDisplayContent.getIgnoreOrientationRequest(); if (!canBeUniversalResizeable(info.applicationInfo, mWmService, isLargeScreen, true /* forActivity */)) { @@ -4585,6 +4584,19 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } + /** + * Returns {@code true} if the requested orientation of this activity is the same as the + * resolved orientation of the from activity. + */ + private boolean isStartingOrientationCompatible(@NonNull ActivityRecord fromActivity) { + final int fromOrientation = fromActivity.getConfiguration().orientation; + final int requestedOrientation = getRequestedConfigurationOrientation(); + if (requestedOrientation == ORIENTATION_UNDEFINED) { + return fromOrientation == getConfiguration().orientation; + } + return fromOrientation == requestedOrientation; + } + private boolean transferStartingWindow(@NonNull ActivityRecord fromActivity) { final WindowState tStartingWindow = fromActivity.mStartingWindow; if (tStartingWindow != null && fromActivity.mStartingSurface != null) { @@ -4604,13 +4616,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } // Do not transfer if the orientation doesn't match, redraw starting window while it is // on top will cause flicker. - final int fromOrientation = fromActivity.getConfiguration().orientation; - final int requestedOrientation = getRequestedConfigurationOrientation(); - if (requestedOrientation == ORIENTATION_UNDEFINED) { - if (fromOrientation != getConfiguration().orientation) { - return false; - } - } else if (fromOrientation != requestedOrientation) { + if (!isStartingOrientationCompatible(fromActivity)) { return false; } @@ -4708,6 +4714,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } return true; } else if (fromActivity.mStartingData != null) { + if (fromActivity.mStartingData instanceof SnapshotStartingData + && !isStartingOrientationCompatible(fromActivity)) { + // Do not transfer because the snapshot will be distorted in different orientation. + return false; + } // The previous app was getting ready to show a // starting window, but hasn't yet done so. Steal it! ProtoLog.v(WM_DEBUG_STARTING_WINDOW, @@ -5408,10 +5419,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } void setDeferHidingClient() { + if (Flags.removeDeferHidingClient()) { + return; + } mDeferHidingClient = true; } void clearDeferHidingClient() { + if (Flags.removeDeferHidingClient()) { + return; + } if (!mDeferHidingClient) return; mDeferHidingClient = false; if (!mVisibleRequested) { @@ -7141,9 +7158,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Override void setClientVisible(boolean clientVisible) { - // TODO(shell-transitions): Remove mDeferHidingClient once everything is shell-transitions. - // pip activities should just remain in clientVisible. - if (!clientVisible && mDeferHidingClient) return; + if (!Flags.removeDeferHidingClient()) { + // TODO(shell-transitions): Remove mDeferHidingClient once everything is + // shell-transitions. pip activities should just remain in clientVisible. + if (!clientVisible && mDeferHidingClient) return; + } super.setClientVisible(clientVisible); } diff --git a/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java b/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java index 1208b6ef396f..08ceb61e14a8 100644 --- a/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java +++ b/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java @@ -142,6 +142,8 @@ public class ActivityServiceConnectionsHolder<T> { /** Used by {@link ActivityRecord#dump}. */ @Override public String toString() { - return String.valueOf(mConnections); + synchronized (mActivity) { + return String.valueOf(mConnections); + } } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index c2141a7103be..f8086615b7d1 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -439,6 +439,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ private boolean mSandboxDisplayApis = true; + /** Whether {@link #setIgnoreOrientationRequest} is called to override the default policy. */ + @VisibleForTesting + boolean mHasSetIgnoreOrientationRequest; + /** * Overridden display density for current user. Initialized with {@link #mInitialDisplayDensity} * but can be set from Settings or via shell command "adb shell wm density". @@ -6722,8 +6726,25 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return mDisplayPolicy.getSystemUiContext(); } + /** Returns {@code} true if the smallest screen width dp >= 600. */ + boolean isLargeScreen() { + return getConfiguration().smallestScreenWidthDp + >= WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP; + } + + @Override + boolean getIgnoreOrientationRequest() { + if (mHasSetIgnoreOrientationRequest + || !com.android.window.flags.Flags.universalResizableByDefault()) { + return super.getIgnoreOrientationRequest(); + } + // Large screen (sw >= 600dp) ignores orientation request by default. + return isLargeScreen() && !mWmService.isIgnoreOrientationRequestDisabled(); + } + @Override boolean setIgnoreOrientationRequest(boolean ignoreOrientationRequest) { + mHasSetIgnoreOrientationRequest = true; if (mSetIgnoreOrientationRequest == ignoreOrientationRequest) return false; final boolean rotationChanged = super.setIgnoreOrientationRequest(ignoreOrientationRequest); mWmService.mDisplayWindowSettings.setIgnoreOrientationRequest( diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java index f6d05d08cb04..f0ba822c37c5 100644 --- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java +++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java @@ -374,9 +374,9 @@ class DisplayWindowSettings { final DisplayInfo displayInfo = dc.getDisplayInfo(); final SettingsProvider.SettingsEntry settings = mSettingsProvider.getSettings(displayInfo); - final boolean ignoreOrientationRequest = settings.mIgnoreOrientationRequest != null - ? settings.mIgnoreOrientationRequest : false; - dc.setIgnoreOrientationRequest(ignoreOrientationRequest); + if (settings.mIgnoreOrientationRequest != null) { + dc.setIgnoreOrientationRequest(settings.mIgnoreOrientationRequest); + } dc.getDisplayRotation().resetAllowAllRotations(); } diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index 4230cd868c03..cba606cf2b0c 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -324,7 +324,8 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { if (target != imeControlTarget) { // TODO(b/353463205): check if fromUser=false is correct here boolean imeVisible = target.isRequestedVisible(WindowInsets.Type.ime()); - ImeTracker.Token statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE, + ImeTracker.Token statsToken = ImeTracker.forLogging().onStart( + imeVisible ? ImeTracker.TYPE_SHOW : ImeTracker.TYPE_HIDE, ImeTracker.ORIGIN_SERVER, imeVisible ? SoftInputShowHideReason.SHOW_INPUT_TARGET_CHANGED : SoftInputShowHideReason.HIDE_INPUT_TARGET_CHANGED, diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 46312aff1fb6..b3b8c6eab25e 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -156,6 +156,7 @@ import com.android.server.policy.PermissionPolicyInternal; import com.android.server.policy.WindowManagerPolicy; import com.android.server.utils.Slogf; import com.android.server.wm.utils.RegionUtils; +import com.android.window.flags.Flags; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -262,6 +263,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> int mCurrentUser; /** Root task id of the front root task when user switched, indexed by userId. */ SparseIntArray mUserRootTaskInFront = new SparseIntArray(2); + SparseArray<IntArray> mUserVisibleRootTasks = new SparseArray<>(); /** * A list of tokens that cause the top activity to be put to sleep. @@ -1924,7 +1926,18 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // appropriate. removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED); - mUserRootTaskInFront.put(mCurrentUser, focusRootTaskId); + if (Flags.enableTopVisibleRootTaskPerUserTracking()) { + final IntArray visibleRootTasks = new IntArray(); + forAllRootTasks(rootTask -> { + if (mCurrentUser == rootTask.mUserId && rootTask.isVisibleRequested()) { + visibleRootTasks.add(rootTask.getRootTaskId()); + } + }, /* traverseTopToBottom */ false); + mUserVisibleRootTasks.put(mCurrentUser, visibleRootTasks); + } else { + mUserRootTaskInFront.put(mCurrentUser, focusRootTaskId); + } + mCurrentUser = userId; mTaskSupervisor.mStartingUsers.add(uss); @@ -1937,22 +1950,60 @@ class RootWindowContainer extends WindowContainer<DisplayContent> Slog.i(TAG, "Persisting top task because it belongs to an always-visible user"); // For a normal user-switch, we will restore the new user's task. But if the pre-switch // top task is an always-visible (Communal) one, keep it even after the switch. - mUserRootTaskInFront.put(mCurrentUser, focusRootTaskId); + if (Flags.enableTopVisibleRootTaskPerUserTracking()) { + final IntArray rootTasks = mUserVisibleRootTasks.get(mCurrentUser); + rootTasks.add(focusRootTaskId); + mUserVisibleRootTasks.put(mCurrentUser, rootTasks); + } else { + mUserRootTaskInFront.put(mCurrentUser, focusRootTaskId); + } + } final int restoreRootTaskId = mUserRootTaskInFront.get(userId); + final IntArray rootTaskIdsToRestore = mUserVisibleRootTasks.get(userId); + boolean homeInFront = false; + if (Flags.enableTopVisibleRootTaskPerUserTracking()) { + if (rootTaskIdsToRestore == null) { + // If there are no root tasks saved, try restore id 0 which should create and launch + // the home task. + handleRootTaskLaunchOnUserSwitch(/* restoreRootTaskId */INVALID_TASK_ID); + homeInFront = true; + } else { + for (int i = 0; i < rootTaskIdsToRestore.size(); i++) { + handleRootTaskLaunchOnUserSwitch(rootTaskIdsToRestore.get(i)); + } + // Check if the top task is type home + if (rootTaskIdsToRestore.size() > 0) { + final int topRootTaskId = rootTaskIdsToRestore.get( + rootTaskIdsToRestore.size() - 1); + homeInFront = isHomeTask(topRootTaskId); + } + } + } else { + handleRootTaskLaunchOnUserSwitch(restoreRootTaskId); + // Check if the top task is type home + homeInFront = isHomeTask(restoreRootTaskId); + } + return homeInFront; + } + + private boolean isHomeTask(int taskId) { + final Task rootTask = getRootTask(taskId); + return rootTask != null && rootTask.isActivityTypeHome(); + } + + private void handleRootTaskLaunchOnUserSwitch(int restoreRootTaskId) { Task rootTask = getRootTask(restoreRootTaskId); if (rootTask == null) { rootTask = getDefaultTaskDisplayArea().getOrCreateRootHomeTask(); } - final boolean homeInFront = rootTask.isActivityTypeHome(); if (rootTask.isOnHomeDisplay()) { rootTask.moveToFront("switchUserOnHomeDisplay"); } else { // Root task was moved to another display while user was swapped out. resumeHomeActivity(null, "switchUserOnOtherDisplay", getDefaultTaskDisplayArea()); } - return homeInFront; } /** Returns whether the given user is to be always-visible (e.g. a communal profile). */ @@ -1963,7 +2014,11 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } void removeUser(int userId) { - mUserRootTaskInFront.delete(userId); + if (Flags.enableTopVisibleRootTaskPerUserTracking()) { + mUserVisibleRootTasks.delete(userId); + } else { + mUserRootTaskInFront.delete(userId); + } } /** @@ -1976,7 +2031,13 @@ class RootWindowContainer extends WindowContainer<DisplayContent> rootTask = getDefaultTaskDisplayArea().getOrCreateRootHomeTask(); } - mUserRootTaskInFront.put(userId, rootTask.getRootTaskId()); + if (Flags.enableTopVisibleRootTaskPerUserTracking()) { + final IntArray rootTasks = mUserVisibleRootTasks.get(userId, new IntArray()); + rootTasks.add(rootTask.getRootTaskId()); + mUserVisibleRootTasks.put(userId, rootTasks); + } else { + mUserRootTaskInFront.put(userId, rootTask.getRootTaskId()); + } } } @@ -2124,7 +2185,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> if (!tf.isOrganizedTaskFragment()) { return; } - tf.resetAdjacentTaskFragment(); + tf.clearAdjacentTaskFragments(); tf.setCompanionTaskFragment(null /* companionTaskFragment */); tf.setAnimationParams(TaskFragmentAnimationParams.DEFAULT); if (tf.getTopNonFinishingActivity() != null) { diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 51b8bd1f0091..9fb5bea132ef 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -94,6 +94,7 @@ import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; import android.os.UserHandle; +import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.Slog; import android.util.proto.ProtoOutputStream; @@ -239,12 +240,20 @@ class TaskFragment extends WindowContainer<WindowContainer> { /** This task fragment will be removed when the cleanup of its children are done. */ private boolean mIsRemovalRequested; - /** The TaskFragment that is adjacent to this one. */ + /** @deprecated b/373709676 replace with {@link #mAdjacentTaskFragments} */ + @Deprecated @Nullable private TaskFragment mAdjacentTaskFragment; /** - * Unlike the {@link mAdjacentTaskFragment}, the companion TaskFragment is not always visually + * The TaskFragments that are adjacent to each other, including this TaskFragment. + * All TaskFragments in this set share the same set instance. + */ + @Nullable + private AdjacentSet mAdjacentTaskFragments; + + /** + * Unlike the {@link #mAdjacentTaskFragments}, the companion TaskFragment is not always visually * adjacent to this one, but this TaskFragment will be removed by the organizer if the * companion TaskFragment is removed. */ @@ -442,15 +451,24 @@ class TaskFragment extends WindowContainer<WindowContainer> { return service.mWindowOrganizerController.getTaskFragment(token); } - void setAdjacentTaskFragment(@Nullable TaskFragment taskFragment) { - if (mAdjacentTaskFragment == taskFragment) { - return; - } - resetAdjacentTaskFragment(); - if (taskFragment != null) { + /** @deprecated b/373709676 replace with {@link #setAdjacentTaskFragments}. */ + @Deprecated + void setAdjacentTaskFragment(@NonNull TaskFragment taskFragment) { + if (!Flags.allowMultipleAdjacentTaskFragments()) { + if (mAdjacentTaskFragment == taskFragment) { + return; + } + resetAdjacentTaskFragment(); mAdjacentTaskFragment = taskFragment; taskFragment.setAdjacentTaskFragment(this); + return; } + + setAdjacentTaskFragments(new AdjacentSet(this, taskFragment)); + } + + void setAdjacentTaskFragments(@NonNull AdjacentSet adjacentTaskFragments) { + adjacentTaskFragments.setAsAdjacent(); } void setCompanionTaskFragment(@Nullable TaskFragment companionTaskFragment) { @@ -461,7 +479,14 @@ class TaskFragment extends WindowContainer<WindowContainer> { return mCompanionTaskFragment; } - void resetAdjacentTaskFragment() { + /** @deprecated b/373709676 replace with {@link #clearAdjacentTaskFragments()}. */ + @Deprecated + private void resetAdjacentTaskFragment() { + if (Flags.allowMultipleAdjacentTaskFragments()) { + throw new IllegalStateException("resetAdjacentTaskFragment shouldn't be called when" + + " allowMultipleAdjacentTaskFragments is enabled. Use either" + + " #clearAdjacentTaskFragments or #removeFromAdjacentTaskFragments"); + } // Reset the adjacent TaskFragment if its adjacent TaskFragment is also this TaskFragment. if (mAdjacentTaskFragment != null && mAdjacentTaskFragment.mAdjacentTaskFragment == this) { mAdjacentTaskFragment.mAdjacentTaskFragment = null; @@ -471,6 +496,57 @@ class TaskFragment extends WindowContainer<WindowContainer> { mDelayLastActivityRemoval = false; } + void clearAdjacentTaskFragments() { + if (!Flags.allowMultipleAdjacentTaskFragments()) { + resetAdjacentTaskFragment(); + return; + } + + if (mAdjacentTaskFragments != null) { + mAdjacentTaskFragments.clear(); + } + } + + void removeFromAdjacentTaskFragments() { + if (!Flags.allowMultipleAdjacentTaskFragments()) { + resetAdjacentTaskFragment(); + return; + } + + if (mAdjacentTaskFragments != null) { + mAdjacentTaskFragments.remove(this); + } + } + + // TODO(b/373709676): update usages. + /** @deprecated b/373709676 replace with {@link #getAdjacentTaskFragments()}. */ + @Deprecated + @Nullable + TaskFragment getAdjacentTaskFragment() { + return mAdjacentTaskFragment; + } + + @Nullable + AdjacentSet getAdjacentTaskFragments() { + return mAdjacentTaskFragments; + } + + boolean hasAdjacentTaskFragment() { + if (!Flags.allowMultipleAdjacentTaskFragments()) { + return mAdjacentTaskFragment != null; + } + return mAdjacentTaskFragments != null; + } + + boolean isAdjacentTo(@NonNull TaskFragment other) { + if (!Flags.allowMultipleAdjacentTaskFragments()) { + return mAdjacentTaskFragment == other; + } + return other != this + && mAdjacentTaskFragments != null + && mAdjacentTaskFragments.contains(other); + } + void setTaskFragmentOrganizer(@NonNull TaskFragmentOrganizerToken organizer, int uid, @NonNull String processName) { mTaskFragmentOrganizer = ITaskFragmentOrganizer.Stub.asInterface(organizer.asBinder()); @@ -566,10 +642,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { return isEmbedded() && mPinned; } - TaskFragment getAdjacentTaskFragment() { - return mAdjacentTaskFragment; - } - /** Returns the currently topmost resumed activity. */ @Nullable ActivityRecord getTopResumedActivity() { @@ -616,7 +688,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { mResumedActivity = r; final ActivityRecord topResumed = mTaskSupervisor.updateTopResumedActivityIfNeeded(reason); if (mResumedActivity != null && topResumed != null && topResumed.isEmbedded() - && topResumed.getTaskFragment().getAdjacentTaskFragment() == this) { + && topResumed.getTaskFragment().isAdjacentTo(this)) { // Explicitly updates the last resumed Activity if the resumed activity is // adjacent to the top-resumed embedded activity. mAtmService.setLastResumedActivityUncheckLocked(mResumedActivity, reason); @@ -2036,7 +2108,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { private boolean shouldReportOrientationUnspecified() { // Apps and their containers are not allowed to specify orientation from adjacent // TaskFragment. - return getAdjacentTaskFragment() != null && isVisibleRequested(); + return hasAdjacentTaskFragment() && isVisibleRequested(); } @Override @@ -3086,7 +3158,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { EventLogTags.writeWmTfRemoved(System.identityHashCode(this), getTaskId()); } mIsRemovalRequested = false; - resetAdjacentTaskFragment(); + removeFromAdjacentTaskFragments(); cleanUpEmbeddedTaskFragment(); final boolean shouldExecuteAppTransition = mClearedTaskFragmentForPip && isTaskVisibleRequested(); @@ -3267,9 +3339,16 @@ class TaskFragment extends WindowContainer<WindowContainer> { sb.append(" organizerProc="); sb.append(mTaskFragmentOrganizerProcessName); } - if (mAdjacentTaskFragment != null) { - sb.append(" adjacent="); - sb.append(mAdjacentTaskFragment); + if (Flags.allowMultipleAdjacentTaskFragments()) { + if (mAdjacentTaskFragments != null) { + sb.append(" adjacent="); + sb.append(mAdjacentTaskFragments); + } + } else { + if (mAdjacentTaskFragment != null) { + sb.append(" adjacent="); + sb.append(mAdjacentTaskFragment); + } } sb.append('}'); return sb.toString(); @@ -3385,4 +3464,94 @@ class TaskFragment extends WindowContainer<WindowContainer> { proto.end(token); } + + /** Set of {@link TaskFragment}s that are adjacent to each other. */ + static class AdjacentSet { + private final ArraySet<TaskFragment> mAdjacentSet; + + AdjacentSet(@NonNull TaskFragment... taskFragments) { + this(new ArraySet<>(taskFragments)); + } + + AdjacentSet(@NonNull ArraySet<TaskFragment> taskFragments) { + if (!Flags.allowMultipleAdjacentTaskFragments()) { + throw new IllegalStateException("allowMultipleAdjacentTaskFragments must be" + + " enabled to set more than two TaskFragments adjacent to each other."); + } + if (taskFragments.size() < 2) { + throw new IllegalArgumentException("Adjacent TaskFragments must contain at least" + + " two TaskFragments, but only " + taskFragments.size() + + " were provided."); + } + mAdjacentSet = taskFragments; + } + + /** Updates each {@link TaskFragment} in the set to be adjacent to each other. */ + private void setAsAdjacent() { + if (mAdjacentSet.isEmpty() + || equals(mAdjacentSet.valueAt(0).mAdjacentTaskFragments)) { + // No need to update if any TaskFragment in the set has already been updated to the + // same set. + return; + } + for (int i = mAdjacentSet.size() - 1; i >= 0; i--) { + final TaskFragment taskFragment = mAdjacentSet.valueAt(i); + taskFragment.removeFromAdjacentTaskFragments(); + taskFragment.mAdjacentTaskFragments = this; + } + } + + /** Removes the {@link TaskFragment} from the adjacent set. */ + private void remove(@NonNull TaskFragment taskFragment) { + taskFragment.mAdjacentTaskFragments = null; + taskFragment.mDelayLastActivityRemoval = false; + mAdjacentSet.remove(taskFragment); + if (mAdjacentSet.size() < 2) { + // To be considered as adjacent, there must be at least 2 TaskFragments in the set. + clear(); + } + } + + /** Clears the adjacent relationship. */ + private void clear() { + for (int i = mAdjacentSet.size() - 1; i >= 0; i--) { + final TaskFragment taskFragment = mAdjacentSet.valueAt(i); + // Clear all reference. + taskFragment.mAdjacentTaskFragments = null; + taskFragment.mDelayLastActivityRemoval = false; + } + mAdjacentSet.clear(); + } + + /** Whether the {@link TaskFragment} is in this adjacent set. */ + boolean contains(@NonNull TaskFragment taskFragment) { + return mAdjacentSet.contains(taskFragment); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (!(o instanceof AdjacentSet other)) { + return false; + } + return mAdjacentSet.equals(other.mAdjacentSet); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("AdjacentSet{"); + final int size = mAdjacentSet.size(); + for (int i = 0; i < size; i++) { + if (i != 0) { + sb.append(", "); + } + sb.append(mAdjacentSet.valueAt(i)); + } + sb.append("}"); + return sb.toString(); + } + } } diff --git a/services/core/java/com/android/server/wm/WindowManagerConstants.java b/services/core/java/com/android/server/wm/WindowManagerConstants.java index 3ad9b62ef058..9a5c8dffc0fc 100644 --- a/services/core/java/com/android/server/wm/WindowManagerConstants.java +++ b/services/core/java/com/android/server/wm/WindowManagerConstants.java @@ -42,12 +42,23 @@ final class WindowManagerConstants { * <ul> * <li>false: applies to no apps (default)</li> * <li>true: applies to all apps</li> - * <li>large: applies to all apps but only on large screens</li> * </ul> */ private static final String KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST = "ignore_activity_orientation_request"; + /** + * The orientation of activity will be always "unspecified" except for game apps. + * <p>Possible values: + * <ul> + * <li>none: applies to no apps (default)</li> + * <li>all: applies to all apps ({@see #KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST})</li> + * <li>large: applies to all apps but only on large screens</li> + * </ul> + */ + private static final String KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST_SCREENS = + "ignore_activity_orientation_request_screens"; + /** The packages that ignore {@link #KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST}. */ private static final String KEY_OPT_OUT_IGNORE_ACTIVITY_ORIENTATION_REQUEST_LIST = "opt_out_ignore_activity_orientation_request_list"; @@ -155,6 +166,7 @@ final class WindowManagerConstants { updateSystemGestureExclusionLogDebounceMillis(); break; case KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST: + case KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST_SCREENS: updateIgnoreActivityOrientationRequest(); break; case KEY_OPT_OUT_IGNORE_ACTIVITY_ORIENTATION_REQUEST_LIST: @@ -186,12 +198,16 @@ final class WindowManagerConstants { } private void updateIgnoreActivityOrientationRequest() { - final String value = mDeviceConfig.getProperty( + boolean allScreens = mDeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_WINDOW_MANAGER, + KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST, false); + String whichScreens = mDeviceConfig.getProperty( DeviceConfig.NAMESPACE_WINDOW_MANAGER, - KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST); - mIgnoreActivityOrientationRequestSmallScreen = Boolean.parseBoolean(value); - mIgnoreActivityOrientationRequestLargeScreen = mIgnoreActivityOrientationRequestSmallScreen - || ("large".equals(value)); + KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST_SCREENS); + allScreens |= ("all".equalsIgnoreCase(whichScreens)); + boolean largeScreens = allScreens || ("large".equalsIgnoreCase(whichScreens)); + mIgnoreActivityOrientationRequestSmallScreen = allScreens; + mIgnoreActivityOrientationRequestLargeScreen = largeScreens; } private void updateOptOutIgnoreActivityOrientationRequestList() { @@ -221,9 +237,9 @@ final class WindowManagerConstants { pw.print("="); pw.println(mSystemGestureExclusionLimitDp); pw.print(" "); pw.print(KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE); pw.print("="); pw.println(mSystemGestureExcludedByPreQStickyImmersive); - pw.print(" "); pw.print(KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST); - pw.print("="); pw.println(mIgnoreActivityOrientationRequestSmallScreen ? "true" - : mIgnoreActivityOrientationRequestLargeScreen ? "large" : "false"); + pw.print(" "); pw.print(KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST_SCREENS); + pw.print("="); pw.println(mIgnoreActivityOrientationRequestSmallScreen ? "all" + : mIgnoreActivityOrientationRequestLargeScreen ? "large" : "none"); if (mOptOutIgnoreActivityOrientationRequestPackages != null) { pw.print(" "); pw.print(KEY_OPT_OUT_IGNORE_ACTIVITY_ORIENTATION_REQUEST_LIST); pw.print("="); pw.println(mOptOutIgnoreActivityOrientationRequestPackages); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 0b6ca75c5f0d..58319f4cdabc 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2673,7 +2673,7 @@ public class WindowManagerService extends IWindowManager.Stub if (outRelayoutResult != null) { if (win.syncNextBuffer() && viewVisibility == View.VISIBLE - && win.mSyncSeqId > lastSyncSeqId) { + && win.mSyncSeqId > lastSyncSeqId && !displayContent.mWaitingForConfig) { outRelayoutResult.syncSeqId = win.shouldSyncWithBuffers() ? win.mSyncSeqId : -1; diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 66921ff3adeb..1eb84650d591 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -1155,7 +1155,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } else if (!task.mCreatedByOrganizer) { throw new UnsupportedOperationException( "Cannot set non-organized task as adjacent flag root: " + wc); - } else if (task.getAdjacentTaskFragment() == null && !clearRoot) { + } else if (!task.hasAdjacentTaskFragment() && !clearRoot) { throw new UnsupportedOperationException( "Cannot set non-adjacent task as adjacent flag root: " + wc); } @@ -1645,9 +1645,15 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub opType, exception); break; } - if (taskFragment.getAdjacentTaskFragment() != secondaryTaskFragment) { + if (!taskFragment.isAdjacentTo(secondaryTaskFragment)) { // Only have lifecycle effect if the adjacent changed. - taskFragment.setAdjacentTaskFragment(secondaryTaskFragment); + if (Flags.allowMultipleAdjacentTaskFragments()) { + // Activity Embedding only set two TFs adjacent. + taskFragment.setAdjacentTaskFragments( + new TaskFragment.AdjacentSet(taskFragment, secondaryTaskFragment)); + } else { + taskFragment.setAdjacentTaskFragment(secondaryTaskFragment); + } effects |= TRANSACT_EFFECTS_LIFECYCLE; } @@ -1663,21 +1669,25 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub break; } case OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS: { - final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment(); - if (adjacentTaskFragment == null) { + if (!taskFragment.hasAdjacentTaskFragment()) { break; } - taskFragment.resetAdjacentTaskFragment(); - effects |= TRANSACT_EFFECTS_LIFECYCLE; - // Clear the focused app if the focused app is no longer visible after reset the - // adjacent TaskFragments. + // Check if the focused app is in the adjacent set that will be cleared. final ActivityRecord focusedApp = taskFragment.getDisplayContent().mFocusedApp; final TaskFragment focusedTaskFragment = focusedApp != null ? focusedApp.getTaskFragment() : null; - if ((focusedTaskFragment == taskFragment - || focusedTaskFragment == adjacentTaskFragment) + final boolean wasFocusedInAdjacent = focusedTaskFragment == taskFragment + || (focusedTaskFragment != null + && taskFragment.isAdjacentTo(focusedTaskFragment)); + + taskFragment.removeFromAdjacentTaskFragments(); + effects |= TRANSACT_EFFECTS_LIFECYCLE; + + // Clear the focused app if the focused app is no longer visible after reset the + // adjacent TaskFragments. + if (wasFocusedInAdjacent && !focusedTaskFragment.shouldBeVisible(null /* starting */)) { focusedTaskFragment.getDisplayContent().setFocusedApp(null /* newFocus */); } @@ -2207,10 +2217,15 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub throw new IllegalArgumentException("setAdjacentRootsHierarchyOp: Not created by" + " organizer root1=" + root1 + " root2=" + root2); } - if (root1.getAdjacentTaskFragment() == root2) { + if (root1.isAdjacentTo(root2)) { return TRANSACT_EFFECTS_NONE; } - root1.setAdjacentTaskFragment(root2); + if (Flags.allowMultipleAdjacentTaskFragments()) { + // TODO(b/373709676): allow three roots. + root1.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(root1, root2)); + } else { + root1.setAdjacentTaskFragment(root2); + } return TRANSACT_EFFECTS_LIFECYCLE; } @@ -2225,10 +2240,10 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub throw new IllegalArgumentException("clearAdjacentRootsHierarchyOp: Not created by" + " organizer root=" + root); } - if (root.getAdjacentTaskFragment() == null) { + if (!root.hasAdjacentTaskFragment()) { return TRANSACT_EFFECTS_NONE; } - root.resetAdjacentTaskFragment(); + root.removeFromAdjacentTaskFragments(); return TRANSACT_EFFECTS_LIFECYCLE; } diff --git a/services/core/jni/com_android_server_am_Freezer.cpp b/services/core/jni/com_android_server_am_Freezer.cpp index 81487281dee7..e9a99f0fab64 100644 --- a/services/core/jni/com_android_server_am_Freezer.cpp +++ b/services/core/jni/com_android_server_am_Freezer.cpp @@ -68,7 +68,7 @@ jint getBinderFreezeInfo(JNIEnv *env, jobject, jint pid) { bool isFreezerSupported(JNIEnv *env, jclass) { std::string path; - if (!getAttributePathForTask("FreezerState", getpid(), &path)) { + if (!CgroupGetAttributePathForTask("FreezerState", getpid(), &path)) { ALOGI("No attribute for FreezerState"); return false; } diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java index 0c9a89bb0a30..8533eafaf9e0 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java @@ -114,7 +114,7 @@ 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(mContext, requestId, - /*shouldShowCancellationUi=*/ true, packageName); + /*shouldShowCancellationUi=*/ true, packageName, mUserId); } /** @@ -177,7 +177,7 @@ public class CredentialManagerUi { IntentCreationResult intentCreationResult = IntentFactory .createCredentialSelectorIntentForCredMan(mContext, requestInfo, providerDataList, - new ArrayList<>(disabledProviderDataList), mResultReceiver); + new ArrayList<>(disabledProviderDataList), mResultReceiver, mUserId); requestSessionMetric.collectUiConfigurationResults( mContext, intentCreationResult, mUserId); Intent intent = intentCreationResult.getIntent(); @@ -211,7 +211,7 @@ public class CredentialManagerUi { RequestSessionMetric requestSessionMetric) { IntentCreationResult intentCreationResult = IntentFactory .createCredentialSelectorIntentForAutofill(mContext, requestInfo, new ArrayList<>(), - mResultReceiver); + mResultReceiver, mUserId); requestSessionMetric.collectUiConfigurationResults( mContext, intentCreationResult, mUserId); return intentCreationResult.getIntent(); 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 91f1aaf603e6..8ca39194de3b 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -2471,6 +2471,26 @@ public final class DisplayPowerControllerTest { eq(false)); } + @Test + public void onDisplayChange_canceledAfterStop() { + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); + + // stop the dpc (turn it down) + mHolder.dpc.stop(); + advanceTime(1); + + // To trigger all the changes that can happen, we will completely change the underlying + // display device. + setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class), + mock(DisplayDeviceConfig.class), /* isEnabled= */ true); + + // Call onDisplayChange after we stopped DPC and make sure it doesn't crash + mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY); + advanceTime(1); + + // No crash = success + } + /** * Creates a mock and registers it to {@link LocalServices}. */ diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamControllerTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamControllerTest.java index 874e99173c63..495e853370ee 100644 --- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamControllerTest.java +++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamControllerTest.java @@ -46,7 +46,6 @@ import android.os.RemoteException; import android.os.test.TestLooper; import android.service.dreams.IDreamService; -import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -108,10 +107,8 @@ public class DreamControllerTest { .thenReturn(Context.ACTIVITY_TASK_SERVICE); final PowerManager powerManager = new PowerManager(mContext, mPowerManager, null, null); - when(mContext.getSystemService(Context.POWER_SERVICE)) + when(mContext.getSystemService(PowerManager.class)) .thenReturn(powerManager); - when(mContext.getSystemServiceName(PowerManager.class)) - .thenReturn(Context.POWER_SERVICE); when(mContext.getResources()).thenReturn(mResources); mToken = new Binder(); @@ -234,8 +231,13 @@ public class DreamControllerTest { } @Test - @FlakyTest(bugId = 293109503) public void serviceDisconnect_resetsScreenTimeout() throws RemoteException { + when(mResources.getBoolean( + com.android.internal.R.bool.config_resetScreenTimeoutOnUnexpectedDreamExit)) + .thenReturn(true); + // Recreate DreamManager because the configuration gets retrieved in the constructor + mDreamController = new DreamController(mContext, mHandler, mListener); + // Start dream. mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/, 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/); @@ -254,8 +256,13 @@ public class DreamControllerTest { } @Test - @FlakyTest(bugId = 293109503) public void binderDied_resetsScreenTimeout() throws RemoteException { + when(mResources.getBoolean( + com.android.internal.R.bool.config_resetScreenTimeoutOnUnexpectedDreamExit)) + .thenReturn(true); + // Recreate DreamManager because the configuration gets retrieved in the constructor + mDreamController = new DreamController(mContext, mHandler, mListener); + // Start dream. mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/, 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/); diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java index 0304a74f7654..cd8d415bdfa2 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java @@ -21,11 +21,7 @@ import static android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import android.content.Context; import android.content.pm.SharedLibraryInfo; @@ -90,32 +86,15 @@ public class InstallDependencyHelperTest { } @Test - public void testResolveLibraryDependenciesIfNeeded_errorInSharedLibrariesImpl() - throws Exception { - doThrow(new PackageManagerException(new Exception("xyz"))) - .when(mSharedLibraries).collectMissingSharedLibraryInfos(any()); - - PackageLite pkg = getPackageLite(TEST_APP_USING_SDK1_AND_SDK2); - CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ false); - mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, mComputer, - 0, mHandler, callback); - callback.assertFailure(); - - assertThat(callback.error).hasMessageThat().contains("xyz"); - } - - @Test public void testResolveLibraryDependenciesIfNeeded_failsToBind() throws Exception { // Return a non-empty list as missing dependency PackageLite pkg = getPackageLite(TEST_APP_USING_SDK1_AND_SDK2); List<SharedLibraryInfo> missingDependency = Collections.singletonList( mock(SharedLibraryInfo.class)); - when(mSharedLibraries.collectMissingSharedLibraryInfos(eq(pkg))) - .thenReturn(missingDependency); CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ false); - mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, mComputer, - 0, mHandler, callback); + mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(missingDependency, pkg, + mComputer, 0, mHandler, callback); callback.assertFailure(); assertThat(callback.error).hasMessageThat().contains( @@ -128,12 +107,10 @@ public class InstallDependencyHelperTest { // Return an empty list as missing dependency PackageLite pkg = getPackageLite(TEST_APP_USING_SDK1_AND_SDK2); List<SharedLibraryInfo> missingDependency = Collections.emptyList(); - when(mSharedLibraries.collectMissingSharedLibraryInfos(eq(pkg))) - .thenReturn(missingDependency); CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ true); - mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, mComputer, - 0, mHandler, callback); + mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(missingDependency, pkg, + mComputer, 0, mHandler, callback); callback.assertSuccess(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index d4a921c5f00a..1b0d9dc3b170 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -554,6 +554,7 @@ public class ActivityRecordTests extends WindowTestsBase { @Test public void testSetRequestedOrientationUpdatesConfiguration() throws Exception { + mDisplayContent.setIgnoreOrientationRequest(false); final ActivityRecord activity = new ActivityBuilder(mAtm) .setCreateTask(true) .setConfigChanges(ORIENTATION_CONFIG_CHANGES) @@ -641,6 +642,7 @@ public class ActivityRecordTests extends WindowTestsBase { @Test public void ignoreRequestedOrientationForResizableInSplitWindows() { + mDisplayContent.setIgnoreOrientationRequest(false); final ActivityRecord activity = createActivityWith2LevelTask(); final Task task = activity.getTask(); final Task rootTask = activity.getRootTask(); @@ -685,6 +687,7 @@ public class ActivityRecordTests extends WindowTestsBase { @Test public void respectRequestedOrientationForNonResizableInSplitWindows() { + mDisplayContent.setIgnoreOrientationRequest(false); final TaskDisplayArea tda = mDisplayContent.getDefaultTaskDisplayArea(); spyOn(tda); doReturn(true).when(tda).supportsNonResizableMultiWindow(); @@ -1906,6 +1909,7 @@ public class ActivityRecordTests extends WindowTestsBase { @Test public void testActivityOnCancelFixedRotationTransform() { + mDisplayContent.setIgnoreOrientationRequest(false); final ActivityRecord activity = createActivityWithTask(); final DisplayRotation displayRotation = activity.mDisplayContent.getDisplayRotation(); final RemoteDisplayChangeController remoteDisplayChangeController = activity @@ -2054,6 +2058,7 @@ public class ActivityRecordTests extends WindowTestsBase { @Test public void testFixedRotationSnapshotStartingWindow() { + mDisplayContent.setIgnoreOrientationRequest(false); final ActivityRecord activity = createActivityWithTask(); // TaskSnapshotSurface requires a fullscreen opaque window. final WindowManager.LayoutParams params = new WindowManager.LayoutParams( @@ -2278,6 +2283,7 @@ public class ActivityRecordTests extends WindowTestsBase { @Test public void testSupportsFreeform() { final ActivityRecord activity = new ActivityBuilder(mAtm) + .setComponent(getUniqueComponentName(mContext.getPackageName())) .setCreateTask(true) .setResizeMode(ActivityInfo.RESIZE_MODE_UNRESIZEABLE) .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE) @@ -2410,6 +2416,7 @@ public class ActivityRecordTests extends WindowTestsBase { @Test public void testOrientationForScreenOrientationBehind() { + mDisplayContent.setIgnoreOrientationRequest(false); final Task task = createTask(mDisplayContent); // Activity below new ActivityBuilder(mAtm) @@ -2507,6 +2514,7 @@ public class ActivityRecordTests extends WindowTestsBase { @SetupWindows(addWindows = W_ACTIVITY) @Test public void testLandscapeSeascapeRotationByApp() { + mDisplayContent.setIgnoreOrientationRequest(false); final Task task = new TaskBuilder(mSupervisor) .setDisplay(mDisplayContent).setCreateActivity(true).build(); final ActivityRecord activity = task.getTopNonFinishingActivity(); @@ -2572,6 +2580,7 @@ public class ActivityRecordTests extends WindowTestsBase { @Test @Presubmit public void testGetOrientation() { + mDisplayContent.setIgnoreOrientationRequest(false); // ActivityBuilder will resume top activities and cause the activity been added into // opening apps list. Since this test is focus on the effect of visible on getting // orientation, we skip app transition to avoid interference. @@ -2663,8 +2672,8 @@ public class ActivityRecordTests extends WindowTestsBase { @Test public void testSetOrientation_restrictedByTargetSdk() { mSetFlagsRule.enableFlags(Flags.FLAG_UNIVERSAL_RESIZABLE_BY_DEFAULT); - mDisplayContent.setIgnoreOrientationRequest(true); makeDisplayLargeScreen(mDisplayContent); + assertTrue(mDisplayContent.getIgnoreOrientationRequest()); assertSetOrientation(Build.VERSION_CODES.CUR_DEVELOPMENT, CATEGORY_SOCIAL, false); assertSetOrientation(Build.VERSION_CODES.CUR_DEVELOPMENT, CATEGORY_GAME, true); @@ -2702,6 +2711,7 @@ public class ActivityRecordTests extends WindowTestsBase { @Test public void testRespectTopFullscreenOrientation() { + mDisplayContent.setIgnoreOrientationRequest(false); final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build(); final Configuration displayConfig = activity.mDisplayContent.getConfiguration(); final Configuration activityConfig = activity.getConfiguration(); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java index e0b29c937381..1cb1e3cae413 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java @@ -511,6 +511,7 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase { @Test public void testSupportsMultiWindow_nonResizable() { final ActivityRecord activity = new ActivityBuilder(mAtm) + .setComponent(getUniqueComponentName(mContext.getPackageName())) .setCreateTask(true) .setResizeMode(RESIZE_MODE_UNRESIZEABLE) .build(); diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java index 40da9ea2d718..579ed6659976 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java @@ -365,8 +365,8 @@ public class BackNavigationControllerTests extends WindowTestsBase { assertTrue(outPrevActivities.isEmpty()); assertTrue(predictable); // reset - tf1.setAdjacentTaskFragment(null); - tf2.setAdjacentTaskFragment(null); + tf1.clearAdjacentTaskFragments(); + tf2.clearAdjacentTaskFragments(); tf1.setCompanionTaskFragment(null); tf2.setCompanionTaskFragment(null); @@ -398,8 +398,8 @@ public class BackNavigationControllerTests extends WindowTestsBase { assertTrue(predictable); // reset outPrevActivities.clear(); - tf2.setAdjacentTaskFragment(null); - tf3.setAdjacentTaskFragment(null); + tf2.clearAdjacentTaskFragments(); + tf3.clearAdjacentTaskFragments(); final TaskFragment tf4 = createTaskFragmentWithActivity(task); // Stacked + next companion to top => predict for previous activity below companion. diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaGroupTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaGroupTest.java index 87dbca51e24e..060b379c1281 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaGroupTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaGroupTest.java @@ -84,6 +84,7 @@ public class DisplayAreaGroupTest extends WindowTestsBase { @Test public void testGetRequestedOrientationForDisplay() { + mDisplayContent.setIgnoreOrientationRequest(false); final Task task = new TaskBuilder(mSupervisor) .setTaskDisplayArea(mTaskDisplayArea).setCreateActivity(true).build(); final ActivityRecord activity = task.getTopNonFinishingActivity(); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java index 7b2cd63b4afb..0a7df5a305bc 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java @@ -491,6 +491,7 @@ public class DisplayAreaTest extends WindowTestsBase { @Test public void testSetIgnoreOrientationRequest_callSuperOnDescendantOrientationChangedNoSensor() { final TaskDisplayArea tda = mDisplayContent.getDefaultTaskDisplayArea(); + mDisplayContent.setIgnoreOrientationRequest(false); final Task stack = new TaskBuilder(mSupervisor).setOnTop(!ON_TOP).setCreateActivity(true).build(); final ActivityRecord activity = stack.getTopNonFinishingActivity(); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 9cbea2e2f0ad..db71f2bf039d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -898,6 +898,7 @@ public class DisplayContentTests extends WindowTestsBase { public void testOrientationDefinedByKeyguard() { final DisplayContent dc = mDisplayContent; dc.getDisplayPolicy().setAwake(true); + dc.setIgnoreOrientationRequest(false); // Create a window that requests landscape orientation. It will define device orientation // by default. @@ -925,6 +926,7 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testOrientationForAspectRatio() { final DisplayContent dc = createNewDisplay(); + dc.setIgnoreOrientationRequest(false); // When display content is created its configuration is not yet initialized, which could // cause unnecessary configuration propagation, so initialize it here. @@ -1034,6 +1036,7 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testAllowsTopmostFullscreenOrientation() { final DisplayContent dc = createNewDisplay(); + dc.setIgnoreOrientationRequest(false); assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, dc.getOrientation()); dc.getDisplayRotation().setFixedToUserRotation( IWindowManager.FIXED_TO_USER_ROTATION_DISABLED); @@ -1112,6 +1115,7 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testOnDescendantOrientationRequestChanged() { final DisplayContent dc = createNewDisplay(); + dc.setIgnoreOrientationRequest(false); dc.getDisplayRotation().setFixedToUserRotation( IWindowManager.FIXED_TO_USER_ROTATION_DISABLED); dc.getDefaultTaskDisplayArea().setWindowingMode(WINDOWING_MODE_FULLSCREEN); @@ -1130,6 +1134,7 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testOnDescendantOrientationRequestChanged_FrozenToUserRotation() { final DisplayContent dc = createNewDisplay(); + dc.setIgnoreOrientationRequest(false); dc.getDisplayRotation().setFixedToUserRotation( IWindowManager.FIXED_TO_USER_ROTATION_ENABLED); dc.getDisplayRotation().setUserRotation( @@ -1152,6 +1157,7 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testOrientationBehind() { assertNull(mDisplayContent.getLastOrientationSource()); + mDisplayContent.setIgnoreOrientationRequest(false); final ActivityRecord prev = new ActivityBuilder(mAtm).setCreateTask(true) .setScreenOrientation(getRotatedOrientation(mDisplayContent)).build(); prev.setVisibleRequested(false); @@ -1172,6 +1178,7 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testFixedToUserRotationChanged() { final DisplayContent dc = createNewDisplay(); + dc.setIgnoreOrientationRequest(false); dc.getDisplayRotation().setFixedToUserRotation( IWindowManager.FIXED_TO_USER_ROTATION_ENABLED); dc.getDisplayRotation().setUserRotation( @@ -1589,6 +1596,7 @@ public class DisplayContentTests extends WindowTestsBase { W_INPUT_METHOD, W_NOTIFICATION_SHADE }) @Test public void testApplyTopFixedRotationTransform() { + mDisplayContent.setIgnoreOrientationRequest(false); final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy(); spyOn(displayPolicy); // Only non-movable (gesture) navigation bar will be animated by fixed rotation animation. @@ -1742,6 +1750,7 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testFixedRotationWithPip() { final DisplayContent displayContent = mDefaultDisplay; + displayContent.setIgnoreOrientationRequest(false); unblockDisplayRotation(displayContent); // Unblock the condition in PinnedTaskController#continueOrientationChangeIfNeeded. doNothing().when(displayContent).prepareAppTransition(anyInt()); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java index 63973345b5fb..6527af1ec704 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java @@ -77,7 +77,7 @@ public class DisplayRotationImmersiveAppCompatPolicyTests extends WindowTestsBas when(mMockActivityRecord.findMainWindow()).thenReturn(mMockWindowState); doReturn(mMockActivityRecord).when(mDisplayContent).topRunningActivity(); - when(mDisplayContent.getIgnoreOrientationRequest()).thenReturn(true); + mDisplayContent.setIgnoreOrientationRequest(true); mMockAppCompatConfiguration = mock(AppCompatConfiguration.class); when(mMockAppCompatConfiguration.isDisplayRotationImmersiveAppCompatPolicyEnabled()) @@ -195,7 +195,7 @@ public class DisplayRotationImmersiveAppCompatPolicyTests extends WindowTestsBas @Test public void testIsRotationLockEnforced_ignoreOrientationRequestDisabled_lockNotEnforced() { - when(mDisplayContent.getIgnoreOrientationRequest()).thenReturn(false); + mDisplayContent.setIgnoreOrientationRequest(false); assertIsRotationLockEnforcedReturnsFalseForAllRotations(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java index 708d6860abc2..bd15bc42e811 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java @@ -106,6 +106,8 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { // Display: 1920x1200 (landscape). First and second display are both 860x1200 (portrait). mDisplay = new DualDisplayContent.Builder(mAtm, 1920, 1200).build(); + // The test verifies that the display area can affect display's getLastOrientation(). + mDisplay.setIgnoreOrientationRequest(false); mFirstRoot = mDisplay.mFirstRoot; mSecondRoot = mDisplay.mSecondRoot; mFirstTda = mDisplay.getTaskDisplayArea(FEATURE_FIRST_TASK_CONTAINER); diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java index 7cb62c5a6769..d96512588c7c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java @@ -132,6 +132,7 @@ public class RootTaskTests extends WindowTestsBase { @Test public void testClosingAppDifferentTaskOrientation() { + mDisplayContent.setIgnoreOrientationRequest(false); final ActivityRecord activity1 = createActivityRecord(mDisplayContent); activity1.setOrientation(SCREEN_ORIENTATION_LANDSCAPE); @@ -146,6 +147,7 @@ public class RootTaskTests extends WindowTestsBase { @Test public void testMoveTaskToBackDifferentTaskOrientation() { + mDisplayContent.setIgnoreOrientationRequest(false); final ActivityRecord activity1 = createActivityRecord(mDisplayContent); activity1.setOrientation(SCREEN_ORIENTATION_LANDSCAPE); 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 7e8bd38fb6a9..699ed0263756 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java @@ -41,6 +41,7 @@ import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP; import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE; import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; + import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; @@ -63,6 +64,7 @@ import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.testng.internal.junit.ArrayAsserts.assertArrayEquals; import android.app.ActivityOptions; import android.app.WindowConfiguration; @@ -77,12 +79,14 @@ import android.graphics.Rect; import android.os.PowerManager; import android.os.RemoteException; import android.os.UserHandle; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.util.Pair; import androidx.test.filters.MediumTest; import com.android.internal.app.ResolverActivity; +import com.android.window.flags.Flags; import org.junit.Before; import org.junit.Test; @@ -693,6 +697,7 @@ public class RootWindowContainerTests extends WindowTestsBase { @Test public void testAwakeFromSleepingWithAppConfiguration() { final DisplayContent display = mRootWindowContainer.getDefaultDisplay(); + display.setIgnoreOrientationRequest(false); final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build(); activity.moveFocusableActivityToTop("test"); assertTrue(activity.getRootTask().isFocusedRootTaskOnDisplay()); @@ -1331,6 +1336,38 @@ public class RootWindowContainerTests extends WindowTestsBase { assertEquals(taskDisplayArea.getTopRootTask(), taskDisplayArea.getRootHomeTask()); } + @EnableFlags(Flags.FLAG_ENABLE_TOP_VISIBLE_ROOT_TASK_PER_USER_TRACKING) + @Test + public void testSwitchUser_withVisibleRootTasks_storesAllVisibleRootTasksForCurrentUser() { + // Set up root tasks + final Task rootTask1 = mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final Task rootTask2 = mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask( + WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final Task rootTask3 = mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask( + WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */); + doReturn(rootTask3).when(mRootWindowContainer).getTopDisplayFocusedRootTask(); + + // Set up user ids and visibility + rootTask1.mUserId = mRootWindowContainer.mCurrentUser; + rootTask2.mUserId = mRootWindowContainer.mCurrentUser; + rootTask3.mUserId = mRootWindowContainer.mCurrentUser; + rootTask1.mVisibleRequested = false; + rootTask2.mVisibleRequested = true; + rootTask3.mVisibleRequested = true; + + // Switch to a different user + int currentUser = mRootWindowContainer.mCurrentUser; + int otherUser = currentUser + 1; + mRootWindowContainer.switchUser(otherUser, null); + + // Verify that the previous user persists it's previous visible root tasks + assertArrayEquals( + new int[]{rootTask2.mTaskId, rootTask3.mTaskId}, + mRootWindowContainer.mUserVisibleRootTasks.get(currentUser).toArray() + ); + } + @Test public void testLockAllProfileTasks() { final int profileUid = UserHandle.PER_USER_RANGE + UserHandle.MIN_SECONDARY_USER_ID; diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index bf96f0eb03b8..201ff51f1495 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -107,6 +107,8 @@ import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.provider.DeviceConfig; import android.provider.DeviceConfig.Properties; +import android.view.DisplayCutout; +import android.view.DisplayInfo; import android.view.InsetsFrameProvider; import android.view.InsetsSource; import android.view.InsetsState; @@ -210,6 +212,37 @@ public class SizeCompatTests extends WindowTestsBase { return setUpApp(builder.build(), appBuilder); } + private void setUpLargeScreenDisplayWithApp(int dw, int dh) { + final DisplayContent display = mDisplayContent; + final DisplayInfo displayInfo = display.getDisplayInfo(); + displayInfo.logicalWidth = dw; + displayInfo.logicalHeight = dh; + // Prevent legacy sdk from being affected by INSETS_DECOUPLED_CONFIGURATION_ENFORCED. + display.mInitialDisplayCutout = displayInfo.displayCutout = DisplayCutout.NO_CUTOUT; + // Smallest screen width=747dp according to 1400/(300/160). + display.mBaseDisplayDensity = displayInfo.logicalDensityDpi = + TestDisplayContent.DEFAULT_LOGICAL_DISPLAY_DENSITY; + doNothing().when(display).updateDisplayInfo(any()); + resizeDisplay(display, displayInfo.logicalWidth, displayInfo.logicalHeight); + assertTrue(display.isLargeScreen()); + if (com.android.window.flags.Flags.universalResizableByDefault()) { + assertTrue("Large screen must ignore orientation request", + display.getIgnoreOrientationRequest()); + } else { + display.setIgnoreOrientationRequest(true); + } + setUpApp(display, null /* appBuilder */); + spyOn(display.getDisplayRotation()); + } + + private void setUpLandscapeLargeScreenDisplayWithApp() { + setUpLargeScreenDisplayWithApp(/* dw */ 2800, /* dh */ 1400); + } + + private void setUpPortraitLargeScreenDisplayWithApp() { + setUpLargeScreenDisplayWithApp(/* dw */ 1400, /* dh */ 2800); + } + @Test public void testHorizontalReachabilityEnabledForTranslucentActivities() { testReachabilityEnabledForTranslucentActivity(/* dw */ 2500, /* dh */1000, @@ -658,9 +691,7 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testIsLetterboxed_activityFromBubble_returnsFalse() { - setUpDisplaySizeWithApp(1000, 2500); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - spyOn(mActivity); + setUpPortraitLargeScreenDisplayWithApp(); doReturn(true).when(mActivity).getLaunchedFromBubble(); prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE); @@ -1694,10 +1725,9 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testGetLetterboxInnerBounds_noScalingApplied() { // Set up a display in portrait and ignoring orientation request. - final int dw = 1400; - final int dh = 2800; - setUpDisplaySizeWithApp(dw, dh); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + setUpPortraitLargeScreenDisplayWithApp(); + final int dw = mDisplayContent.mBaseDisplayWidth; + final int dh = mDisplayContent.mBaseDisplayHeight; // Rotate display to landscape. rotateDisplay(mActivity.mDisplayContent, ROTATION_90); @@ -1823,8 +1853,7 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testDisplayIgnoreOrientationRequest_fixedOrientationAppLaunchedLetterbox() { // Set up a display in landscape and ignoring orientation request. - setUpDisplaySizeWithApp(2800, 1400); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + setUpLandscapeLargeScreenDisplayWithApp(); // Portrait fixed app without max aspect. prepareUnresizable(mActivity, /* maxAspect= */ 0, SCREEN_ORIENTATION_PORTRAIT); @@ -1852,8 +1881,7 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testDisplayIgnoreOrientationRequest_fixedOrientationAppRespectMinAspectRatio() { // Set up a display in landscape and ignoring orientation request. - setUpDisplaySizeWithApp(2800, 1400); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + setUpLandscapeLargeScreenDisplayWithApp(); // Portrait fixed app with min aspect ratio higher that aspect ratio override for fixed // orientation letterbox. @@ -1884,8 +1912,7 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testDisplayIgnoreOrientationRequest_fixedOrientationAppRespectMaxAspectRatio() { // Set up a display in landscape and ignoring orientation request. - setUpDisplaySizeWithApp(2800, 1400); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + setUpLandscapeLargeScreenDisplayWithApp(); // Portrait fixed app with max aspect ratio lower that aspect ratio override for fixed // orientation letterbox. @@ -1915,8 +1942,7 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testDisplayIgnoreOrientationRequest_fixedOrientationAppWithAspectRatioOverride() { // Set up a display in landscape and ignoring orientation request. - setUpDisplaySizeWithApp(2800, 1400); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + setUpLandscapeLargeScreenDisplayWithApp(); final float fixedOrientationLetterboxAspectRatio = 1.1f; mActivity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio( @@ -2011,8 +2037,7 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testDisplayIgnoreOrientationRequest_unresizableWithCorrespondingMinAspectRatio() { // Set up a display in landscape and ignoring orientation request. - setUpDisplaySizeWithApp(2800, 1400); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + setUpLandscapeLargeScreenDisplayWithApp(); final float fixedOrientationLetterboxAspectRatio = 1.1f; mActivity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio( @@ -2045,13 +2070,10 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testComputeConfigResourceOverrides_unresizableApp() { // Set up a display in landscape and ignoring orientation request. - setUpDisplaySizeWithApp(2800, 1400); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + setUpLandscapeLargeScreenDisplayWithApp(); prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); - final Rect activityBounds = new Rect(mActivity.getBounds()); - int originalScreenWidthDp = mActivity.getConfiguration().screenWidthDp; int originalScreenHeighthDp = mActivity.getConfiguration().screenHeightDp; @@ -2068,7 +2090,7 @@ public class SizeCompatTests extends WindowTestsBase { // After we rotate, the activity should go in the size-compat mode and report the same // configuration values. - assertDownScaled(); + assertThat(mActivity.inSizeCompatMode()).isTrue(); assertEquals(originalScreenWidthDp, mActivity.getConfiguration().smallestScreenWidthDp); assertEquals(originalScreenWidthDp, mActivity.getConfiguration().screenWidthDp); assertEquals(originalScreenHeighthDp, mActivity.getConfiguration().screenHeightDp); @@ -2087,14 +2109,11 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testComputeConfigResourceOverrides_resizableFixedOrientationActivity() { // Set up a display in landscape and ignoring orientation request. - setUpDisplaySizeWithApp(2800, 1400); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + setUpLandscapeLargeScreenDisplayWithApp(); // Portrait fixed app without max aspect. prepareLimitedBounds(mActivity, SCREEN_ORIENTATION_PORTRAIT, false /* isUnresizable */); - final Rect activityBounds = new Rect(mActivity.getBounds()); - int originalScreenWidthDp = mActivity.getConfiguration().screenWidthDp; int originalScreenHeighthDp = mActivity.getConfiguration().screenHeightDp; @@ -2206,10 +2225,10 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testSystemFullscreenOverrideForLandscapeDisplay() { - final int displayWidth = 1600; - final int displayHeight = 1400; - setUpDisplaySizeWithApp(displayWidth, displayHeight); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + setUpLandscapeLargeScreenDisplayWithApp(); + final int displayWidth = mDisplayContent.mBaseDisplayWidth; + final int displayHeight = mDisplayContent.mBaseDisplayHeight; + spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()); doReturn(true).when( mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()) @@ -2226,10 +2245,10 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testSystemFullscreenOverrideForPortraitDisplay() { - final int displayWidth = 1400; - final int displayHeight = 1600; - setUpDisplaySizeWithApp(displayWidth, displayHeight); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + setUpPortraitLargeScreenDisplayWithApp(); + final int displayWidth = mDisplayContent.mBaseDisplayWidth; + final int displayHeight = mDisplayContent.mBaseDisplayHeight; + spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()); doReturn(true).when( mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()) @@ -2619,7 +2638,7 @@ public class SizeCompatTests extends WindowTestsBase { @EnableCompatChanges({ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTATION}) public void testOverrideRespectRequestedOrientationIsEnabled_orientationIsRespected() { // Set up a display in landscape - setUpDisplaySizeWithApp(2800, 1400); + setUpDisplaySizeWithApp(1000, 500); final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */ false, RESIZE_MODE_UNRESIZEABLE, SCREEN_ORIENTATION_PORTRAIT); @@ -2636,7 +2655,7 @@ public class SizeCompatTests extends WindowTestsBase { @EnableCompatChanges({ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTATION}) public void testOverrideRespectRequestedOrientationIsEnabled_multiWindow_orientationIgnored() { // Set up a display in landscape - setUpDisplaySizeWithApp(2800, 1400); + setUpDisplaySizeWithApp(1000, 500); final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */ false, RESIZE_MODE_UNRESIZEABLE, SCREEN_ORIENTATION_PORTRAIT); @@ -2655,10 +2674,9 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testSplitAspectRatioForUnresizableLandscapeApps() { // Set up a display in portrait and ignoring orientation request. - int screenWidth = 1400; - int screenHeight = 1600; - setUpDisplaySizeWithApp(screenWidth, screenHeight); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + setUpLargeScreenDisplayWithApp(1400, 2400); + final int screenWidth = mDisplayContent.mBaseDisplayWidth; + final int screenHeight = mDisplayContent.mBaseDisplayHeight; mActivity.mWmService.mAppCompatConfiguration .setIsSplitScreenAspectRatioForUnresizableAppsEnabled(true); @@ -2692,10 +2710,9 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testDisplayAspectRatioForResizablePortraitApps() { // Set up a display in portrait and ignoring orientation request. - int displayWidth = 1400; - int displayHeight = 1600; - setUpDisplaySizeWithApp(displayWidth, displayHeight); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + setUpLargeScreenDisplayWithApp(1400, 2400); + final int displayWidth = mDisplayContent.mBaseDisplayWidth; + final int displayHeight = mDisplayContent.mBaseDisplayHeight; mWm.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(2f); // Enable display aspect ratio to take precedence before @@ -2728,10 +2745,9 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testDisplayAspectRatioForResizableLandscapeApps() { // Set up a display in landscape and ignoring orientation request. - int displayWidth = 1600; - int displayHeight = 1400; - setUpDisplaySizeWithApp(displayWidth, displayHeight); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + setUpLandscapeLargeScreenDisplayWithApp(); + final int displayWidth = mDisplayContent.mBaseDisplayWidth; + final int displayHeight = mDisplayContent.mBaseDisplayHeight; mWm.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(2f); // Enable display aspect ratio to take precedence before @@ -2764,10 +2780,9 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testDisplayAspectRatioForUnresizableLandscapeApps() { // Set up a display in portrait and ignoring orientation request. - int displayWidth = 1400; - int displayHeight = 1600; - setUpDisplaySizeWithApp(displayWidth, displayHeight); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + setUpPortraitLargeScreenDisplayWithApp(); + final int displayWidth = mDisplayContent.mBaseDisplayWidth; + final int displayHeight = mDisplayContent.mBaseDisplayHeight; mActivity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.1f); // Enable display aspect ratio to take precedence before @@ -2791,10 +2806,9 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testDisplayAspectRatioForUnresizablePortraitApps() { // Set up a display in landscape and ignoring orientation request. - int displayWidth = 1600; - int displayHeight = 1400; - setUpDisplaySizeWithApp(displayWidth, displayHeight); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + setUpLandscapeLargeScreenDisplayWithApp(); + final int displayWidth = mDisplayContent.mBaseDisplayWidth; + final int displayHeight = mDisplayContent.mBaseDisplayHeight; mActivity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.1f); // Enable display aspect ratio to take precedence before @@ -2819,8 +2833,7 @@ public class SizeCompatTests extends WindowTestsBase { public void testDisplayIgnoreOrientationRequest_orientationLetterboxBecameSizeCompatAfterRotate() { // Set up a display in landscape and ignoring orientation request. - setUpDisplaySizeWithApp(2800, 1400); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + setUpLandscapeLargeScreenDisplayWithApp(); // Portrait fixed app without max aspect. prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT); @@ -2837,7 +2850,7 @@ public class SizeCompatTests extends WindowTestsBase { // App should be in size compat. assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy() .isLetterboxedForFixedOrientationAndAspectRatio()); - assertDownScaled(); + assertThat(mActivity.inSizeCompatMode()).isTrue(); assertEquals(activityBounds.width(), newActivityBounds.width()); assertEquals(activityBounds.height(), newActivityBounds.height()); assertActivityMaxBoundsSandboxed(); @@ -2846,8 +2859,7 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testDisplayIgnoreOrientationRequest_sizeCompatAfterRotate() { // Set up a display in portrait and ignoring orientation request. - setUpDisplaySizeWithApp(1400, 2800); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + setUpPortraitLargeScreenDisplayWithApp(); // Portrait fixed app without max aspect. prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT); @@ -2882,9 +2894,8 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testDisplayIgnoreOrientationRequest_newLaunchedOrientationAppInLetterbox() { // Set up a display in landscape and ignoring orientation request. - setUpDisplaySizeWithApp(2800, 1400); + setUpLandscapeLargeScreenDisplayWithApp(); final DisplayContent display = mActivity.mDisplayContent; - display.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); // Portrait fixed app without max aspect. prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT); @@ -2928,9 +2939,7 @@ public class SizeCompatTests extends WindowTestsBase { @EnableCompatChanges({ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION}) public void testDisplayIgnoreOrientationRequest_orientationChangedToUnspecified() { // Set up a display in landscape and ignoring orientation request. - setUpDisplaySizeWithApp(2800, 1400); - final DisplayContent display = mActivity.mDisplayContent; - display.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + setUpLandscapeLargeScreenDisplayWithApp(); // Portrait fixed app without max aspect. prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT); @@ -2950,9 +2959,8 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testDisplayIgnoreOrientationRequest_newLaunchedMaxAspectApp() { // Set up a display in landscape and ignoring orientation request. - setUpDisplaySizeWithApp(2800, 1400); + setUpLandscapeLargeScreenDisplayWithApp(); final DisplayContent display = mActivity.mDisplayContent; - display.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); // Portrait fixed app without max aspect. prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT); @@ -3001,9 +3009,7 @@ public class SizeCompatTests extends WindowTestsBase { @SuppressWarnings("GuardedBy") public void testDisplayIgnoreOrientationRequest_pausedAppNotLostSizeCompat() { // Set up a display in landscape and ignoring orientation request. - setUpDisplaySizeWithApp(2800, 1400); - final DisplayContent display = mActivity.mDisplayContent; - display.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + setUpLandscapeLargeScreenDisplayWithApp(); // Portrait fixed app. prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT); @@ -3019,7 +3025,6 @@ public class SizeCompatTests extends WindowTestsBase { // App should be in size compat. assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy() .isLetterboxedForFixedOrientationAndAspectRatio()); - assertDownScaled(); assertThat(mActivity.inSizeCompatMode()).isTrue(); // Activity max bounds are sandboxed due to size compat mode. assertActivityMaxBoundsSandboxed(); @@ -3035,7 +3040,7 @@ public class SizeCompatTests extends WindowTestsBase { verify(scmPolicy, never()).clearSizeCompatMode(); assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy() .isLetterboxedForFixedOrientationAndAspectRatio()); - assertDownScaled(); + assertThat(mActivity.inSizeCompatMode()).isTrue(); assertEquals(activityBounds, mActivity.getBounds()); // Activity max bounds are sandboxed due to size compat. assertActivityMaxBoundsSandboxed(); @@ -3044,9 +3049,8 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testDisplayIgnoreOrientationRequest_rotated180_notInSizeCompat() { // Set up a display in landscape and ignoring orientation request. - setUpDisplaySizeWithApp(2800, 1400); + setUpLandscapeLargeScreenDisplayWithApp(); final DisplayContent display = mActivity.mDisplayContent; - display.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); // Portrait fixed app. prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT); @@ -3063,7 +3067,7 @@ public class SizeCompatTests extends WindowTestsBase { // App should be in size compat. assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy() .isLetterboxedForFixedOrientationAndAspectRatio()); - assertDownScaled(); + assertThat(mActivity.inSizeCompatMode()).isTrue(); assertActivityMaxBoundsSandboxed(); // Rotate display to landscape. @@ -3246,8 +3250,9 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testTaskDisplayAreaNotFillDisplay() { - setUpDisplaySizeWithApp(1400, 2800); + setUpPortraitLargeScreenDisplayWithApp(); final DisplayContent display = mActivity.mDisplayContent; + display.setIgnoreOrientationRequest(false); final TaskDisplayArea taskDisplayArea = mActivity.getDisplayArea(); taskDisplayArea.setBounds(0, 0, 1000, 2400); @@ -3430,8 +3435,7 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testIsHorizontalReachabilityEnabled_splitScreen_false() { mAtm.mDevEnableNonResizableMultiWindow = true; - setUpDisplaySizeWithApp(2800, 1000); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + setUpLandscapeLargeScreenDisplayWithApp(); mWm.mAppCompatConfiguration.setIsHorizontalReachabilityEnabled(true); setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true); final TestSplitOrganizer organizer = @@ -3518,8 +3522,7 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testIsHorizontalReachabilityEnabled_emptyBounds_true() { - setUpDisplaySizeWithApp(/* dw */ 2800, /* dh */ 1000); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + setUpLandscapeLargeScreenDisplayWithApp(); mWm.mAppCompatConfiguration.setIsHorizontalReachabilityEnabled(true); setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true); @@ -3566,8 +3569,7 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testIsHorizontalReachabilityEnabled_doesNotMatchParentHeight_false() { - setUpDisplaySizeWithApp(2800, 1000); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + setUpLandscapeLargeScreenDisplayWithApp(); mWm.mAppCompatConfiguration.setIsHorizontalReachabilityEnabled(true); setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true); @@ -3787,12 +3789,10 @@ public class SizeCompatTests extends WindowTestsBase { } private void assertLandscapeActivityAlignedToBottomWithNavbar(boolean immersive) { - final int screenHeight = 2800; - final int screenWidth = 1400; + setUpPortraitLargeScreenDisplayWithApp(); + final int screenHeight = mDisplayContent.mBaseDisplayHeight; + final int screenWidth = mDisplayContent.mBaseDisplayWidth; final int taskbarHeight = 200; - setUpDisplaySizeWithApp(screenWidth, screenHeight); - - mActivity.mDisplayContent.setIgnoreOrientationRequest(true); mActivity.mWmService.mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier(1.0f); final InsetsSource navSource = new InsetsSource( @@ -3972,8 +3972,7 @@ public class SizeCompatTests extends WindowTestsBase { float letterboxHorizontalPositionMultiplier, Rect fixedOrientationLetterbox, Rect sizeCompatUnscaled, Rect sizeCompatScaled) { // Set up a display in landscape and ignoring orientation request. - setUpDisplaySizeWithApp(2800, 1400); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + setUpLandscapeLargeScreenDisplayWithApp(); mActivity.mWmService.mAppCompatConfiguration.setLetterboxHorizontalPositionMultiplier( letterboxHorizontalPositionMultiplier); @@ -4177,13 +4176,28 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testUpdateResolvedBoundsHorizontalPosition_activityFillParentWidth() { + // Set up a display in landscape and ignoring orientation request. + setUpLandscapeLargeScreenDisplayWithApp(); + prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE); + + final Consumer<Float> assertHorizontalPosition = letterboxHorizontalPositionMultiplier -> { + mActivity.mWmService.mAppCompatConfiguration.setLetterboxHorizontalPositionMultiplier( + letterboxHorizontalPositionMultiplier); + mActivity.recomputeConfiguration(); + assertFitted(); + // Rotate to put activity in size compat mode. + rotateDisplay(mActivity.mDisplayContent, ROTATION_90); + assertTrue(mActivity.inSizeCompatMode()); + // Activity is in size compat mode but not scaled. + assertEquals(new Rect(0, 0, 1400, 700), mActivity.getBounds()); + if (letterboxHorizontalPositionMultiplier < 1f) { + rotateDisplay(mActivity.mDisplayContent, ROTATION_0); + } + }; // When activity width equals parent width, multiplier shouldn't have any effect. - assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity( - /* letterboxHorizontalPositionMultiplier */ 0.0f); - assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity( - /* letterboxHorizontalPositionMultiplier */ 0.5f); - assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity( - /* letterboxHorizontalPositionMultiplier */ 1.0f); + assertHorizontalPosition.accept(0.0f); + assertHorizontalPosition.accept(0.5f); + assertHorizontalPosition.accept(1.0f); } @Test @@ -4354,8 +4368,7 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testUpdateResolvedBoundsHorizontalPosition_bookModeEnabled() { // Set up a display in landscape with a fixed-orientation PORTRAIT app - setUpDisplaySizeWithApp(2800, 1400); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + setUpLandscapeLargeScreenDisplayWithApp(); mWm.mAppCompatConfiguration.setIsAutomaticReachabilityInBookModeEnabled(true); mWm.mAppCompatConfiguration.setLetterboxHorizontalPositionMultiplier( 1.0f /*letterboxHorizontalPositionMultiplier*/); @@ -4380,8 +4393,7 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testUpdateResolvedBoundsHorizontalPosition_bookModeDisabled_centered() { // Set up a display in landscape with a fixed-orientation PORTRAIT app - setUpDisplaySizeWithApp(2800, 1400); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + setUpLandscapeLargeScreenDisplayWithApp(); mWm.mAppCompatConfiguration.setIsAutomaticReachabilityInBookModeEnabled(false); mWm.mAppCompatConfiguration.setLetterboxHorizontalPositionMultiplier(0.5f); prepareUnresizable(mActivity, 1.75f, SCREEN_ORIENTATION_PORTRAIT); @@ -4462,8 +4474,7 @@ public class SizeCompatTests extends WindowTestsBase { float letterboxVerticalPositionMultiplier, Rect fixedOrientationLetterbox, Rect sizeCompatUnscaled, Rect sizeCompatScaled) { // Set up a display in portrait and ignoring orientation request. - setUpDisplaySizeWithApp(1400, 2800); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + setUpPortraitLargeScreenDisplayWithApp(); mActivity.mWmService.mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier( letterboxVerticalPositionMultiplier); @@ -5036,23 +5047,6 @@ public class SizeCompatTests extends WindowTestsBase { return (dimensionToSplit - (dividerWindowWidth - dividerInsets * 2)) / 2; } - private void assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity( - float letterboxHorizontalPositionMultiplier) { - // Set up a display in landscape and ignoring orientation request. - setUpDisplaySizeWithApp(2800, 1400); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - - mActivity.mWmService.mAppCompatConfiguration.setLetterboxHorizontalPositionMultiplier( - letterboxHorizontalPositionMultiplier); - prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE); - assertFitted(); - // Rotate to put activity in size compat mode. - rotateDisplay(mActivity.mDisplayContent, ROTATION_90); - assertTrue(mActivity.inSizeCompatMode()); - // Activity is in size compat mode but not scaled. - assertEquals(new Rect(0, 0, 1400, 700), mActivity.getBounds()); - } - private void assertVerticalPositionForDifferentDisplayConfigsForPortraitActivity( float letterboxVerticalPositionMultiplier) { // Set up a display in portrait and ignoring orientation request. diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java index 65a6a69fc45e..dafa96f91812 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java @@ -51,6 +51,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.clearInvocations; @@ -62,6 +63,7 @@ import android.content.res.Configuration; import android.graphics.Color; import android.graphics.Rect; import android.os.Binder; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.view.SurfaceControl; import android.view.View; @@ -1066,6 +1068,98 @@ public class TaskFragmentTest extends WindowTestsBase { Math.min(outConfig.screenWidthDp, outConfig.screenHeightDp)); } + @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS) + @Test + public void testSetAdjacentTaskFragments() { + final Task task = createTask(mDisplayContent); + final TaskFragment tf0 = createTaskFragmentWithActivity(task); + final TaskFragment tf1 = createTaskFragmentWithActivity(task); + final TaskFragment tf2 = createTaskFragmentWithActivity(task); + final TaskFragment.AdjacentSet adjacentTfs = new TaskFragment.AdjacentSet(tf0, tf1, tf2); + assertFalse(tf0.hasAdjacentTaskFragment()); + + tf0.setAdjacentTaskFragments(adjacentTfs); + + assertSame(adjacentTfs, tf0.getAdjacentTaskFragments()); + assertSame(adjacentTfs, tf1.getAdjacentTaskFragments()); + assertSame(adjacentTfs, tf2.getAdjacentTaskFragments()); + assertTrue(tf0.hasAdjacentTaskFragment()); + assertTrue(tf1.hasAdjacentTaskFragment()); + assertTrue(tf2.hasAdjacentTaskFragment()); + + final TaskFragment.AdjacentSet adjacentTfs2 = new TaskFragment.AdjacentSet(tf0, tf1); + tf0.setAdjacentTaskFragments(adjacentTfs2); + + assertSame(adjacentTfs2, tf0.getAdjacentTaskFragments()); + assertSame(adjacentTfs2, tf1.getAdjacentTaskFragments()); + assertNull(tf2.getAdjacentTaskFragments()); + assertTrue(tf0.hasAdjacentTaskFragment()); + assertTrue(tf1.hasAdjacentTaskFragment()); + assertFalse(tf2.hasAdjacentTaskFragment()); + } + + @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS) + @Test + public void testClearAdjacentTaskFragments() { + final Task task = createTask(mDisplayContent); + final TaskFragment tf0 = createTaskFragmentWithActivity(task); + final TaskFragment tf1 = createTaskFragmentWithActivity(task); + final TaskFragment tf2 = createTaskFragmentWithActivity(task); + final TaskFragment.AdjacentSet adjacentTfs = new TaskFragment.AdjacentSet(tf0, tf1, tf2); + tf0.setAdjacentTaskFragments(adjacentTfs); + + tf0.clearAdjacentTaskFragments(); + + assertNull(tf0.getAdjacentTaskFragments()); + assertNull(tf1.getAdjacentTaskFragments()); + assertNull(tf2.getAdjacentTaskFragments()); + assertFalse(tf0.hasAdjacentTaskFragment()); + assertFalse(tf1.hasAdjacentTaskFragment()); + assertFalse(tf2.hasAdjacentTaskFragment()); + } + + @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS) + @Test + public void testRemoveFromAdjacentTaskFragments() { + final Task task = createTask(mDisplayContent); + final TaskFragment tf0 = createTaskFragmentWithActivity(task); + final TaskFragment tf1 = createTaskFragmentWithActivity(task); + final TaskFragment tf2 = createTaskFragmentWithActivity(task); + final TaskFragment.AdjacentSet adjacentTfs = new TaskFragment.AdjacentSet(tf0, tf1, tf2); + tf0.setAdjacentTaskFragments(adjacentTfs); + + tf0.removeFromAdjacentTaskFragments(); + + assertNull(tf0.getAdjacentTaskFragments()); + assertSame(adjacentTfs, tf1.getAdjacentTaskFragments()); + assertSame(adjacentTfs, tf2.getAdjacentTaskFragments()); + assertFalse(adjacentTfs.contains(tf0)); + assertTrue(tf1.isAdjacentTo(tf2)); + assertTrue(tf2.isAdjacentTo(tf1)); + assertFalse(tf1.isAdjacentTo(tf0)); + assertFalse(tf0.isAdjacentTo(tf1)); + assertFalse(tf0.isAdjacentTo(tf0)); + assertFalse(tf1.isAdjacentTo(tf1)); + } + + @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS) + @Test + public void testRemoveFromAdjacentTaskFragmentsWhenRemove() { + final Task task = createTask(mDisplayContent); + final TaskFragment tf0 = createTaskFragmentWithActivity(task); + final TaskFragment tf1 = createTaskFragmentWithActivity(task); + final TaskFragment tf2 = createTaskFragmentWithActivity(task); + final TaskFragment.AdjacentSet adjacentTfs = new TaskFragment.AdjacentSet(tf0, tf1, tf2); + tf0.setAdjacentTaskFragments(adjacentTfs); + + tf0.removeImmediately(); + + assertNull(tf0.getAdjacentTaskFragments()); + assertSame(adjacentTfs, tf1.getAdjacentTaskFragments()); + assertSame(adjacentTfs, tf2.getAdjacentTaskFragments()); + assertFalse(adjacentTfs.contains(tf0)); + } + private WindowState createAppWindow(ActivityRecord app, String name) { final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, app, name, 0 /* ownerId */, false /* ownerCanAddInternalSystemWindow */, new TestIWindow()); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index e4512c31069a..1febc9fb4742 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -527,6 +527,7 @@ public class TaskTests extends WindowTestsBase { @Test public void testHandlesOrientationChangeFromDescendant() { + mDisplayContent.setIgnoreOrientationRequest(false); final Task rootTask = createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); final Task leafTask1 = createTaskInRootTask(rootTask, 0 /* userId */); @@ -1570,6 +1571,7 @@ public class TaskTests extends WindowTestsBase { @Test public void testNotSpecifyOrientationByFloatingTask() { + mDisplayContent.setIgnoreOrientationRequest(false); final Task task = new TaskBuilder(mSupervisor) .setCreateActivity(true).setCreateParentTask(true).build(); final ActivityRecord activity = task.getTopMostActivity(); @@ -1589,6 +1591,7 @@ public class TaskTests extends WindowTestsBase { @Test public void testNotSpecifyOrientation_taskDisplayAreaNotFocused() { + mDisplayContent.setIgnoreOrientationRequest(false); final TaskDisplayArea firstTaskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea(); final TaskDisplayArea secondTaskDisplayArea = createTaskDisplayArea( mDisplayContent, mRootWindowContainer.mWmService, "TestTaskDisplayArea", @@ -1625,6 +1628,7 @@ public class TaskTests extends WindowTestsBase { @Test public void testTaskOrientationOnDisplayWindowingModeChange() { + mDisplayContent.setIgnoreOrientationRequest(false); // Skip unnecessary operations to speed up the test. mAtm.deferWindowLayout(); final Task task = getTestTask(); diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 039a3ddd3e4f..78f32c1a4f88 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -1333,6 +1333,7 @@ public class TransitionTests extends WindowTestsBase { @Test public void testDeferRotationForTransientLaunch() { + mDisplayContent.setIgnoreOrientationRequest(false); final TestTransitionPlayer player = registerTestTransitionPlayer(); assumeFalse(mDisplayContent.mTransitionController.useShellTransitionsRotation()); final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build(); diff --git a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java index 42752c326615..f1180ff93edb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java @@ -314,6 +314,7 @@ public class TransparentPolicyTest extends WindowTestsBase { runTestScenario((robot) -> { robot.transparentActivity((ta) -> { ta.applyOnActivity((a) -> { + a.setIgnoreOrientationRequest(false); a.applyToTopActivity((topActivity) -> { topActivity.mWmService.mAppCompatConfiguration .setLetterboxHorizontalPositionMultiplier(1.0f); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index 410fa2879600..bb1e3ea078c3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -777,6 +777,7 @@ public class WindowOrganizerTests extends WindowTestsBase { @Test public void testSetIgnoreOrientationRequest_taskDisplayArea() { removeGlobalMinSizeRestriction(); + mDisplayContent.setIgnoreOrientationRequest(false); final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea(); final Task rootTask = taskDisplayArea.createRootTask( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); @@ -815,6 +816,7 @@ public class WindowOrganizerTests extends WindowTestsBase { @Test public void testSetIgnoreOrientationRequest_displayContent() { removeGlobalMinSizeRestriction(); + mDisplayContent.setIgnoreOrientationRequest(false); final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea(); final Task rootTask = taskDisplayArea.createRootTask( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 3a97cc621e0d..b27025c34d6b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -201,14 +201,10 @@ public class WindowTestsBase extends SystemServiceTestsBase { * {@link WindowTestsBase#setUpBase()}. */ private static boolean sGlobalOverridesChecked; + /** * Whether device-specific overrides have already been checked in - * {@link WindowTestsBase#setUpBase()} when the default display is used. - */ - private static boolean sOverridesCheckedDefaultDisplay; - /** - * Whether device-specific overrides have already been checked in - * {@link WindowTestsBase#setUpBase()} when a {@link TestDisplayContent} is used. + * {@link WindowTestsBase#setUpBase()}. */ private static boolean sOverridesCheckedTestDisplay; @@ -332,17 +328,14 @@ public class WindowTestsBase extends SystemServiceTestsBase { private void checkDeviceSpecificOverridesNotApplied() { // Check global overrides if (!sGlobalOverridesChecked) { + sGlobalOverridesChecked = true; assertEquals(0, mWm.mAppCompatConfiguration.getFixedOrientationLetterboxAspectRatio(), 0 /* delta */); - sGlobalOverridesChecked = true; } // Check display-specific overrides - if (!sOverridesCheckedDefaultDisplay && mDisplayContent == mDefaultDisplay) { - assertFalse(mDisplayContent.getIgnoreOrientationRequest()); - sOverridesCheckedDefaultDisplay = true; - } else if (!sOverridesCheckedTestDisplay && mDisplayContent instanceof TestDisplayContent) { - assertFalse(mDisplayContent.getIgnoreOrientationRequest()); + if (!sOverridesCheckedTestDisplay) { sOverridesCheckedTestDisplay = true; + assertFalse(mDisplayContent.mHasSetIgnoreOrientationRequest); } } @@ -1121,7 +1114,7 @@ public class WindowTestsBase extends SystemServiceTestsBase { displayContent.getDisplayRotation().configure(width, height); final Configuration c = new Configuration(); displayContent.computeScreenConfiguration(c); - displayContent.onRequestedOverrideConfigurationChanged(c); + displayContent.performDisplayOverrideConfigUpdate(c); } static void makeDisplayLargeScreen(DisplayContent displayContent) { diff --git a/telephony/java/android/telephony/CellIdentityCdma.java b/telephony/java/android/telephony/CellIdentityCdma.java index 4e5a246ef773..4cb622b3eb9e 100644 --- a/telephony/java/android/telephony/CellIdentityCdma.java +++ b/telephony/java/android/telephony/CellIdentityCdma.java @@ -105,20 +105,30 @@ public final class CellIdentityCdma extends CellIdentity { */ public CellIdentityCdma(int nid, int sid, int bid, int lon, int lat, @Nullable String alphal, @Nullable String alphas) { - super(TAG, CellInfo.TYPE_CDMA, null, null, alphal, alphas); - mNetworkId = inRangeOrUnavailable(nid, 0, NETWORK_ID_MAX); - mSystemId = inRangeOrUnavailable(sid, 0, SYSTEM_ID_MAX); - mBasestationId = inRangeOrUnavailable(bid, 0, BASESTATION_ID_MAX); - lat = inRangeOrUnavailable(lat, LATITUDE_MIN, LATITUDE_MAX); - lon = inRangeOrUnavailable(lon, LONGITUDE_MIN, LONGITUDE_MAX); - - if (!isNullIsland(lat, lon)) { - mLongitude = lon; - mLatitude = lat; + super(TAG, CellInfo.TYPE_CDMA, null, null, Flags.cleanupCdma() ? null : alphal, + Flags.cleanupCdma() ? null : alphas); + if (Flags.cleanupCdma()) { + mNetworkId = CellInfo.UNAVAILABLE; + mSystemId = CellInfo.UNAVAILABLE; + mBasestationId = CellInfo.UNAVAILABLE; + mLongitude = CellInfo.UNAVAILABLE; + mLatitude = CellInfo.UNAVAILABLE; + mGlobalCellId = null; } else { - mLongitude = mLatitude = CellInfo.UNAVAILABLE; + mNetworkId = inRangeOrUnavailable(nid, 0, NETWORK_ID_MAX); + mSystemId = inRangeOrUnavailable(sid, 0, SYSTEM_ID_MAX); + mBasestationId = inRangeOrUnavailable(bid, 0, BASESTATION_ID_MAX); + lat = inRangeOrUnavailable(lat, LATITUDE_MIN, LATITUDE_MAX); + lon = inRangeOrUnavailable(lon, LONGITUDE_MIN, LONGITUDE_MAX); + + if (!isNullIsland(lat, lon)) { + mLongitude = lon; + mLatitude = lat; + } else { + mLongitude = mLatitude = CellInfo.UNAVAILABLE; + } + updateGlobalCellId(); } - updateGlobalCellId(); } private CellIdentityCdma(@NonNull CellIdentityCdma cid) { @@ -300,14 +310,34 @@ public final class CellIdentityCdma extends CellIdentity { /** Construct from Parcel, type has already been processed */ private CellIdentityCdma(Parcel in) { super(TAG, CellInfo.TYPE_CDMA, in); - mNetworkId = in.readInt(); - mSystemId = in.readInt(); - mBasestationId = in.readInt(); - mLongitude = in.readInt(); - mLatitude = in.readInt(); - - updateGlobalCellId(); - if (DBG) log(toString()); + + if (Flags.cleanupCdma()) { + in.readInt(); + mNetworkId = CellInfo.UNAVAILABLE; + + in.readInt(); + mSystemId = CellInfo.UNAVAILABLE; + + in.readInt(); + mBasestationId = CellInfo.UNAVAILABLE; + + in.readInt(); + mLongitude = CellInfo.UNAVAILABLE; + + in.readInt(); + mLatitude = CellInfo.UNAVAILABLE; + + mGlobalCellId = null; + } else { + mNetworkId = in.readInt(); + mSystemId = in.readInt(); + mBasestationId = in.readInt(); + mLongitude = in.readInt(); + mLatitude = in.readInt(); + + updateGlobalCellId(); + if (DBG) log(toString()); + } } /** diff --git a/telephony/java/android/telephony/CellSignalStrengthCdma.java b/telephony/java/android/telephony/CellSignalStrengthCdma.java index 5298e67bdf80..12a7294c42de 100644 --- a/telephony/java/android/telephony/CellSignalStrengthCdma.java +++ b/telephony/java/android/telephony/CellSignalStrengthCdma.java @@ -21,6 +21,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; +import com.android.internal.telephony.flags.Flags; import com.android.telephony.Rlog; import java.util.Objects; @@ -68,13 +69,17 @@ public final class CellSignalStrengthCdma extends CellSignalStrength implements */ public CellSignalStrengthCdma(int cdmaDbm, int cdmaEcio, int evdoDbm, int evdoEcio, int evdoSnr) { - mCdmaDbm = inRangeOrUnavailable(cdmaDbm, -120, 0); - mCdmaEcio = inRangeOrUnavailable(cdmaEcio, -160, 0); - mEvdoDbm = inRangeOrUnavailable(evdoDbm, -120, 0); - mEvdoEcio = inRangeOrUnavailable(evdoEcio, -160, 0); - mEvdoSnr = inRangeOrUnavailable(evdoSnr, 0, 8); + if (Flags.cleanupCdma()) { + setDefaultValues(); + } else { + mCdmaDbm = inRangeOrUnavailable(cdmaDbm, -120, 0); + mCdmaEcio = inRangeOrUnavailable(cdmaEcio, -160, 0); + mEvdoDbm = inRangeOrUnavailable(evdoDbm, -120, 0); + mEvdoEcio = inRangeOrUnavailable(evdoEcio, -160, 0); + mEvdoSnr = inRangeOrUnavailable(evdoSnr, 0, 8); - updateLevel(null, null); + updateLevel(null, null); + } } /** @hide */ @@ -84,6 +89,10 @@ public final class CellSignalStrengthCdma extends CellSignalStrength implements /** @hide */ protected void copyFrom(CellSignalStrengthCdma s) { + if (Flags.cleanupCdma()) { + setDefaultValues(); + return; + } mCdmaDbm = s.mCdmaDbm; mCdmaEcio = s.mCdmaEcio; mEvdoDbm = s.mEvdoDbm; @@ -389,6 +398,7 @@ public final class CellSignalStrengthCdma extends CellSignalStrength implements /** @hide */ @Override public boolean isValid() { + if (Flags.cleanupCdma()) return false; return !this.equals(sInvalid); } @@ -446,7 +456,12 @@ public final class CellSignalStrengthCdma extends CellSignalStrength implements mEvdoEcio = in.readInt(); mEvdoSnr = in.readInt(); mLevel = in.readInt(); - if (DBG) log("CellSignalStrengthCdma(Parcel): " + toString()); + + if (Flags.cleanupCdma()) { + setDefaultValues(); + } else { + if (DBG) log("CellSignalStrengthCdma(Parcel): " + toString()); + } } /** Implement the Parcelable interface */ diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 3f8e0669ba7b..9694b90f06a5 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -6387,6 +6387,7 @@ public class TelephonyManager { * @deprecated use {@link #getImsPrivateUserIdentity()} */ @UnsupportedAppUsage + @Deprecated public String getIsimImpi() { try { IPhoneSubInfo info = getSubscriberInfoService(); @@ -6476,6 +6477,7 @@ public class TelephonyManager { * @deprecated use {@link #getImsPublicUserIdentities()} */ @UnsupportedAppUsage + @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String[] getIsimImpu() { @@ -8510,7 +8512,9 @@ public class TelephonyManager { if (telephony == null) { throw new IllegalStateException("telephony service is null."); } - telephony.rebootModem(getSlotIndex()); + if (!telephony.rebootModem(getSlotIndex())) { + throw new RuntimeException("Couldn't reboot modem (it may be not supported)"); + } } catch (RemoteException ex) { Rlog.e(TAG, "rebootRadio RemoteException", ex); throw ex.rethrowAsRuntimeException(); @@ -14051,6 +14055,7 @@ public class TelephonyManager { * @hide */ @SystemApi + @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CARRIERLOCK) public int setAllowedCarriers(int slotIndex, List<CarrierIdentifier> carriers) { diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java index eaeed2a0a9fa..b1a5b1bebc59 100644 --- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java +++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java @@ -17,6 +17,7 @@ package android.telephony.satellite.stub; import android.annotation.NonNull; +import android.hardware.radio.network.IRadioNetwork; import android.os.IBinder; import android.os.RemoteException; import android.telephony.IBooleanConsumer; @@ -586,7 +587,11 @@ public class SatelliteImplBase extends SatelliteService { * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * + * @deprecated Use + * {@link IRadioNetwork#setSatellitePlmn(int, int, String[], String[])}. */ + @Deprecated public void setSatellitePlmn(@NonNull int simLogicalSlotIndex, @NonNull List<String> carrierPlmnList, @NonNull List<String> allSatellitePlmnList, @NonNull IIntegerConsumer resultCallback) { @@ -608,7 +613,11 @@ public class SatelliteImplBase extends SatelliteService { * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * + * @deprecated Use + * {@link IRadioNetwork#setSatelliteEnabledForCarrier(int, int, boolean)}. */ + @Deprecated public void setSatelliteEnabledForCarrier(@NonNull int simLogicalSlotIndex, @NonNull boolean satelliteEnabled, @NonNull IIntegerConsumer callback) { // stub implementation @@ -629,7 +638,11 @@ public class SatelliteImplBase extends SatelliteService { * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * + * @deprecated Use + * {@link IRadioNetwork#isSatelliteEnabledForCarrier(int, int)}. */ + @Deprecated public void requestIsSatelliteEnabledForCarrier(@NonNull int simLogicalSlotIndex, @NonNull IIntegerConsumer resultCallback, @NonNull IBooleanConsumer callback) { // stub implementation |