diff options
521 files changed, 14007 insertions, 4024 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt index da5b88bbfbf4..19de1a5b0aa6 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -3349,12 +3349,17 @@ package android.window { ctor public TaskFragmentOrganizer(@NonNull java.util.concurrent.Executor); method @NonNull public java.util.concurrent.Executor getExecutor(); method @NonNull public android.window.TaskFragmentOrganizerToken getOrganizerToken(); - method public void onTaskFragmentAppeared(@NonNull android.window.TaskFragmentInfo); + method public void onActivityReparentedToTask(@NonNull android.window.WindowContainerTransaction, int, @NonNull android.content.Intent, @NonNull android.os.IBinder); + method @Deprecated public void onTaskFragmentAppeared(@NonNull android.window.TaskFragmentInfo); + method public void onTaskFragmentAppeared(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.TaskFragmentInfo); method @Deprecated public void onTaskFragmentError(@NonNull android.os.IBinder, @NonNull Throwable); - method public void onTaskFragmentError(@NonNull android.os.IBinder, @Nullable android.window.TaskFragmentInfo, int, @NonNull Throwable); - method public void onTaskFragmentInfoChanged(@NonNull android.window.TaskFragmentInfo); - method public void onTaskFragmentParentInfoChanged(@NonNull android.os.IBinder, @NonNull android.content.res.Configuration); - method public void onTaskFragmentVanished(@NonNull android.window.TaskFragmentInfo); + method public void onTaskFragmentError(@NonNull android.window.WindowContainerTransaction, @NonNull android.os.IBinder, @Nullable android.window.TaskFragmentInfo, int, @NonNull Throwable); + method @Deprecated public void onTaskFragmentInfoChanged(@NonNull android.window.TaskFragmentInfo); + method public void onTaskFragmentInfoChanged(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.TaskFragmentInfo); + method @Deprecated public void onTaskFragmentParentInfoChanged(@NonNull android.os.IBinder, @NonNull android.content.res.Configuration); + method public void onTaskFragmentParentInfoChanged(@NonNull android.window.WindowContainerTransaction, int, @NonNull android.content.res.Configuration); + method @Deprecated public void onTaskFragmentVanished(@NonNull android.window.TaskFragmentInfo); + method public void onTaskFragmentVanished(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.TaskFragmentInfo); method @CallSuper public void registerOrganizer(); method @CallSuper public void unregisterOrganizer(); } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 4ea0c32d3b36..7c1f9c80eec1 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -980,7 +980,8 @@ public class Activity extends ContextThemeWrapper boolean mEnterAnimationComplete; private boolean mIsInMultiWindowMode; - private boolean mIsInPictureInPictureMode; + /** @hide */ + boolean mIsInPictureInPictureMode; private boolean mShouldDockBigOverlays; diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index f384fa9e6a0b..a70a1a8d51de 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -4162,7 +4162,8 @@ public final class ActivityThread extends ClientTransactionHandler private void schedulePauseWithUserLeavingHint(ActivityClientRecord r) { final ClientTransaction transaction = ClientTransaction.obtain(this.mAppThread, r.token); transaction.setLifecycleStateRequest(PauseActivityItem.obtain(r.activity.isFinishing(), - /* userLeaving */ true, r.activity.mConfigChangeFlags, /* dontReport */ false)); + /* userLeaving */ true, r.activity.mConfigChangeFlags, /* dontReport */ false, + /* autoEnteringPip */ false)); executeTransaction(transaction); } @@ -4952,12 +4953,18 @@ public final class ActivityThread extends ClientTransactionHandler @Override public void handlePauseActivity(ActivityClientRecord r, boolean finished, boolean userLeaving, - int configChanges, PendingTransactionActions pendingActions, String reason) { + int configChanges, boolean autoEnteringPip, PendingTransactionActions pendingActions, + String reason) { if (userLeaving) { performUserLeavingActivity(r); } r.activity.mConfigChangeFlags |= configChanges; + if (autoEnteringPip) { + // Set mIsInPictureInPictureMode earlier in case of auto-enter-pip, see also + // {@link Activity#enterPictureInPictureMode(PictureInPictureParams)}. + r.activity.mIsInPictureInPictureMode = true; + } performPauseActivity(r, finished, reason, pendingActions); // Make sure any pending writes are now committed. diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index cb64173b7809..692d3f3eea9a 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1906,6 +1906,7 @@ public class AppOpsManager { OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, OP_SCHEDULE_EXACT_ALARM, OP_MANAGE_MEDIA, + OP_GET_USAGE_STATS, }; /** diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java index 65e6ab7a2137..a7566fdaae64 100644 --- a/core/java/android/app/ClientTransactionHandler.java +++ b/core/java/android/app/ClientTransactionHandler.java @@ -97,8 +97,8 @@ public abstract class ClientTransactionHandler { /** Pause the activity. */ public abstract void handlePauseActivity(@NonNull ActivityClientRecord r, boolean finished, - boolean userLeaving, int configChanges, PendingTransactionActions pendingActions, - String reason); + boolean userLeaving, int configChanges, boolean autoEnteringPip, + PendingTransactionActions pendingActions, String reason); /** * Resume the activity. diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index 02be051d973a..52732d3eba78 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -72,6 +72,7 @@ import android.view.IRemoteAnimationRunner; import android.view.IWindowFocusObserver; import android.view.RemoteAnimationDefinition; import android.view.RemoteAnimationAdapter; +import android.window.BackAnimationAdaptor; import android.window.IWindowOrganizerController; import android.window.BackNavigationInfo; import android.window.SplashScreenView; @@ -356,5 +357,5 @@ interface IActivityTaskManager { * @param focusObserver a remote callback to nofify shell when the focused window lost focus. */ android.window.BackNavigationInfo startBackNavigation(in boolean requestAnimation, - in IWindowFocusObserver focusObserver); + in IWindowFocusObserver focusObserver, in BackAnimationAdaptor adaptor); } diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index c9cc1a179102..05c9fca5ed68 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -124,8 +124,13 @@ public final class NotificationChannel implements Parcelable { /** * The maximum length for text fields in a NotificationChannel. Fields will be truncated at this * limit. + * @hide */ - private static final int MAX_TEXT_LENGTH = 1000; + public static final int MAX_TEXT_LENGTH = 1000; + /** + * @hide + */ + public static final int MAX_VIBRATION_LENGTH = 1000; private static final String TAG_CHANNEL = "channel"; private static final String ATT_NAME = "name"; @@ -283,17 +288,17 @@ public final class NotificationChannel implements Parcelable { */ protected NotificationChannel(Parcel in) { if (in.readByte() != 0) { - mId = in.readString(); + mId = getTrimmedString(in.readString()); } else { mId = null; } if (in.readByte() != 0) { - mName = in.readString(); + mName = getTrimmedString(in.readString()); } else { mName = null; } if (in.readByte() != 0) { - mDesc = in.readString(); + mDesc = getTrimmedString(in.readString()); } else { mDesc = null; } @@ -302,18 +307,22 @@ public final class NotificationChannel implements Parcelable { mLockscreenVisibility = in.readInt(); if (in.readByte() != 0) { mSound = Uri.CREATOR.createFromParcel(in); + mSound = Uri.parse(getTrimmedString(mSound.toString())); } else { mSound = null; } mLights = in.readByte() != 0; mVibration = in.createLongArray(); + if (mVibration != null && mVibration.length > MAX_VIBRATION_LENGTH) { + mVibration = Arrays.copyOf(mVibration, MAX_VIBRATION_LENGTH); + } mUserLockedFields = in.readInt(); mFgServiceShown = in.readByte() != 0; mVibrationEnabled = in.readByte() != 0; mShowBadge = in.readByte() != 0; mDeleted = in.readByte() != 0; if (in.readByte() != 0) { - mGroup = in.readString(); + mGroup = getTrimmedString(in.readString()); } else { mGroup = null; } @@ -322,8 +331,8 @@ public final class NotificationChannel implements Parcelable { mBlockableSystem = in.readBoolean(); mAllowBubbles = in.readInt(); mOriginalImportance = in.readInt(); - mParentId = in.readString(); - mConversationId = in.readString(); + mParentId = getTrimmedString(in.readString()); + mConversationId = getTrimmedString(in.readString()); mDemoted = in.readBoolean(); mImportantConvo = in.readBoolean(); mDeletedTime = in.readLong(); diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java index f97415ca20c8..2b245aae915f 100644 --- a/core/java/android/app/NotificationChannelGroup.java +++ b/core/java/android/app/NotificationChannelGroup.java @@ -43,8 +43,9 @@ public final class NotificationChannelGroup implements Parcelable { /** * The maximum length for text fields in a NotificationChannelGroup. Fields will be truncated at * this limit. + * @hide */ - private static final int MAX_TEXT_LENGTH = 1000; + public static final int MAX_TEXT_LENGTH = 1000; private static final String TAG_GROUP = "channelGroup"; private static final String ATT_NAME = "name"; @@ -90,13 +91,14 @@ public final class NotificationChannelGroup implements Parcelable { */ protected NotificationChannelGroup(Parcel in) { if (in.readByte() != 0) { - mId = in.readString(); + mId = getTrimmedString(in.readString()); } else { mId = null; } mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + mName = getTrimmedString(mName.toString()); if (in.readByte() != 0) { - mDescription = in.readString(); + mDescription = getTrimmedString(in.readString()); } else { mDescription = null; } @@ -120,7 +122,7 @@ public final class NotificationChannelGroup implements Parcelable { } else { dest.writeByte((byte) 0); } - TextUtils.writeToParcel(mName, dest, flags); + TextUtils.writeToParcel(mName.toString(), dest, flags); if (mDescription != null) { dest.writeByte((byte) 1); dest.writeString(mDescription); diff --git a/core/java/android/app/servertransaction/PauseActivityItem.java b/core/java/android/app/servertransaction/PauseActivityItem.java index 813e0f93a1f7..965e761ebfb3 100644 --- a/core/java/android/app/servertransaction/PauseActivityItem.java +++ b/core/java/android/app/servertransaction/PauseActivityItem.java @@ -39,13 +39,14 @@ public class PauseActivityItem extends ActivityLifecycleItem { private boolean mUserLeaving; private int mConfigChanges; private boolean mDontReport; + private boolean mAutoEnteringPip; @Override public void execute(ClientTransactionHandler client, ActivityClientRecord r, PendingTransactionActions pendingActions) { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityPause"); - client.handlePauseActivity(r, mFinished, mUserLeaving, mConfigChanges, pendingActions, - "PAUSE_ACTIVITY_ITEM"); + client.handlePauseActivity(r, mFinished, mUserLeaving, mConfigChanges, mAutoEnteringPip, + pendingActions, "PAUSE_ACTIVITY_ITEM"); Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } @@ -71,7 +72,7 @@ public class PauseActivityItem extends ActivityLifecycleItem { /** Obtain an instance initialized with provided params. */ public static PauseActivityItem obtain(boolean finished, boolean userLeaving, int configChanges, - boolean dontReport) { + boolean dontReport, boolean autoEnteringPip) { PauseActivityItem instance = ObjectPool.obtain(PauseActivityItem.class); if (instance == null) { instance = new PauseActivityItem(); @@ -80,6 +81,7 @@ public class PauseActivityItem extends ActivityLifecycleItem { instance.mUserLeaving = userLeaving; instance.mConfigChanges = configChanges; instance.mDontReport = dontReport; + instance.mAutoEnteringPip = autoEnteringPip; return instance; } @@ -94,6 +96,7 @@ public class PauseActivityItem extends ActivityLifecycleItem { instance.mUserLeaving = false; instance.mConfigChanges = 0; instance.mDontReport = true; + instance.mAutoEnteringPip = false; return instance; } @@ -105,6 +108,7 @@ public class PauseActivityItem extends ActivityLifecycleItem { mUserLeaving = false; mConfigChanges = 0; mDontReport = false; + mAutoEnteringPip = false; ObjectPool.recycle(this); } @@ -117,6 +121,7 @@ public class PauseActivityItem extends ActivityLifecycleItem { dest.writeBoolean(mUserLeaving); dest.writeInt(mConfigChanges); dest.writeBoolean(mDontReport); + dest.writeBoolean(mAutoEnteringPip); } /** Read from Parcel. */ @@ -125,6 +130,7 @@ public class PauseActivityItem extends ActivityLifecycleItem { mUserLeaving = in.readBoolean(); mConfigChanges = in.readInt(); mDontReport = in.readBoolean(); + mAutoEnteringPip = in.readBoolean(); } public static final @NonNull Creator<PauseActivityItem> CREATOR = @@ -148,7 +154,8 @@ public class PauseActivityItem extends ActivityLifecycleItem { } final PauseActivityItem other = (PauseActivityItem) o; return mFinished == other.mFinished && mUserLeaving == other.mUserLeaving - && mConfigChanges == other.mConfigChanges && mDontReport == other.mDontReport; + && mConfigChanges == other.mConfigChanges && mDontReport == other.mDontReport + && mAutoEnteringPip == other.mAutoEnteringPip; } @Override @@ -158,12 +165,14 @@ public class PauseActivityItem extends ActivityLifecycleItem { result = 31 * result + (mUserLeaving ? 1 : 0); result = 31 * result + mConfigChanges; result = 31 * result + (mDontReport ? 1 : 0); + result = 31 * result + (mAutoEnteringPip ? 1 : 0); return result; } @Override public String toString() { return "PauseActivityItem{finished=" + mFinished + ",userLeaving=" + mUserLeaving - + ",configChanges=" + mConfigChanges + ",dontReport=" + mDontReport + "}"; + + ",configChanges=" + mConfigChanges + ",dontReport=" + mDontReport + + ",autoEnteringPip=" + mAutoEnteringPip + "}"; } } diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java index 25ff8a78a0c8..de1d38a64163 100644 --- a/core/java/android/app/servertransaction/TransactionExecutor.java +++ b/core/java/android/app/servertransaction/TransactionExecutor.java @@ -227,7 +227,8 @@ public class TransactionExecutor { break; case ON_PAUSE: mTransactionHandler.handlePauseActivity(r, false /* finished */, - false /* userLeaving */, 0 /* configChanges */, mPendingActions, + false /* userLeaving */, 0 /* configChanges */, + false /* autoEnteringPip */, mPendingActions, "LIFECYCLER_PAUSE_ACTIVITY"); break; case ON_STOP: diff --git a/core/java/android/attention/AttentionManagerInternal.java b/core/java/android/attention/AttentionManagerInternal.java index 47bec618cfd9..24fe0dbe5760 100644 --- a/core/java/android/attention/AttentionManagerInternal.java +++ b/core/java/android/attention/AttentionManagerInternal.java @@ -83,11 +83,11 @@ public abstract class AttentionManagerInternal { } /** Internal interface for proximity callback. */ - public abstract static class ProximityUpdateCallbackInternal { + public interface ProximityUpdateCallbackInternal { /** * @param distance the estimated distance of the user (in meter) * The distance will be PROXIMITY_UNKNOWN if the proximity sensing was inconclusive. */ - public abstract void onProximityUpdate(double distance); + void onProximityUpdate(double distance); } } diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java index a9d665c8b8a5..621eab558337 100644 --- a/core/java/android/hardware/soundtrigger/SoundTrigger.java +++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java @@ -1963,7 +1963,6 @@ public class SoundTrigger { } private static Object mServiceLock = new Object(); - private static ISoundTriggerMiddlewareService mService; /** * Translate an exception thrown from interaction with the underlying service to an error code. @@ -2217,20 +2216,12 @@ public class SoundTrigger { binder = ServiceManager.getServiceOrThrow( Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE); - binder.linkToDeath(() -> { - synchronized (mServiceLock) { - mService = null; - } - }, 0); - mService = ISoundTriggerMiddlewareService.Stub.asInterface(binder); - break; + return ISoundTriggerMiddlewareService.Stub.asInterface(binder); } catch (Exception e) { Log.e(TAG, "Failed to bind to soundtrigger service", e); } } - return mService; } - } /** diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java b/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java index be573721936b..d6f191e31182 100644 --- a/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java +++ b/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java @@ -37,6 +37,7 @@ import android.net.ipsec.ike.IkeSessionParams.IkeAuthPskConfig; import android.net.ipsec.ike.IkeSessionParams.IkeConfigRequest; import android.os.PersistableBundle; import android.util.ArraySet; +import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.server.vcn.util.PersistableBundleUtils; @@ -58,6 +59,8 @@ import java.util.Set; */ @VisibleForTesting(visibility = Visibility.PRIVATE) public final class IkeSessionParamsUtils { + private static final String TAG = IkeSessionParamsUtils.class.getSimpleName(); + private static final String SERVER_HOST_NAME_KEY = "SERVER_HOST_NAME_KEY"; private static final String SA_PROPOSALS_KEY = "SA_PROPOSALS_KEY"; private static final String LOCAL_ID_KEY = "LOCAL_ID_KEY"; @@ -72,6 +75,13 @@ public final class IkeSessionParamsUtils { private static final String NATT_KEEPALIVE_DELAY_SEC_KEY = "NATT_KEEPALIVE_DELAY_SEC_KEY"; private static final String IKE_OPTIONS_KEY = "IKE_OPTIONS_KEY"; + // TODO: b/243181760 Use the IKE API when they are exposed + @VisibleForTesting(visibility = Visibility.PRIVATE) + public static final int IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION = 6; + + @VisibleForTesting(visibility = Visibility.PRIVATE) + public static final int IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES = 7; + private static final Set<Integer> IKE_OPTIONS = new ArraySet<>(); static { @@ -80,6 +90,26 @@ public final class IkeSessionParamsUtils { IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_MOBIKE); IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_FORCE_PORT_4500); IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_INITIAL_CONTACT); + IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_REKEY_MOBILITY); + IKE_OPTIONS.add(IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION); + IKE_OPTIONS.add(IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES); + } + + /** + * Check if an IKE option is supported in the IPsec module installed on the device + * + * <p>This method ensures caller to safely access options that are added between dessert + * releases. + */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + public static boolean isIkeOptionValid(int option) { + try { + new IkeSessionParams.Builder().addIkeOption(option); + return true; + } catch (IllegalArgumentException e) { + Log.d(TAG, "Option not supported; discarding: " + option); + return false; + } } /** Serializes an IkeSessionParams to a PersistableBundle. */ @@ -130,7 +160,7 @@ public final class IkeSessionParamsUtils { // IKE_OPTION is defined in IKE module and added in the IkeSessionParams final List<Integer> enabledIkeOptions = new ArrayList<>(); for (int option : IKE_OPTIONS) { - if (params.hasIkeOption(option)) { + if (isIkeOptionValid(option) && params.hasIkeOption(option)) { enabledIkeOptions.add(option); } } @@ -205,12 +235,16 @@ public final class IkeSessionParamsUtils { // Clear IKE Options that are by default enabled for (int option : IKE_OPTIONS) { - builder.removeIkeOption(option); + if (isIkeOptionValid(option)) { + builder.removeIkeOption(option); + } } final int[] optionArray = in.getIntArray(IKE_OPTIONS_KEY); for (int option : optionArray) { - builder.addIkeOption(option); + if (isIkeOptionValid(option)) { + builder.addIkeOption(option); + } } return builder.build(); diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 06c35b5bec5d..801d34d9d884 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -956,16 +956,7 @@ public abstract class BatteryStats implements Parcelable { public static final int NUM_WIFI_BATCHED_SCAN_BINS = 5; - /** - * Note that these must match the constants in android.os.PowerManager. - * Also, if the user activity types change, the BatteryStatsImpl.VERSION must - * also be bumped. - */ - static final String[] USER_ACTIVITY_TYPES = { - "other", "button", "touch", "accessibility", "attention" - }; - - public static final int NUM_USER_ACTIVITY_TYPES = USER_ACTIVITY_TYPES.length; + public static final int NUM_USER_ACTIVITY_TYPES = PowerManager.USER_ACTIVITY_EVENT_MAX + 1; public abstract void noteUserActivityLocked(int type); public abstract boolean hasUserActivity(); @@ -6177,7 +6168,7 @@ public abstract class BatteryStats implements Parcelable { } sb.append(val); sb.append(" "); - sb.append(Uid.USER_ACTIVITY_TYPES[i]); + sb.append(PowerManager.userActivityEventToString(i)); } } if (hasData) { diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index e5de3e157c88..e1d15defad38 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -74,6 +74,7 @@ interface IUserManager { String getUserAccount(int userId); void setUserAccount(int userId, String accountName); long getUserCreationTime(int userId); + boolean isUserSwitcherEnabled(int mUserId); boolean isRestricted(int userId); boolean canHaveRestrictedProfile(int userId); int getUserSerialNumber(int userId); diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 13ca2c34b27e..8a203e07ae6d 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -345,6 +345,44 @@ public final class PowerManager { public static final int USER_ACTIVITY_EVENT_DEVICE_STATE = 6; /** + * @hide + */ + public static final int USER_ACTIVITY_EVENT_MAX = USER_ACTIVITY_EVENT_DEVICE_STATE; + + /** + * @hide + */ + @IntDef(prefix = { "USER_ACTIVITY_EVENT_" }, value = { + USER_ACTIVITY_EVENT_OTHER, + USER_ACTIVITY_EVENT_BUTTON, + USER_ACTIVITY_EVENT_TOUCH, + USER_ACTIVITY_EVENT_ACCESSIBILITY, + USER_ACTIVITY_EVENT_ATTENTION, + USER_ACTIVITY_EVENT_FACE_DOWN, + USER_ACTIVITY_EVENT_DEVICE_STATE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface UserActivityEvent{} + + /** + * + * Convert the user activity event to a string for debugging purposes. + * @hide + */ + public static String userActivityEventToString(@UserActivityEvent int userActivityEvent) { + switch (userActivityEvent) { + case USER_ACTIVITY_EVENT_OTHER: return "other"; + case USER_ACTIVITY_EVENT_BUTTON: return "button"; + case USER_ACTIVITY_EVENT_TOUCH: return "touch"; + case USER_ACTIVITY_EVENT_ACCESSIBILITY: return "accessibility"; + case USER_ACTIVITY_EVENT_ATTENTION: return "attention"; + case USER_ACTIVITY_EVENT_FACE_DOWN: return "faceDown"; + case USER_ACTIVITY_EVENT_DEVICE_STATE: return "deviceState"; + default: return Integer.toString(userActivityEvent); + } + } + + /** * User activity flag: If already dimmed, extend the dim timeout * but do not brighten. This flag is useful for keeping the screen on * a little longer without causing a visible change such as when diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index d6566048f2c4..5487a1203833 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -1971,7 +1971,8 @@ public class UserManager { /** @hide */ public UserManager(Context context, IUserManager service) { mService = service; - mContext = context.getApplicationContext(); + Context appContext = context.getApplicationContext(); + mContext = (appContext == null ? context : appContext); mUserId = context.getUserId(); } @@ -5131,23 +5132,13 @@ public class UserManager { }) @UserHandleAware public boolean isUserSwitcherEnabled(boolean showEvenIfNotActionable) { - if (!supportsMultipleUsers()) { - return false; - } - if (hasUserRestrictionForUser(DISALLOW_USER_SWITCH, mUserId)) { - return false; - } - // If Demo Mode is on, don't show user switcher - if (isDeviceInDemoMode(mContext)) { - return false; - } - // Check the Settings.Global.USER_SWITCHER_ENABLED that the user can toggle on/off. - final boolean userSwitcherSettingOn = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.USER_SWITCHER_ENABLED, - Resources.getSystem().getBoolean(R.bool.config_showUserSwitcherByDefault) ? 1 : 0) - != 0; - if (!userSwitcherSettingOn) { - return false; + + try { + if (!mService.isUserSwitcherEnabled(mUserId)) { + return false; + } + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); } // The feature is enabled. But is it worth showing? diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index a76524a10ddb..1fcdd608c5a2 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9142,14 +9142,12 @@ public final class Settings { public static final String SCREENSAVER_DEFAULT_COMPONENT = "screensaver_default_component"; /** - * The complications that are enabled to be shown over the screensaver by the user. Holds - * a comma separated list of - * {@link com.android.settingslib.dream.DreamBackend.ComplicationType}. + * Whether complications are enabled to be shown over the screensaver by the user. * * @hide */ - public static final String SCREENSAVER_ENABLED_COMPLICATIONS = - "screensaver_enabled_complications"; + public static final String SCREENSAVER_COMPLICATIONS_ENABLED = + "screensaver_complications_enabled"; /** @@ -10591,6 +10589,13 @@ public final class Settings { public static final String MEDIA_CONTROLS_RESUME = "qs_media_resumption"; /** + * Whether to enable media controls on lock screen. + * When enabled, media controls will appear on lock screen. + * @hide + */ + public static final String MEDIA_CONTROLS_LOCK_SCREEN = "media_controls_lock_screen"; + + /** * Controls whether contextual suggestions can be shown in the media controls. * @hide */ diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java index 91042bfa3402..a9c2ad1ce915 100644 --- a/core/java/android/service/notification/NotificationAssistantService.java +++ b/core/java/android/service/notification/NotificationAssistantService.java @@ -91,6 +91,22 @@ public abstract class NotificationAssistantService extends NotificationListenerS = "android.service.notification.NotificationAssistantService"; /** + * Activity Action: Show notification assistant detail setting page in NAS app. + * <p> + * In some cases, a matching Activity may not exist, so ensure you + * safeguard against this. + * <p> + * Input: Nothing. + * <p> + * Output: Nothing. + * @hide + */ + @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_NOTIFICATION_ASSISTANT_DETAIL_SETTINGS = + "android.service.notification.action.NOTIFICATION_ASSISTANT_DETAIL_SETTINGS"; + + + /** * Data type: int, the feedback rating score provided by user. The score can be any integer * value depends on the experimental and feedback UX design. */ diff --git a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java index 95bcda5f7c55..9292e9608261 100644 --- a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java +++ b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java @@ -1317,7 +1317,6 @@ final class RemoteSelectionToolbar { contentContainer.setLayoutParams(new ViewGroup.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); contentContainer.setTag(FloatingToolbar.FLOATING_TOOLBAR_TAG); - contentContainer.setContentDescription(FloatingToolbar.FLOATING_TOOLBAR_TAG); contentContainer.setClipToOutline(true); return contentContainer; } diff --git a/core/java/android/service/voice/HotwordDetectedResult.java b/core/java/android/service/voice/HotwordDetectedResult.java index 72341453a1f4..ab71459ed51e 100644 --- a/core/java/android/service/voice/HotwordDetectedResult.java +++ b/core/java/android/service/voice/HotwordDetectedResult.java @@ -95,6 +95,16 @@ public final class HotwordDetectedResult implements Parcelable { /** Limits the max value for the triggered audio channel. */ private static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE = 63; + /** + * The bundle key for proximity value + * + * TODO(b/238896013): Move the proximity logic out of bundle to proper API. + * + * @hide + */ + public static final String EXTRA_PROXIMITY_METERS = + "android.service.voice.extra.PROXIMITY_METERS"; + /** Confidence level in the trigger outcome. */ @HotwordConfidenceLevelValue private final int mConfidenceLevel; @@ -197,6 +207,14 @@ public final class HotwordDetectedResult implements Parcelable { * <p>The use of this method is discouraged, and support for it will be removed in future * versions of Android. * + * <p>After the trigger happens, a special case of proximity-related extra, with the key of + * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double), + * will be stored to enable proximity logic. The proximity meters is provided by the system, + * on devices that support detecting proximity of nearby users, to help disambiguate which + * nearby device should respond. When the proximity is unknown, the proximity value will not + * be stored. This mapping will be excluded from the max bundle size calculation because this + * mapping is included after the result is returned from the hotword detector service. + * * <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents * that can be used to communicate with other processes. */ @@ -315,8 +333,23 @@ public final class HotwordDetectedResult implements Parcelable { "audioChannel"); } if (!mExtras.isEmpty()) { - Preconditions.checkArgumentInRange(getParcelableSize(mExtras), 0, getMaxBundleSize(), - "extras"); + // Remove the proximity key from the bundle before checking the bundle size. The + // proximity value is added after the privileged module and can avoid the + // maxBundleSize limitation. + if (mExtras.containsKey(EXTRA_PROXIMITY_METERS)) { + double proximityMeters = mExtras.getDouble(EXTRA_PROXIMITY_METERS); + mExtras.remove(EXTRA_PROXIMITY_METERS); + // Skip checking parcelable size if the new bundle size is 0. Newly empty bundle + // has parcelable size of 4, but the default bundle has parcelable size of 0. + if (mExtras.size() > 0) { + Preconditions.checkArgumentInRange(getParcelableSize(mExtras), 0, + getMaxBundleSize(), "extras"); + } + mExtras.putDouble(EXTRA_PROXIMITY_METERS, proximityMeters); + } else { + Preconditions.checkArgumentInRange(getParcelableSize(mExtras), 0, + getMaxBundleSize(), "extras"); + } } } @@ -513,6 +546,14 @@ public final class HotwordDetectedResult implements Parcelable { * <p>The use of this method is discouraged, and support for it will be removed in future * versions of Android. * + * <p>After the trigger happens, a special case of proximity-related extra, with the key of + * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double), + * will be stored to enable proximity logic. The proximity meters is provided by the system, + * on devices that support detecting proximity of nearby users, to help disambiguate which + * nearby device should respond. When the proximity is unknown, the proximity value will not + * be stored. This mapping will be excluded from the max bundle size calculation because this + * mapping is included after the result is returned from the hotword detector service. + * * <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents * that can be used to communicate with other processes. */ @@ -813,6 +854,14 @@ public final class HotwordDetectedResult implements Parcelable { * <p>The use of this method is discouraged, and support for it will be removed in future * versions of Android. * + * <p>After the trigger happens, a special case of proximity-related extra, with the key of + * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double), + * will be stored to enable proximity logic. The proximity meters is provided by the system, + * on devices that support detecting proximity of nearby users, to help disambiguate which + * nearby device should respond. When the proximity is unknown, the proximity value will not + * be stored. This mapping will be excluded from the max bundle size calculation because this + * mapping is included after the result is returned from the hotword detector service. + * * <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents * that can be used to communicate with other processes. */ @@ -882,10 +931,10 @@ public final class HotwordDetectedResult implements Parcelable { } @DataClass.Generated( - time = 1625541522353L, + time = 1658357814396L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/service/voice/HotwordDetectedResult.java", - inputSignatures = "public static final int CONFIDENCE_LEVEL_NONE\npublic static final int CONFIDENCE_LEVEL_LOW\npublic static final int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final int CONFIDENCE_LEVEL_HIGH\npublic static final int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final int HOTWORD_OFFSET_UNSET\npublic static final int AUDIO_CHANNEL_UNSET\nprivate static final int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate int mHotwordOffsetMillis\nprivate int mHotwordDurationMillis\nprivate int mAudioChannel\nprivate boolean mHotwordDetectionPersonalized\nprivate final int mScore\nprivate final int mPersonalizedScore\nprivate final int mHotwordPhraseId\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static int sMaxBundleSize\nprivate static int defaultConfidenceLevel()\nprivate static int defaultScore()\nprivate static int defaultPersonalizedScore()\npublic static int getMaxScore()\nprivate static int defaultHotwordPhraseId()\npublic static int getMaxHotwordPhraseId()\nprivate static android.os.PersistableBundle defaultExtras()\npublic static int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static int getParcelableSize(android.os.Parcelable)\npublic static int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static int bitCount(long)\nprivate void onConstructed()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)") + inputSignatures = "public static final int CONFIDENCE_LEVEL_NONE\npublic static final int CONFIDENCE_LEVEL_LOW\npublic static final int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final int CONFIDENCE_LEVEL_HIGH\npublic static final int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final int HOTWORD_OFFSET_UNSET\npublic static final int AUDIO_CHANNEL_UNSET\nprivate static final int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE\npublic static final java.lang.String EXTRA_PROXIMITY_METERS\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate int mHotwordOffsetMillis\nprivate int mHotwordDurationMillis\nprivate int mAudioChannel\nprivate boolean mHotwordDetectionPersonalized\nprivate final int mScore\nprivate final int mPersonalizedScore\nprivate final int mHotwordPhraseId\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static int sMaxBundleSize\nprivate static int defaultConfidenceLevel()\nprivate static int defaultScore()\nprivate static int defaultPersonalizedScore()\npublic static int getMaxScore()\nprivate static int defaultHotwordPhraseId()\npublic static int getMaxHotwordPhraseId()\nprivate static android.os.PersistableBundle defaultExtras()\npublic static int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static int getParcelableSize(android.os.Parcelable)\npublic static int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static int bitCount(long)\nprivate void onConstructed()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 0ef23bbfc8c9..bb26c46142d2 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -413,11 +413,6 @@ interface IWindowManager boolean hasNavigationBar(int displayId); /** - * Get the position of the nav bar - */ - int getNavBarPosition(int displayId); - - /** * Lock the device immediately with the specified options (can be null). */ @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index a2cb1d5c6bd2..0e3bcd1b6842 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -314,6 +314,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation (int) (startValue.right + fraction * (endValue.right - startValue.right)), (int) (startValue.bottom + fraction * (endValue.bottom - startValue.bottom))); + /** Logging listener. */ + private WindowInsetsAnimationControlListener mLoggingListener; + /** * The default implementation of listener, to be used by InsetsController and InsetsPolicy to * animate insets. @@ -330,6 +333,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private final long mDurationMs; private final boolean mDisable; private final int mFloatingImeBottomInset; + private final WindowInsetsAnimationControlListener mLoggingListener; private final ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal = new ThreadLocal<AnimationHandler>() { @@ -343,7 +347,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation public InternalAnimationControlListener(boolean show, boolean hasAnimationCallbacks, @InsetsType int requestedTypes, @Behavior int behavior, boolean disable, - int floatingImeBottomInset) { + int floatingImeBottomInset, WindowInsetsAnimationControlListener loggingListener) { mShow = show; mHasAnimationCallbacks = hasAnimationCallbacks; mRequestedTypes = requestedTypes; @@ -351,12 +355,16 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation mDurationMs = calculateDurationMs(); mDisable = disable; mFloatingImeBottomInset = floatingImeBottomInset; + mLoggingListener = loggingListener; } @Override public void onReady(WindowInsetsAnimationController controller, int types) { mController = controller; if (DEBUG) Log.d(TAG, "default animation onReady types: " + types); + if (mLoggingListener != null) { + mLoggingListener.onReady(controller, types); + } if (mDisable) { onAnimationFinish(); @@ -410,6 +418,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation public void onFinished(WindowInsetsAnimationController controller) { if (DEBUG) Log.d(TAG, "InternalAnimationControlListener onFinished types:" + Type.toString(mRequestedTypes)); + if (mLoggingListener != null) { + mLoggingListener.onFinished(controller); + } } @Override @@ -420,6 +431,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } if (DEBUG) Log.d(TAG, "InternalAnimationControlListener onCancelled types:" + mRequestedTypes); + if (mLoggingListener != null) { + mLoggingListener.onCancelled(controller); + } } protected Interpolator getInsetsInterpolator() { @@ -1147,6 +1161,13 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation updateRequestedVisibilities(); } + // TODO(b/242962223): Make this setter restrictive. + @Override + public void setSystemDrivenInsetsAnimationLoggingListener( + @Nullable WindowInsetsAnimationControlListener listener) { + mLoggingListener = listener; + } + /** * @return Pair of (types ready to animate, IME ready to animate). */ @@ -1460,7 +1481,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation boolean hasAnimationCallbacks = mHost.hasAnimationCallbacks(); final InternalAnimationControlListener listener = new InternalAnimationControlListener( show, hasAnimationCallbacks, types, mHost.getSystemBarsBehavior(), - skipAnim || mAnimationsDisabled, mHost.dipToPx(FLOATING_IME_BOTTOM_INSET_DP)); + skipAnim || mAnimationsDisabled, mHost.dipToPx(FLOATING_IME_BOTTOM_INSET_DP), + mLoggingListener); // We are about to playing the default animation (show/hide). Passing a null frame indicates // the controlled types should be animated regardless of the frame. diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index d63c25a09382..5236fe772a7b 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -176,7 +176,9 @@ public class InsetsSourceConsumer { // If we have a new leash, make sure visibility is up-to-date, even though we // didn't want to run an animation above. - applyRequestedVisibilityToControl(); + if (mController.getAnimationType(control.getType()) == ANIMATION_TYPE_NONE) { + applyRequestedVisibilityToControl(); + } // Remove the surface that owned by last control when it lost. if (!requestedVisible && lastControl == null) { diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index c198098cb6ff..c102ad3a3ace 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -349,6 +349,20 @@ public class InsetsState implements Parcelable { return insets; } + // TODO: Remove this once the task bar is treated as navigation bar. + public Insets calculateInsetsWithInternalTypes(Rect frame, @InternalInsetsType int[] types, + boolean ignoreVisibility) { + Insets insets = Insets.NONE; + for (int i = types.length - 1; i >= 0; i--) { + InsetsSource source = mSources[types[i]]; + if (source == null) { + continue; + } + insets = Insets.max(source.calculateInsets(frame, ignoreVisibility), insets); + } + return insets; + } + public Insets calculateInsets(Rect frame, @InsetsType int types, InsetsVisibilities overrideVisibilities) { Insets insets = Insets.NONE; diff --git a/core/java/android/view/PendingInsetsController.java b/core/java/android/view/PendingInsetsController.java index c61baf6fb40c..3fe9110283a6 100644 --- a/core/java/android/view/PendingInsetsController.java +++ b/core/java/android/view/PendingInsetsController.java @@ -44,6 +44,7 @@ public class PendingInsetsController implements WindowInsetsController { private ArrayList<OnControllableInsetsChangedListener> mControllableInsetsChangedListeners = new ArrayList<>(); private int mCaptionInsetsHeight = 0; + private WindowInsetsAnimationControlListener mLoggingListener; @Override public void show(int types) { @@ -176,6 +177,9 @@ public class PendingInsetsController implements WindowInsetsController { controller.addOnControllableInsetsChangedListener( mControllableInsetsChangedListeners.get(i)); } + if (mLoggingListener != null) { + controller.setSystemDrivenInsetsAnimationLoggingListener(mLoggingListener); + } // Reset all state so it doesn't get applied twice just in case mRequests.clear(); @@ -184,7 +188,7 @@ public class PendingInsetsController implements WindowInsetsController { mAppearance = 0; mAppearanceMask = 0; mAnimationsDisabled = false; - + mLoggingListener = null; // After replaying, we forward everything directly to the replayed instance. mReplayedInsetsController = controller; } @@ -198,6 +202,16 @@ public class PendingInsetsController implements WindowInsetsController { } @Override + public void setSystemDrivenInsetsAnimationLoggingListener( + @Nullable WindowInsetsAnimationControlListener listener) { + if (mReplayedInsetsController != null) { + mReplayedInsetsController.setSystemDrivenInsetsAnimationLoggingListener(listener); + } else { + mLoggingListener = listener; + } + } + + @Override public void controlWindowInsetsAnimation(@InsetsType int types, long durationMillis, @Nullable Interpolator interpolator, CancellationSignal cancellationSignal, diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java index 5721fa6dd11a..3acb0534e388 100644 --- a/core/java/android/view/SurfaceControlViewHost.java +++ b/core/java/android/view/SurfaceControlViewHost.java @@ -28,8 +28,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; import android.util.Log; -import android.window.WindowTokenClient; import android.view.accessibility.IAccessibilityEmbeddedConnection; +import android.window.WindowTokenClient; import java.util.Objects; @@ -271,14 +271,8 @@ public class SurfaceControlViewHost { /** @hide */ public SurfaceControlViewHost(@NonNull Context c, @NonNull Display d, @NonNull WindowlessWindowManager wwm) { - this(c, d, wwm, false /* useSfChoreographer */); - } - - /** @hide */ - public SurfaceControlViewHost(@NonNull Context c, @NonNull Display d, - @NonNull WindowlessWindowManager wwm, boolean useSfChoreographer) { mWm = wwm; - mViewRoot = new ViewRootImpl(c, d, mWm, new WindowlessWindowLayout(), useSfChoreographer); + mViewRoot = new ViewRootImpl(c, d, mWm, new WindowlessWindowLayout()); addConfigCallback(c, d); WindowManagerGlobal.getInstance().addWindowlessRoot(mViewRoot); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index af57f3ba215f..ec6b4acd6aff 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -905,17 +905,11 @@ public final class ViewRootImpl implements ViewParent, private String mTag = TAG; public ViewRootImpl(Context context, Display display) { - this(context, display, WindowManagerGlobal.getWindowSession(), new WindowLayout(), - false /* useSfChoreographer */); + this(context, display, WindowManagerGlobal.getWindowSession(), new WindowLayout()); } public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session, WindowLayout windowLayout) { - this(context, display, session, windowLayout, false /* useSfChoreographer */); - } - - public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session, - WindowLayout windowLayout, boolean useSfChoreographer) { mContext = context; mWindowSession = session; mWindowLayout = windowLayout; @@ -947,8 +941,7 @@ public final class ViewRootImpl implements ViewParent, mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi; mFallbackEventHandler = new PhoneFallbackEventHandler(context); // TODO(b/222696368): remove getSfInstance usage and use vsyncId for transactions - mChoreographer = useSfChoreographer - ? Choreographer.getSfInstance() : Choreographer.getInstance(); + mChoreographer = Choreographer.getInstance(); mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE); mInsetsController = new InsetsController(new ViewRootInsetsControllerHost(this)); mHandwritingInitiator = new HandwritingInitiator(mViewConfiguration, diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java index 227b9f402bba..63f9e13214ff 100644 --- a/core/java/android/view/WindowInsetsController.java +++ b/core/java/android/view/WindowInsetsController.java @@ -201,6 +201,21 @@ public interface WindowInsetsController { @NonNull WindowInsetsAnimationControlListener listener); /** + * Lets the application add non-controllable listener object that can be called back + * when animation is invoked by the system by host calling methods such as {@link #show} or + * {@link #hide}. + * + * The listener is supposed to be used for logging only, using the control or + * relying on the timing of the callback in any other way is not supported. + * + * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when + * the animation is driven by the system and not the host + * @hide + */ + void setSystemDrivenInsetsAnimationLoggingListener( + @Nullable WindowInsetsAnimationControlListener listener); + + /** * Controls the appearance of system bars. * <p> * For example, the following statement adds {@link #APPEARANCE_LIGHT_STATUS_BARS}: diff --git a/core/java/android/view/WindowLayout.java b/core/java/android/view/WindowLayout.java index 57a0330e3c18..5ed9d2f90a72 100644 --- a/core/java/android/view/WindowLayout.java +++ b/core/java/android/view/WindowLayout.java @@ -118,11 +118,11 @@ public class WindowLayout { } if (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES) { if (displayFrame.width() < displayFrame.height()) { - displayCutoutSafeExceptMaybeBars.top = Integer.MIN_VALUE; - displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE; + displayCutoutSafeExceptMaybeBars.top = MIN_Y; + displayCutoutSafeExceptMaybeBars.bottom = MAX_Y; } else { - displayCutoutSafeExceptMaybeBars.left = Integer.MIN_VALUE; - displayCutoutSafeExceptMaybeBars.right = Integer.MAX_VALUE; + displayCutoutSafeExceptMaybeBars.left = MIN_X; + displayCutoutSafeExceptMaybeBars.right = MAX_X; } } final boolean layoutInsetDecor = (attrs.flags & FLAG_LAYOUT_INSET_DECOR) != 0; @@ -132,23 +132,23 @@ public class WindowLayout { final Insets systemBarsInsets = state.calculateInsets( displayFrame, WindowInsets.Type.systemBars(), requestedVisibilities); if (systemBarsInsets.left > 0) { - displayCutoutSafeExceptMaybeBars.left = Integer.MIN_VALUE; + displayCutoutSafeExceptMaybeBars.left = MIN_X; } if (systemBarsInsets.top > 0) { - displayCutoutSafeExceptMaybeBars.top = Integer.MIN_VALUE; + displayCutoutSafeExceptMaybeBars.top = MIN_Y; } if (systemBarsInsets.right > 0) { - displayCutoutSafeExceptMaybeBars.right = Integer.MAX_VALUE; + displayCutoutSafeExceptMaybeBars.right = MAX_X; } if (systemBarsInsets.bottom > 0) { - displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE; + displayCutoutSafeExceptMaybeBars.bottom = MAX_Y; } } if (type == TYPE_INPUT_METHOD) { final InsetsSource navSource = state.peekSource(ITYPE_NAVIGATION_BAR); if (navSource != null && navSource.calculateInsets(displayFrame, true).bottom > 0) { // The IME can always extend under the bottom cutout if the navbar is there. - displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE; + displayCutoutSafeExceptMaybeBars.bottom = MAX_Y; } } final boolean attachedInParent = attachedWindowFrame != null && !layoutInScreen; diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 67352c02ce18..2195b83933a5 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -1023,6 +1023,14 @@ public interface WindowManager extends ViewManager { } } + /** + * Ensure scales are between 0 and 20. + * @hide + */ + static float fixScale(float scale) { + return Math.max(Math.min(scale, 20), 0); + } + public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable { /** * X position for this window. With the default gravity it is ignored. diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index cae48683810e..b5a742bfb2eb 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -89,6 +89,7 @@ import android.view.InputEventSender; import android.view.KeyEvent; import android.view.View; import android.view.ViewRootImpl; +import android.view.WindowInsets; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import android.view.autofill.AutofillManager; import android.window.ImeOnBackInvokedDispatcher; @@ -2122,8 +2123,9 @@ public final class InputMethodManager { null /* icProto */); synchronized (mH) { final View view = getServedViewLocked(); - if (mImeInsetsConsumer != null && view != null) { - if (mImeInsetsConsumer.isRequestedVisible()) { + if (view != null) { + final WindowInsets rootInsets = view.getRootWindowInsets(); + if (rootInsets != null && rootInsets.isVisible(WindowInsets.Type.ime())) { hideSoftInputFromWindow(view.getWindowToken(), hideFlags, null, SoftInputShowHideReason.HIDE_TOGGLE_SOFT_INPUT); } else { diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java index fa84407c5c4d..7314ad83bd0c 100644 --- a/core/java/android/widget/LinearLayout.java +++ b/core/java/android/widget/LinearLayout.java @@ -732,6 +732,10 @@ public class LinearLayout extends ViewGroup { * @hide Pending API consideration. Currently only used internally by the system. */ protected boolean hasDividerBeforeChildAt(int childIndex) { + if (mShowDividers == SHOW_DIVIDER_NONE) { + // Short-circuit to save iteration over child views. + return false; + } if (childIndex == getVirtualChildCount()) { // Check whether the end divider should draw. return (mShowDividers & SHOW_DIVIDER_END) != 0; @@ -746,6 +750,24 @@ public class LinearLayout extends ViewGroup { } /** + * Determines whether or not there's a divider after a specified child index. + * + * @param childIndex Index of child to check for following divider + * @return true if there should be a divider after the child at childIndex + */ + private boolean hasDividerAfterChildAt(int childIndex) { + if (mShowDividers == SHOW_DIVIDER_NONE) { + // Short-circuit to save iteration over child views. + return false; + } + if (allViewsAreGoneAfter(childIndex)) { + // This is the last view that's not gone, check if end divider is enabled. + return (mShowDividers & SHOW_DIVIDER_END) != 0; + } + return (mShowDividers & SHOW_DIVIDER_MIDDLE) != 0; + } + + /** * Checks whether all (virtual) child views before the given index are gone. */ private boolean allViewsAreGoneBefore(int childIndex) { @@ -759,6 +781,20 @@ public class LinearLayout extends ViewGroup { } /** + * Checks whether all (virtual) child views after the given index are gone. + */ + private boolean allViewsAreGoneAfter(int childIndex) { + final int count = getVirtualChildCount(); + for (int i = childIndex + 1; i < count; i++) { + final View child = getVirtualChildAt(i); + if (child != null && child.getVisibility() != GONE) { + return false; + } + } + return true; + } + + /** * Measures the children when the orientation of this LinearLayout is set * to {@link #VERTICAL}. * @@ -1295,6 +1331,7 @@ public class LinearLayout extends ViewGroup { if (useLargestChild && (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED)) { mTotalLength = 0; + nonSkippedChildCount = 0; for (int i = 0; i < count; ++i) { final View child = getVirtualChildAt(i); @@ -1308,6 +1345,11 @@ public class LinearLayout extends ViewGroup { continue; } + nonSkippedChildCount++; + if (hasDividerBeforeChildAt(i)) { + mTotalLength += mDividerWidth; + } + final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); if (isExactly) { @@ -1319,6 +1361,10 @@ public class LinearLayout extends ViewGroup { lp.leftMargin + lp.rightMargin + getNextLocationOffset(child)); } } + + if (nonSkippedChildCount > 0 && hasDividerBeforeChildAt(count)) { + mTotalLength += mDividerWidth; + } } // Add in our padding @@ -1347,6 +1393,7 @@ public class LinearLayout extends ViewGroup { maxHeight = -1; mTotalLength = 0; + nonSkippedChildCount = 0; for (int i = 0; i < count; ++i) { final View child = getVirtualChildAt(i); @@ -1354,6 +1401,11 @@ public class LinearLayout extends ViewGroup { continue; } + nonSkippedChildCount++; + if (hasDividerBeforeChildAt(i)) { + mTotalLength += mDividerWidth; + } + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final float childWeight = lp.weight; if (childWeight > 0) { @@ -1423,6 +1475,10 @@ public class LinearLayout extends ViewGroup { } } + if (nonSkippedChildCount > 0 && hasDividerBeforeChildAt(count)) { + mTotalLength += mDividerWidth; + } + // Add in our padding mTotalLength += mPaddingLeft + mPaddingRight; // TODO: Should we update widthSize with the new total length? @@ -1810,7 +1866,13 @@ public class LinearLayout extends ViewGroup { break; } - if (hasDividerBeforeChildAt(childIndex)) { + if (isLayoutRtl) { + // Because rtl rendering occurs in the reverse direction, we need to check + // after the child rather than before (since after=left in this context) + if (hasDividerAfterChildAt(childIndex)) { + childLeft += mDividerWidth; + } + } else if (hasDividerBeforeChildAt(childIndex)) { childLeft += mDividerWidth; } diff --git a/core/java/android/window/BackAnimationAdaptor.aidl b/core/java/android/window/BackAnimationAdaptor.aidl new file mode 100644 index 000000000000..1082d0ace1ae --- /dev/null +++ b/core/java/android/window/BackAnimationAdaptor.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +/** + * @hide + */ +parcelable BackAnimationAdaptor;
\ No newline at end of file diff --git a/core/java/android/window/BackAnimationAdaptor.java b/core/java/android/window/BackAnimationAdaptor.java new file mode 100644 index 000000000000..cf82046e7e55 --- /dev/null +++ b/core/java/android/window/BackAnimationAdaptor.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Object that describes how to run a remote back animation. + * + * @hide + */ +public class BackAnimationAdaptor implements Parcelable { + + private final IBackAnimationRunner mRunner; + @BackNavigationInfo.BackTargetType + private final int mSupportType; + + public BackAnimationAdaptor(IBackAnimationRunner runner, int supportType) { + mRunner = runner; + mSupportType = supportType; + } + + public BackAnimationAdaptor(Parcel in) { + mRunner = IBackAnimationRunner.Stub.asInterface(in.readStrongBinder()); + mSupportType = in.readInt(); + } + + public IBackAnimationRunner getRunner() { + return mRunner; + } + + @BackNavigationInfo.BackTargetType public int getSupportType() { + return mSupportType; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeStrongInterface(mRunner); + dest.writeInt(mSupportType); + } + + public static final @android.annotation.NonNull Creator<BackAnimationAdaptor> CREATOR = + new Creator<BackAnimationAdaptor>() { + public BackAnimationAdaptor createFromParcel(Parcel in) { + return new BackAnimationAdaptor(in); + } + + public BackAnimationAdaptor[] newArray(int size) { + return new BackAnimationAdaptor[size]; + } + }; +} diff --git a/core/java/android/window/BackNavigationInfo.java b/core/java/android/window/BackNavigationInfo.java index dd4901417671..941511ec33be 100644 --- a/core/java/android/window/BackNavigationInfo.java +++ b/core/java/android/window/BackNavigationInfo.java @@ -101,6 +101,8 @@ public final class BackNavigationInfo implements Parcelable { @Nullable private final IOnBackInvokedCallback mOnBackInvokedCallback; + private final boolean mIsPrepareRemoteAnimation; + /** * Create a new {@link BackNavigationInfo} instance. * @@ -117,6 +119,9 @@ public final class BackNavigationInfo implements Parcelable { * @param onBackNavigationDone The callback to be called once the client is done with the * back preview. * @param onBackInvokedCallback The back callback registered by the current top level window. + * @param isPrepareRemoteAnimation Return whether the core is preparing a back gesture + * animation, if true, the caller of startBackNavigation should + * be expected to receive an animation start callback. */ private BackNavigationInfo(@BackTargetType int type, @Nullable RemoteAnimationTarget departingAnimationTarget, @@ -124,7 +129,8 @@ public final class BackNavigationInfo implements Parcelable { @Nullable HardwareBuffer screenshotBuffer, @Nullable WindowConfiguration taskWindowConfiguration, @Nullable RemoteCallback onBackNavigationDone, - @Nullable IOnBackInvokedCallback onBackInvokedCallback) { + @Nullable IOnBackInvokedCallback onBackInvokedCallback, + boolean isPrepareRemoteAnimation) { mType = type; mDepartingAnimationTarget = departingAnimationTarget; mScreenshotSurface = screenshotSurface; @@ -132,6 +138,7 @@ public final class BackNavigationInfo implements Parcelable { mTaskWindowConfiguration = taskWindowConfiguration; mOnBackNavigationDone = onBackNavigationDone; mOnBackInvokedCallback = onBackInvokedCallback; + mIsPrepareRemoteAnimation = isPrepareRemoteAnimation; } private BackNavigationInfo(@NonNull Parcel in) { @@ -142,6 +149,7 @@ public final class BackNavigationInfo implements Parcelable { mTaskWindowConfiguration = in.readTypedObject(WindowConfiguration.CREATOR); mOnBackNavigationDone = in.readTypedObject(RemoteCallback.CREATOR); mOnBackInvokedCallback = IOnBackInvokedCallback.Stub.asInterface(in.readStrongBinder()); + mIsPrepareRemoteAnimation = in.readBoolean(); } @Override @@ -153,6 +161,7 @@ public final class BackNavigationInfo implements Parcelable { dest.writeTypedObject(mTaskWindowConfiguration, flags); dest.writeTypedObject(mOnBackNavigationDone, flags); dest.writeStrongInterface(mOnBackInvokedCallback); + dest.writeBoolean(mIsPrepareRemoteAnimation); } /** @@ -221,6 +230,10 @@ public final class BackNavigationInfo implements Parcelable { return mOnBackInvokedCallback; } + public boolean isPrepareRemoteAnimation() { + return mIsPrepareRemoteAnimation; + } + /** * Callback to be called when the back preview is finished in order to notify the server that * it can clean up the resources created for the animation. @@ -306,6 +319,8 @@ public final class BackNavigationInfo implements Parcelable { @Nullable private IOnBackInvokedCallback mOnBackInvokedCallback = null; + private boolean mPrepareAnimation; + /** * @see BackNavigationInfo#getType() */ @@ -366,12 +381,20 @@ public final class BackNavigationInfo implements Parcelable { } /** + * @param prepareAnimation Whether core prepare animation for shell. + */ + public Builder setPrepareAnimation(boolean prepareAnimation) { + mPrepareAnimation = prepareAnimation; + return this; + } + + /** * Builds and returns an instance of {@link BackNavigationInfo} */ public BackNavigationInfo build() { return new BackNavigationInfo(mType, mDepartingAnimationTarget, mScreenshotSurface, mScreenshotBuffer, mTaskWindowConfiguration, mOnBackNavigationDone, - mOnBackInvokedCallback); + mOnBackInvokedCallback, mPrepareAnimation); } } } diff --git a/core/java/android/window/IBackAnimationRunner.aidl b/core/java/android/window/IBackAnimationRunner.aidl new file mode 100644 index 000000000000..ca04b9d358b8 --- /dev/null +++ b/core/java/android/window/IBackAnimationRunner.aidl @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import android.view.RemoteAnimationTarget; +import android.window.IBackNaviAnimationController; + +/** + * Interface that is used to callback from window manager to the process that runs a back gesture + * animation to start or cancel it. + * + * {@hide} + */ +oneway interface IBackAnimationRunner { + + /** + * Called when the system needs to cancel the current animation. This can be due to the + * wallpaper not drawing in time, or the handler not finishing the animation within a predefined + * amount of time. + * + */ + void onAnimationCancelled() = 1; + + /** + * Called when the system is ready for the handler to start animating all the visible tasks. + * + */ + void onAnimationStart(in IBackNaviAnimationController controller, in int type, + in RemoteAnimationTarget[] apps, in RemoteAnimationTarget[] wallpapers, + in RemoteAnimationTarget[] nonApps) = 2; +} diff --git a/core/java/android/window/IBackNaviAnimationController.aidl b/core/java/android/window/IBackNaviAnimationController.aidl new file mode 100644 index 000000000000..bba223ea339b --- /dev/null +++ b/core/java/android/window/IBackNaviAnimationController.aidl @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.window; + +/** + * Interface to be invoked by the controlling process when a back animation has finished. + * + * @param trigger Whether the back gesture has passed the triggering threshold. + * {@hide} + */ +interface IBackNaviAnimationController { + void finish(in boolean triggerBack); +} diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl index 8407d10bc3ea..884ca77ea377 100644 --- a/core/java/android/window/ITaskFragmentOrganizerController.aidl +++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl @@ -16,8 +16,10 @@ package android.window; +import android.os.IBinder; import android.view.RemoteAnimationDefinition; import android.window.ITaskFragmentOrganizer; +import android.window.WindowContainerTransaction; /** @hide */ interface ITaskFragmentOrganizerController { @@ -46,8 +48,15 @@ interface ITaskFragmentOrganizerController { void unregisterRemoteAnimations(in ITaskFragmentOrganizer organizer, int taskId); /** - * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and - * only occupies a portion of Task bounds. - */ + * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and + * only occupies a portion of Task bounds. + */ boolean isActivityEmbedded(in IBinder activityToken); + + /** + * Notifies the server that the organizer has finished handling the given transaction. The + * server should apply the given {@link WindowContainerTransaction} for the necessary changes. + */ + void onTransactionHandled(in ITaskFragmentOrganizer organizer, in IBinder transactionToken, + in WindowContainerTransaction wct); } diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java index 5372071f7f0e..7b6139fbcc2f 100644 --- a/core/java/android/window/TaskFragmentOrganizer.java +++ b/core/java/android/window/TaskFragmentOrganizer.java @@ -148,27 +148,97 @@ public class TaskFragmentOrganizer extends WindowOrganizer { } /** + * Notifies the server that the organizer has finished handling the given transaction. The + * server should apply the given {@link WindowContainerTransaction} for the necessary changes. + * + * @param transactionToken {@link TaskFragmentTransaction#getTransactionToken()} from + * {@link #onTransactionReady(TaskFragmentTransaction)} + * @param wct {@link WindowContainerTransaction} that the server should apply for + * update of the transaction. + * @see com.android.server.wm.WindowOrganizerController#enforceTaskPermission for permission + * requirement. + * @hide + */ + public void onTransactionHandled(@NonNull IBinder transactionToken, + @NonNull WindowContainerTransaction wct) { + wct.setTaskFragmentOrganizer(mInterface); + try { + getController().onTransactionHandled(mInterface, transactionToken, wct); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Called when a TaskFragment is created and organized by this organizer. * * @param taskFragmentInfo Info of the TaskFragment that is created. + * @deprecated Use {@link #onTaskFragmentAppeared(WindowContainerTransaction, TaskFragmentInfo)} + * instead. */ + @Deprecated public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {} /** + * Called when a TaskFragment is created and organized by this organizer. + * + * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. No + * need to call {@link #applyTransaction} as it will be applied by the caller. + * @param taskFragmentInfo Info of the TaskFragment that is created. + */ + public void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct, + @NonNull TaskFragmentInfo taskFragmentInfo) { + // TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next release. + onTaskFragmentAppeared(taskFragmentInfo); + } + + /** * Called when the status of an organized TaskFragment is changed. * * @param taskFragmentInfo Info of the TaskFragment that is changed. + * @deprecated Use {@link #onTaskFragmentInfoChanged(WindowContainerTransaction, + * TaskFragmentInfo)} instead. */ + @Deprecated public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {} /** + * Called when the status of an organized TaskFragment is changed. + * + * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. No + * need to call {@link #applyTransaction} as it will be applied by the caller. + * @param taskFragmentInfo Info of the TaskFragment that is changed. + */ + public void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct, + @NonNull TaskFragmentInfo taskFragmentInfo) { + // TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next release. + onTaskFragmentInfoChanged(taskFragmentInfo); + } + + /** * Called when an organized TaskFragment is removed. * * @param taskFragmentInfo Info of the TaskFragment that is removed. + * @deprecated Use {@link #onTaskFragmentVanished(WindowContainerTransaction, + * TaskFragmentInfo)} instead. */ + @Deprecated public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {} /** + * Called when an organized TaskFragment is removed. + * + * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. No + * need to call {@link #applyTransaction} as it will be applied by the caller. + * @param taskFragmentInfo Info of the TaskFragment that is removed. + */ + public void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct, + @NonNull TaskFragmentInfo taskFragmentInfo) { + // TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next release. + onTaskFragmentVanished(taskFragmentInfo); + } + + /** * Called when the parent leaf Task of organized TaskFragments is changed. * When the leaf Task is changed, the organizer may want to update the TaskFragments in one * transaction. @@ -176,7 +246,13 @@ public class TaskFragmentOrganizer extends WindowOrganizer { * For case like screen size change, it will trigger onTaskFragmentParentInfoChanged with new * Task bounds, but may not trigger onTaskFragmentInfoChanged because there can be an override * bounds. + * + * @param fragmentToken The parent Task this TaskFragment is changed. + * @param parentConfig Config of the parent Task. + * @deprecated Use {@link #onTaskFragmentParentInfoChanged(WindowContainerTransaction, int, + * Configuration)} instead. */ + @Deprecated public void onTaskFragmentParentInfoChanged( @NonNull IBinder fragmentToken, @NonNull Configuration parentConfig) {} @@ -189,11 +265,13 @@ public class TaskFragmentOrganizer extends WindowOrganizer { * Task bounds, but may not trigger onTaskFragmentInfoChanged because there can be an override * bounds. * + * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. No + * need to call {@link #applyTransaction} as it will be applied by the caller. * @param taskId Id of the parent Task that is changed. * @param parentConfig Config of the parent Task. - * @hide */ - public void onTaskFragmentParentInfoChanged(int taskId, @NonNull Configuration parentConfig) { + public void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct, int taskId, + @NonNull Configuration parentConfig) { // TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next release. final List<IBinder> tokens = mTaskIdToFragmentTokens.get(taskId); if (tokens == null || tokens.isEmpty()) { @@ -211,9 +289,8 @@ public class TaskFragmentOrganizer extends WindowOrganizer { * @param errorCallbackToken token set in * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} * @param exception exception from the server side. - * - * @deprecated Use {@link #onTaskFragmentError(IBinder, TaskFragmentInfo, int, Throwable)} - * instead. + * @deprecated Use {@link #onTaskFragmentError(WindowContainerTransaction, IBinder, + * TaskFragmentInfo, int, Throwable)} instead. */ @Deprecated public void onTaskFragmentError( @@ -223,6 +300,8 @@ public class TaskFragmentOrganizer extends WindowOrganizer { * Called when the {@link WindowContainerTransaction} created with * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side. * + * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. No + * need to call {@link #applyTransaction} as it will be applied by the caller. * @param errorCallbackToken token set in * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} * @param taskFragmentInfo The {@link TaskFragmentInfo}. This could be {@code null} if no @@ -231,7 +310,7 @@ public class TaskFragmentOrganizer extends WindowOrganizer { * transaction operation. * @param exception exception from the server side. */ - public void onTaskFragmentError( + public void onTaskFragmentError(@NonNull WindowContainerTransaction wct, @NonNull IBinder errorCallbackToken, @Nullable TaskFragmentInfo taskFragmentInfo, int opType, @NonNull Throwable exception) { // Doing so to keep compatibility. This will be removed in the next release. @@ -244,6 +323,8 @@ public class TaskFragmentOrganizer extends WindowOrganizer { * original Task. In this case, we need to notify the organizer so that it can check if the * Activity matches any split rule. * + * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. No + * need to call {@link #applyTransaction} as it will be applied by the caller. * @param taskId The Task that the activity is reparented to. * @param activityIntent The intent that the activity is original launched with. * @param activityToken If the activity belongs to the same process as the organizer, this @@ -251,10 +332,9 @@ public class TaskFragmentOrganizer extends WindowOrganizer { * different process, the server will generate a temporary token that * the organizer can use to reparent the activity through * {@link WindowContainerTransaction} if needed. - * @hide */ - public void onActivityReparentedToTask(int taskId, @NonNull Intent activityIntent, - @NonNull IBinder activityToken) {} + public void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct, + int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken) {} /** * Called when the transaction is ready so that the organizer can update the TaskFragments based @@ -262,9 +342,9 @@ public class TaskFragmentOrganizer extends WindowOrganizer { * @hide */ public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); final List<TaskFragmentTransaction.Change> changes = transaction.getChanges(); for (TaskFragmentTransaction.Change change : changes) { - // TODO(b/240519866): apply all changes in one WCT. final int taskId = change.getTaskId(); switch (change.getType()) { case TYPE_TASK_FRAGMENT_APPEARED: @@ -277,10 +357,10 @@ public class TaskFragmentOrganizer extends WindowOrganizer { onTaskFragmentParentInfoChanged(change.getTaskFragmentToken(), mTaskIdToConfigurations.get(taskId)); - onTaskFragmentAppeared(change.getTaskFragmentInfo()); + onTaskFragmentAppeared(wct, change.getTaskFragmentInfo()); break; case TYPE_TASK_FRAGMENT_INFO_CHANGED: - onTaskFragmentInfoChanged(change.getTaskFragmentInfo()); + onTaskFragmentInfoChanged(wct, change.getTaskFragmentInfo()); break; case TYPE_TASK_FRAGMENT_VANISHED: // TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next @@ -294,18 +374,19 @@ public class TaskFragmentOrganizer extends WindowOrganizer { } } - onTaskFragmentVanished(change.getTaskFragmentInfo()); + onTaskFragmentVanished(wct, change.getTaskFragmentInfo()); break; case TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED: // TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next // release. mTaskIdToConfigurations.put(taskId, change.getTaskConfiguration()); - onTaskFragmentParentInfoChanged(taskId, change.getTaskConfiguration()); + onTaskFragmentParentInfoChanged(wct, taskId, change.getTaskConfiguration()); break; case TYPE_TASK_FRAGMENT_ERROR: final Bundle errorBundle = change.getErrorBundle(); onTaskFragmentError( + wct, change.getErrorCallbackToken(), errorBundle.getParcelable( KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, TaskFragmentInfo.class), @@ -315,6 +396,7 @@ public class TaskFragmentOrganizer extends WindowOrganizer { break; case TYPE_ACTIVITY_REPARENTED_TO_TASK: onActivityReparentedToTask( + wct, change.getTaskId(), change.getActivityIntent(), change.getActivityToken()); @@ -324,6 +406,9 @@ public class TaskFragmentOrganizer extends WindowOrganizer { "Unknown TaskFragmentEvent=" + change.getType()); } } + + // Notify the server, and the server should apply the WindowContainerTransaction. + onTransactionHandled(transaction.getTransactionToken(), wct); } @Override diff --git a/core/java/android/window/TaskFragmentTransaction.java b/core/java/android/window/TaskFragmentTransaction.java index 07e8e8c473c6..84a5fea9f57f 100644 --- a/core/java/android/window/TaskFragmentTransaction.java +++ b/core/java/android/window/TaskFragmentTransaction.java @@ -23,6 +23,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Intent; import android.content.res.Configuration; +import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.Parcel; @@ -41,19 +42,31 @@ import java.util.List; */ public final class TaskFragmentTransaction implements Parcelable { + /** Unique token to represent this transaction. */ + private final IBinder mTransactionToken; + + /** Changes in this transaction. */ private final ArrayList<Change> mChanges = new ArrayList<>(); - public TaskFragmentTransaction() {} + public TaskFragmentTransaction() { + mTransactionToken = new Binder(); + } private TaskFragmentTransaction(Parcel in) { + mTransactionToken = in.readStrongBinder(); in.readTypedList(mChanges, Change.CREATOR); } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeStrongBinder(mTransactionToken); dest.writeTypedList(mChanges); } + public IBinder getTransactionToken() { + return mTransactionToken; + } + /** Adds a {@link Change} to this transaction. */ public void addChange(@Nullable Change change) { if (change != null) { @@ -74,7 +87,9 @@ public final class TaskFragmentTransaction implements Parcelable { @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("TaskFragmentTransaction{changes=["); + sb.append("TaskFragmentTransaction{token="); + sb.append(mTransactionToken); + sb.append(" changes=["); for (int i = 0; i < mChanges.size(); ++i) { if (i > 0) { sb.append(','); diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index b263b08d6abc..dc1f612534e2 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -119,6 +119,12 @@ public final class TransitionInfo implements Parcelable { /** The container is going to show IME on its task after the transition. */ public static final int FLAG_WILL_IME_SHOWN = 1 << 11; + /** The container attaches owner profile thumbnail for cross profile animation. */ + public static final int FLAG_CROSS_PROFILE_OWNER_THUMBNAIL = 1 << 12; + + /** The container attaches work profile thumbnail for cross profile animation. */ + public static final int FLAG_CROSS_PROFILE_WORK_THUMBNAIL = 1 << 13; + /** @hide */ @IntDef(prefix = { "FLAG_" }, value = { FLAG_NONE, @@ -508,6 +514,11 @@ public final class TransitionInfo implements Parcelable { return mFlags; } + /** Whether the given change flags has included in this change. */ + public boolean hasFlags(@ChangeFlags int flags) { + return (mFlags & flags) != 0; + } + /** * @return the bounds of the container before the change. It may be empty if the container * is coming into existence. diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index e9e437fda8f2..0e1ed7bd0550 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -1283,9 +1283,6 @@ public class ResolverActivity extends Activity implements } if (target != null) { - if (intent != null && isLaunchingTargetInOtherProfile()) { - prepareIntentForCrossProfileLaunch(intent); - } safelyStartActivity(target); // Rely on the ActivityManager to pop up a dialog regarding app suspension @@ -1298,15 +1295,6 @@ public class ResolverActivity extends Activity implements return true; } - private void prepareIntentForCrossProfileLaunch(Intent intent) { - intent.fixUris(UserHandle.myUserId()); - } - - private boolean isLaunchingTargetInOtherProfile() { - return mMultiProfilePagerAdapter.getCurrentUserHandle().getIdentifier() - != UserHandle.myUserId(); - } - @VisibleForTesting public void safelyStartActivity(TargetInfo cti) { // We're dispatching intents that might be coming from legacy apps, so @@ -1513,9 +1501,6 @@ public class ResolverActivity extends Activity implements findViewById(R.id.button_open).setOnClickListener(v -> { Intent intent = otherProfileResolveInfo.getResolvedIntent(); - if (intent != null) { - prepareIntentForCrossProfileLaunch(intent); - } safelyStartActivityAsUser(otherProfileResolveInfo, inactiveAdapter.mResolverListController.getUserHandle()); finish(); diff --git a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java index ff188dc6f2b9..1be1247b7cc0 100644 --- a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java +++ b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java @@ -207,7 +207,11 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable { case TYPE_HEADER_ALL_OTHERS: TextView textView = (TextView) itemView; if (itemType == TYPE_HEADER_SUGGESTED) { - setTextTo(textView, R.string.language_picker_section_suggested); + if (mCountryMode) { + setTextTo(textView, R.string.language_picker_regions_section_suggested); + } else { + setTextTo(textView, R.string.language_picker_section_suggested); + } } else { if (mCountryMode) { setTextTo(textView, R.string.region_picker_section_all); diff --git a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java index 96cc5e1bd7d2..5f4a9cd5141e 100644 --- a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java +++ b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java @@ -172,12 +172,14 @@ public class DisplayResolveInfo implements TargetInfo, Parcelable { @Override public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) { + prepareIntentForCrossProfileLaunch(mResolvedIntent, userId); activity.startActivityAsCaller(mResolvedIntent, options, false, userId); return true; } @Override public boolean startAsUser(Activity activity, Bundle options, UserHandle user) { + prepareIntentForCrossProfileLaunch(mResolvedIntent, user.getIdentifier()); activity.startActivityAsUser(mResolvedIntent, options, user); return false; } @@ -222,6 +224,13 @@ public class DisplayResolveInfo implements TargetInfo, Parcelable { } }; + private static void prepareIntentForCrossProfileLaunch(Intent intent, int targetUserId) { + final int currentUserId = UserHandle.myUserId(); + if (targetUserId != currentUserId) { + intent.fixUris(currentUserId); + } + } + private DisplayResolveInfo(Parcel in) { mDisplayLabel = in.readCharSequence(); mExtendedInfo = in.readCharSequence(); diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 98d4c5976adc..06473b5ee8de 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -6144,7 +6144,8 @@ public class BatteryStatsImpl extends BatteryStats { } @GuardedBy("this") - public void noteUserActivityLocked(int uid, int event, long elapsedRealtimeMs, long uptimeMs) { + public void noteUserActivityLocked(int uid, @PowerManager.UserActivityEvent int event, + long elapsedRealtimeMs, long uptimeMs) { if (mOnBatteryInternal) { uid = mapUid(uid); getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs).noteUserActivityLocked(event); @@ -9956,14 +9957,14 @@ public class BatteryStatsImpl extends BatteryStats { } @Override - public void noteUserActivityLocked(int type) { + public void noteUserActivityLocked(@PowerManager.UserActivityEvent int event) { if (mUserActivityCounters == null) { initUserActivityLocked(); } - if (type >= 0 && type < NUM_USER_ACTIVITY_TYPES) { - mUserActivityCounters[type].stepAtomic(); + if (event >= 0 && event < NUM_USER_ACTIVITY_TYPES) { + mUserActivityCounters[event].stepAtomic(); } else { - Slog.w(TAG, "Unknown user activity type " + type + " was specified.", + Slog.w(TAG, "Unknown user activity event " + event + " was specified.", new Throwable()); } } diff --git a/core/java/com/android/internal/view/inline/InlineTooltipUi.java b/core/java/com/android/internal/view/inline/InlineTooltipUi.java index 3eae89e350a0..836786d7c592 100644 --- a/core/java/com/android/internal/view/inline/InlineTooltipUi.java +++ b/core/java/com/android/internal/view/inline/InlineTooltipUi.java @@ -170,9 +170,9 @@ public final class InlineTooltipUi extends PopupWindow implements AutoCloseable int delayTimeMs = mShowDelayConfigMs; try { - final float scale = Settings.Global.getFloat( + final float scale = WindowManager.fixScale(Settings.Global.getFloat( anchor.getContext().getContentResolver(), - Settings.Global.ANIMATOR_DURATION_SCALE); + Settings.Global.ANIMATOR_DURATION_SCALE)); delayTimeMs *= scale; } catch (Settings.SettingNotFoundException e) { // do nothing diff --git a/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java index 8c61a12b47e6..80d8bd78e746 100644 --- a/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java +++ b/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java @@ -1475,7 +1475,6 @@ public final class LocalFloatingToolbarPopup implements FloatingToolbarPopup { contentContainer.setLayoutParams(new ViewGroup.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); contentContainer.setTag(FloatingToolbar.FLOATING_TOOLBAR_TAG); - contentContainer.setContentDescription(FloatingToolbar.FLOATING_TOOLBAR_TAG); contentContainer.setClipToOutline(true); return contentContainer; } diff --git a/core/jni/Android.bp b/core/jni/Android.bp index a1be88440c97..aa661713b1fe 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -401,6 +401,7 @@ cc_library_shared { // (e.g. gDefaultServiceManager) "libbinder", "libhidlbase", // libhwbinder is in here + "libvintf", ], }, }, diff --git a/core/res/res/drawable/ic_sd_card_48dp.xml b/core/res/res/drawable/ic_sd_card_48dp.xml index 90bab47c0304..10fd12054820 100644 --- a/core/res/res/drawable/ic_sd_card_48dp.xml +++ b/core/res/res/drawable/ic_sd_card_48dp.xml @@ -19,6 +19,6 @@ Copyright (C) 2015 The Android Open Source Project android:viewportWidth="48.0" android:viewportHeight="48.0"> <path - android:fillColor="#FF000000" + android:fillColor="?android:attr/colorAccent" android:pathData="M36 4H20L8.04 16 8 40c0 2.2 1.8 4 4 4h24c2.2 0 4,-1.8 4,-4V8c0,-2.2,-1.8,-4,-4,-4zM24 16h-4V8h4v8zm6 0h-4V8h4v8zm6 0h-4V8h4v8z"/> </vector> diff --git a/core/res/res/layout/floating_popup_container.xml b/core/res/res/layout/floating_popup_container.xml index ca0373773577..776a35d15ef0 100644 --- a/core/res/res/layout/floating_popup_container.xml +++ b/core/res/res/layout/floating_popup_container.xml @@ -16,6 +16,7 @@ */ --> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/floating_popup_container" android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="0dp" diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 05b9c8a39544..eef2ff627c79 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2427,9 +2427,6 @@ <!-- The list of supported dream complications --> <integer-array name="config_supportedDreamComplications"> </integer-array> - <!-- The list of dream complications which should be enabled by default --> - <integer-array name="config_dreamComplicationsEnabledByDefault"> - </integer-array> <!-- Are we allowed to dream while not plugged in? --> <bool name="config_dreamsEnabledOnBattery">false</bool> @@ -5899,4 +5896,10 @@ <!-- The number of tasks to scan to get the visibility of Home --> <integer name="config_maxScanTasksForHomeVisibility">10</integer> + + <!-- Device state that corresponds to rear display mode, feature provided + through Jetpack WindowManager + TODO(b/236022708) Move rear display state to device state config file + --> + <integer name="config_deviceStateRearDisplay">-1</integer> </resources> diff --git a/core/res/res/values/locale_config.xml b/core/res/res/values/locale_config.xml index e9b42d392a2c..78ec14579bf1 100644 --- a/core/res/res/values/locale_config.xml +++ b/core/res/res/values/locale_config.xml @@ -23,7 +23,7 @@ <item>ak-GH</item> <!-- Akan (Ghana) --> <item>am-ET</item> <!-- Amharic (Ethiopia) --> <item>ar-AE</item> <!-- Arabic (United Arab Emirates) --> - <item>ar-AE-u-nu-latn</item> <!-- Arabic (United Arab Emirates, Western Digits) --> + <item>ar-AE-u-nu-arab</item> <!-- Arabic (United Arab Emirates, Arabic Digits) --> <item>ar-BH</item> <!-- Arabic (Bahrain) --> <item>ar-BH-u-nu-latn</item> <!-- Arabic (Bahrain, Western Digits) --> <item>ar-DJ</item> <!-- Arabic (Djibouti) --> @@ -190,6 +190,7 @@ <item>en-MS</item> <!-- English (Montserrat) --> <item>en-MT</item> <!-- English (Malta) --> <item>en-MU</item> <!-- English (Mauritius) --> + <item>en-MV</item> <!-- English (Maldives) --> <item>en-MW</item> <!-- English (Malawi) --> <item>en-MY</item> <!-- English (Malaysia) --> <item>en-NA</item> <!-- English (Namibia) --> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index e3b710063721..fbc214803403 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -3974,7 +3974,7 @@ <string name="ext_media_new_notification_message" product="automotive">You may need to reformat the device. Tap to eject.</string> <!-- Notification body when external media is ready for use [CHAR LIMIT=NONE] --> - <string name="ext_media_ready_notification_message">For transferring photos and media</string> + <string name="ext_media_ready_notification_message">For storing photos, videos, music and more</string> <!-- TV specific notification body when external media is ready for use [CHAR LIMIT=75] --> <string name="ext_media_ready_notification_message" product="tv">Browse media files</string> @@ -3990,11 +3990,11 @@ <string name="ext_media_unmountable_notification_message" product="automotive">You may need to reformat the device. Tap to eject.</string> <!-- Notification title when external media is unsupported [CHAR LIMIT=30] --> - <string name="ext_media_unsupported_notification_title">Unsupported <xliff:g id="name" example="SD card">%s</xliff:g></string> + <string name="ext_media_unsupported_notification_title"><xliff:g id="name" example="SD card">%s</xliff:g> detected </string> <!-- Automotive specific notification title when external media is unsupported [CHAR LIMIT=30] --> <string name="ext_media_unsupported_notification_title" product="automotive"><xliff:g id="name" example="SD card">%s</xliff:g> isn\u2019t working</string> <!-- Notification body when external media is unsupported [CHAR LIMIT=NONE] --> - <string name="ext_media_unsupported_notification_message">This device doesn\u2019t support this <xliff:g id="name" example="SD card">%s</xliff:g>. Tap to set up in a supported format.</string> + <string name="ext_media_unsupported_notification_message">Tap to set up .</string> <!-- TV-specific notification body when external media is unsupported [CHAR LIMIT=75] --> <string name="ext_media_unsupported_notification_message" product="tv">Select to set up <xliff:g id="name" example="SD card">%s</xliff:g> in a supported format.</string> <!-- Automotive specific notification body when external media is unsupported [CHAR LIMIT=NONE] --> @@ -4025,7 +4025,7 @@ <!-- Notification action to transfer media [CHAR LIMIT=40] --> <string name="ext_media_seamless_action">Switch output</string> - <!-- Notification title when external media is missing [CHAR LIMIT=30] --> + <!-- Notification title when adoptable storage media is ejected [CHAR LIMIT=30] --> <string name="ext_media_missing_title"><xliff:g id="name" example="SD card">%s</xliff:g> missing</string> <!-- Notification body when external media is missing [CHAR LIMIT=30] --> <string name="ext_media_missing_message">Insert device again</string> @@ -5429,8 +5429,10 @@ <!-- Hint text in a search edit box (used to filter long language / country lists) [CHAR LIMIT=25] --> <string name="search_language_hint">Type language name</string> - <!-- List section subheader for the language picker, containing a list of suggested languages determined by the default region [CHAR LIMIT=30] --> + <!-- List section subheader for the language picker, containing a list of suggested languages [CHAR LIMIT=30] --> <string name="language_picker_section_suggested">Suggested</string> + <!-- "List section subheader for the language picker, containing a list of suggested regions available for that language [CHAR LIMIT=30] --> + <string name="language_picker_regions_section_suggested">Suggested</string> <!-- List section subheader for the language picker, containing a list of all languages available [CHAR LIMIT=30] --> <string name="language_picker_section_all">All languages</string> <!-- List section subheader for the region picker, containing a list of all regions supported for the selected language. @@ -5752,7 +5754,7 @@ <!-- Content for the log access confirmation dialog. [CHAR LIMIT=NONE]--> <string name="log_access_confirmation_body">Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps you trust to access all device logs. - \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device. Learn more + \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device. </string> <!-- Privacy notice do not show [CHAR LIMIT=20] --> @@ -6323,6 +6325,8 @@ ul.</string> <string name="vdm_camera_access_denied" product="default">Can’t access the phone’s camera from your <xliff:g id="device" example="Chromebook">%1$s</xliff:g></string> <!-- Error message indicating the camera cannot be accessed when running on a virtual device. [CHAR LIMIT=NONE] --> <string name="vdm_camera_access_denied" product="tablet">Can’t access the tablet’s camera from your <xliff:g id="device" example="Chromebook">%1$s</xliff:g></string> + <!-- Error message indicating the user cannot access secure content when running on a virtual device. [CHAR LIMIT=NONE] --> + <string name="vdm_secure_window">This can’t be accessed while streaming. Try on your phone instead.</string> <!-- Title for preference of the system default locale. [CHAR LIMIT=50]--> <string name="system_locale_title">System default</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 205681c8f59a..3ddaddd6ba40 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2230,7 +2230,6 @@ <java-symbol type="string" name="config_dreamsDefaultComponent" /> <java-symbol type="bool" name="config_dreamsOnlyEnabledForSystemUser" /> <java-symbol type="array" name="config_supportedDreamComplications" /> - <java-symbol type="array" name="config_dreamComplicationsEnabledByDefault" /> <java-symbol type="array" name="config_disabledDreamComponents" /> <java-symbol type="bool" name="config_dismissDreamOnActivityStart" /> <java-symbol type="string" name="config_loggable_dream_prefix" /> @@ -3135,6 +3134,7 @@ <java-symbol type="string" name="language_picker_section_all" /> <java-symbol type="string" name="region_picker_section_all" /> <java-symbol type="string" name="language_picker_section_suggested" /> + <java-symbol type="string" name="language_picker_regions_section_suggested" /> <java-symbol type="string" name="language_selection_title" /> <java-symbol type="string" name="search_language_hint" /> @@ -4795,6 +4795,7 @@ <!-- For VirtualDeviceManager --> <java-symbol type="string" name="vdm_camera_access_denied" /> + <java-symbol type="string" name="vdm_secure_window" /> <java-symbol type="color" name="camera_privacy_light_day"/> <java-symbol type="color" name="camera_privacy_light_night"/> @@ -4821,6 +4822,7 @@ <java-symbol type="drawable" name="ic_swap_horiz" /> <java-symbol type="array" name="config_deviceStatesAvailableForAppRequests" /> <java-symbol type="array" name="config_serviceStateLocationAllowedPackages" /> + <java-symbol type="integer" name="config_deviceStateRearDisplay"/> <!-- For app language picker --> <java-symbol type="string" name="system_locale_title" /> diff --git a/core/tests/coretests/src/android/app/NotificationChannelGroupTest.java b/core/tests/coretests/src/android/app/NotificationChannelGroupTest.java new file mode 100644 index 000000000000..2a3da05eabb3 --- /dev/null +++ b/core/tests/coretests/src/android/app/NotificationChannelGroupTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import static junit.framework.TestCase.assertEquals; + +import android.os.Parcel; +import android.test.AndroidTestCase; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.google.common.base.Strings; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.lang.reflect.Field; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NotificationChannelGroupTest { + private final String CLASS = "android.app.NotificationChannelGroup"; + + @Test + public void testLongStringFields() { + NotificationChannelGroup group = new NotificationChannelGroup("my_group_01", "groupName"); + + try { + String longString = Strings.repeat("A", 65536); + Field mName = Class.forName(CLASS).getDeclaredField("mName"); + mName.setAccessible(true); + mName.set(group, longString); + Field mId = Class.forName(CLASS).getDeclaredField("mId"); + mId.setAccessible(true); + mId.set(group, longString); + Field mDescription = Class.forName(CLASS).getDeclaredField("mDescription"); + mDescription.setAccessible(true); + mDescription.set(group, longString); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + + Parcel parcel = Parcel.obtain(); + group.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + NotificationChannelGroup fromParcel = + NotificationChannelGroup.CREATOR.createFromParcel(parcel); + assertEquals(NotificationChannelGroup.MAX_TEXT_LENGTH, fromParcel.getId().length()); + assertEquals(NotificationChannelGroup.MAX_TEXT_LENGTH, fromParcel.getName().length()); + assertEquals(NotificationChannelGroup.MAX_TEXT_LENGTH, + fromParcel.getDescription().length()); + } +} diff --git a/core/tests/coretests/src/android/app/NotificationChannelTest.java b/core/tests/coretests/src/android/app/NotificationChannelTest.java new file mode 100644 index 000000000000..647bfe84231d --- /dev/null +++ b/core/tests/coretests/src/android/app/NotificationChannelTest.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import static junit.framework.TestCase.assertEquals; + +import android.net.Uri; +import android.os.Parcel; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.google.common.base.Strings; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.lang.reflect.Field; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NotificationChannelTest { + private final String CLASS = "android.app.NotificationChannel"; + + @Test + public void testLongStringFields() { + NotificationChannel channel = new NotificationChannel("id", "name", 3); + + try { + String longString = Strings.repeat("A", 65536); + Field mName = Class.forName(CLASS).getDeclaredField("mName"); + mName.setAccessible(true); + mName.set(channel, longString); + Field mId = Class.forName(CLASS).getDeclaredField("mId"); + mId.setAccessible(true); + mId.set(channel, longString); + Field mDesc = Class.forName(CLASS).getDeclaredField("mDesc"); + mDesc.setAccessible(true); + mDesc.set(channel, longString); + Field mParentId = Class.forName(CLASS).getDeclaredField("mParentId"); + mParentId.setAccessible(true); + mParentId.set(channel, longString); + Field mGroup = Class.forName(CLASS).getDeclaredField("mGroup"); + mGroup.setAccessible(true); + mGroup.set(channel, longString); + Field mConversationId = Class.forName(CLASS).getDeclaredField("mConversationId"); + mConversationId.setAccessible(true); + mConversationId.set(channel, longString); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + + Parcel parcel = Parcel.obtain(); + channel.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + NotificationChannel fromParcel = NotificationChannel.CREATOR.createFromParcel(parcel); + assertEquals(NotificationChannel.MAX_TEXT_LENGTH, fromParcel.getId().length()); + assertEquals(NotificationChannel.MAX_TEXT_LENGTH, fromParcel.getName().length()); + assertEquals(NotificationChannel.MAX_TEXT_LENGTH, + fromParcel.getDescription().length()); + assertEquals(NotificationChannel.MAX_TEXT_LENGTH, + fromParcel.getParentChannelId().length()); + assertEquals(NotificationChannel.MAX_TEXT_LENGTH, + fromParcel.getGroup().length()); + assertEquals(NotificationChannel.MAX_TEXT_LENGTH, + fromParcel.getConversationId().length()); + } + + @Test + public void testLongAlertFields() { + NotificationChannel channel = new NotificationChannel("id", "name", 3); + + channel.setSound(Uri.parse("content://" + Strings.repeat("A",65536)), + Notification.AUDIO_ATTRIBUTES_DEFAULT); + channel.setVibrationPattern(new long[65550/2]); + + Parcel parcel = Parcel.obtain(); + channel.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + NotificationChannel fromParcel = NotificationChannel.CREATOR.createFromParcel(parcel); + assertEquals(NotificationChannel.MAX_VIBRATION_LENGTH, + fromParcel.getVibrationPattern().length); + assertEquals(NotificationChannel.MAX_TEXT_LENGTH, + fromParcel.getSound().toString().length()); + } +} diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java index 50639be57f22..942e1cf3eed5 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java @@ -223,15 +223,15 @@ public class ObjectPoolTests { @Test public void testRecyclePauseActivityItemItem() { - PauseActivityItem emptyItem = PauseActivityItem.obtain(false, false, 0, false); - PauseActivityItem item = PauseActivityItem.obtain(true, true, 5, true); + PauseActivityItem emptyItem = PauseActivityItem.obtain(false, false, 0, false, false); + PauseActivityItem item = PauseActivityItem.obtain(true, true, 5, true, true); assertNotSame(item, emptyItem); assertFalse(item.equals(emptyItem)); item.recycle(); assertEquals(item, emptyItem); - PauseActivityItem item2 = PauseActivityItem.obtain(true, false, 5, true); + PauseActivityItem item2 = PauseActivityItem.obtain(true, false, 5, true, true); assertSame(item, item2); assertFalse(item2.equals(emptyItem)); } diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java index 0eca0a8cb1a7..c868963c4d02 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java @@ -235,7 +235,8 @@ public class TransactionParcelTests { public void testPause() { // Write to parcel PauseActivityItem item = PauseActivityItem.obtain(true /* finished */, - true /* userLeaving */, 135 /* configChanges */, true /* dontReport */); + true /* userLeaving */, 135 /* configChanges */, true /* dontReport */, + true /* autoEnteringPip */); writeAndPrepareForReading(item); // Read from parcel and assert diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index ed6a649021a1..6e59b83810e1 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -236,6 +236,21 @@ public class InsetsControllerTest { } @Test + public void testSystemDrivenInsetsAnimationLoggingListener_onReady() { + prepareControls(); + // only the original thread that created view hierarchy can touch its views + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + WindowInsetsAnimationControlListener loggingListener = + mock(WindowInsetsAnimationControlListener.class); + mController.setSystemDrivenInsetsAnimationLoggingListener(loggingListener); + mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true); + // since there is no focused view, forcefully make IME visible. + mController.show(Type.ime(), true /* fromIme */); + verify(loggingListener).onReady(notNull(), anyInt()); + }); + } + + @Test public void testAnimationEndState() { InsetsSourceControl[] controls = prepareControls(); InsetsSourceControl navBar = controls[0]; diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java index 2054b4fe9a35..8cf118c4b79a 100644 --- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java +++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java @@ -18,8 +18,10 @@ package android.view; import static android.view.InsetsController.ANIMATION_TYPE_NONE; import static android.view.InsetsController.ANIMATION_TYPE_USER; +import static android.view.InsetsSourceConsumer.ShowResult.SHOW_IMMEDIATELY; import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.ITYPE_STATUS_BAR; +import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.statusBars; import static junit.framework.Assert.assertEquals; @@ -28,6 +30,7 @@ import static junit.framework.TestCase.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; @@ -75,6 +78,7 @@ public class InsetsSourceConsumerTest { private boolean mRemoveSurfaceCalled = false; private InsetsController mController; private InsetsState mState; + private ViewRootImpl mViewRoot; @Before public void setup() { @@ -86,10 +90,9 @@ public class InsetsSourceConsumerTest { instrumentation.runOnMainSync(() -> { final Context context = instrumentation.getTargetContext(); // cannot mock ViewRootImpl since it's final. - final ViewRootImpl viewRootImpl = new ViewRootImpl(context, - context.getDisplayNoVerify()); + mViewRoot = new ViewRootImpl(context, context.getDisplayNoVerify()); try { - viewRootImpl.setView(new TextView(context), new LayoutParams(), null); + mViewRoot.setView(new TextView(context), new LayoutParams(), null); } catch (BadTokenException e) { // activity isn't running, lets ignore BadTokenException. } @@ -97,7 +100,7 @@ public class InsetsSourceConsumerTest { mSpyInsetsSource = Mockito.spy(new InsetsSource(ITYPE_STATUS_BAR)); mState.addSource(mSpyInsetsSource); - mController = new InsetsController(new ViewRootInsetsControllerHost(viewRootImpl)); + mController = new InsetsController(new ViewRootInsetsControllerHost(mViewRoot)); mConsumer = new InsetsSourceConsumer(ITYPE_STATUS_BAR, mState, () -> mMockTransaction, mController) { @Override @@ -207,4 +210,40 @@ public class InsetsSourceConsumerTest { }); } + + @Test + public void testWontUpdateImeLeashVisibility_whenAnimation() { + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + InsetsState state = new InsetsState(); + ViewRootInsetsControllerHost host = new ViewRootInsetsControllerHost(mViewRoot); + InsetsController insetsController = new InsetsController(host, (controller, type) -> { + if (type == ITYPE_IME) { + return new InsetsSourceConsumer(ITYPE_IME, state, + () -> mMockTransaction, controller) { + @Override + public int requestShow(boolean fromController) { + return SHOW_IMMEDIATELY; + } + }; + } + return new InsetsSourceConsumer(type, controller.getState(), Transaction::new, + controller); + }, host.getHandler()); + InsetsSourceConsumer imeConsumer = insetsController.getSourceConsumer(ITYPE_IME); + + // Initial IME insets source control with its leash. + imeConsumer.setControl(new InsetsSourceControl(ITYPE_IME, mLeash, + false /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1]); + reset(mMockTransaction); + + // Verify when the app requests controlling show IME animation, the IME leash + // visibility won't be updated when the consumer received the same leash in setControl. + insetsController.controlWindowInsetsAnimation(ime(), 0L, + null /* interpolator */, null /* cancellationSignal */, null /* listener */); + assertTrue(insetsController.getAnimationType(ITYPE_IME) == ANIMATION_TYPE_USER); + imeConsumer.setControl(new InsetsSourceControl(ITYPE_IME, mLeash, + true /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1]); + verify(mMockTransaction, never()).show(mLeash); + }); + } } diff --git a/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java b/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java index 03c8b1bcb475..690b35879388 100644 --- a/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java @@ -213,6 +213,25 @@ public class PendingInsetsControllerTest { } @Test + public void testSystemDrivenInsetsAnimationLoggingListener() { + WindowInsetsAnimationControlListener listener = + mock(WindowInsetsAnimationControlListener.class); + mPendingInsetsController.setSystemDrivenInsetsAnimationLoggingListener(listener); + mPendingInsetsController.replayAndAttach(mReplayedController); + verify(mReplayedController).setSystemDrivenInsetsAnimationLoggingListener(eq(listener)); + } + + @Test + public void testSystemDrivenInsetsAnimationLoggingListener_direct() { + mPendingInsetsController.replayAndAttach(mReplayedController); + WindowInsetsAnimationControlListener listener = + mock(WindowInsetsAnimationControlListener.class); + mPendingInsetsController.setSystemDrivenInsetsAnimationLoggingListener(listener); + verify(mReplayedController).setSystemDrivenInsetsAnimationLoggingListener( + eq(listener)); + } + + @Test public void testDetachReattach() { mPendingInsetsController.show(systemBars()); mPendingInsetsController.setSystemBarsBehavior(BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE); diff --git a/core/tests/coretests/src/android/widget/FloatingToolbarUtils.java b/core/tests/coretests/src/android/widget/FloatingToolbarUtils.java index c6f592447c22..2d3ed9510534 100644 --- a/core/tests/coretests/src/android/widget/FloatingToolbarUtils.java +++ b/core/tests/coretests/src/android/widget/FloatingToolbarUtils.java @@ -16,13 +16,12 @@ package android.widget; -import static com.android.internal.widget.floatingtoolbar.FloatingToolbar.FLOATING_TOOLBAR_TAG; - import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import android.content.res.Resources; import android.support.test.uiautomator.By; +import android.support.test.uiautomator.BySelector; import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.Until; @@ -33,25 +32,27 @@ import com.android.internal.R; final class FloatingToolbarUtils { private final UiDevice mDevice; + private static final BySelector TOOLBAR_CONTAINER_SELECTOR = + By.res("android", "floating_popup_container"); FloatingToolbarUtils() { mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); } void waitForFloatingToolbarPopup() { - mDevice.wait(Until.findObject(By.desc(FLOATING_TOOLBAR_TAG)), 500); + mDevice.wait(Until.findObject(TOOLBAR_CONTAINER_SELECTOR), 500); } void assertFloatingToolbarIsDisplayed() { waitForFloatingToolbarPopup(); - assertThat(mDevice.hasObject(By.desc(FLOATING_TOOLBAR_TAG))).isTrue(); + assertThat(mDevice.hasObject(TOOLBAR_CONTAINER_SELECTOR)).isTrue(); } void assertFloatingToolbarContainsItem(String itemLabel) { waitForFloatingToolbarPopup(); assertWithMessage("Expected to find item labelled [" + itemLabel + "]") .that(mDevice.hasObject( - By.desc(FLOATING_TOOLBAR_TAG).hasDescendant(By.text(itemLabel)))) + TOOLBAR_CONTAINER_SELECTOR.hasDescendant(By.text(itemLabel)))) .isTrue(); } @@ -59,14 +60,14 @@ final class FloatingToolbarUtils { waitForFloatingToolbarPopup(); assertWithMessage("Expected to not find item labelled [" + itemLabel + "]") .that(mDevice.hasObject( - By.desc(FLOATING_TOOLBAR_TAG).hasDescendant(By.text(itemLabel)))) + TOOLBAR_CONTAINER_SELECTOR.hasDescendant(By.text(itemLabel)))) .isFalse(); } void assertFloatingToolbarContainsItemAtIndex(String itemLabel, int index) { waitForFloatingToolbarPopup(); assertWithMessage("Expected to find item labelled [" + itemLabel + "] at index " + index) - .that(mDevice.findObject(By.desc(FLOATING_TOOLBAR_TAG)) + .that(mDevice.findObject(TOOLBAR_CONTAINER_SELECTOR) .findObjects(By.clickable(true)) .get(index) .getChildren() @@ -77,7 +78,7 @@ final class FloatingToolbarUtils { void clickFloatingToolbarItem(String label) { waitForFloatingToolbarPopup(); - mDevice.findObject(By.desc(FLOATING_TOOLBAR_TAG)) + mDevice.findObject(TOOLBAR_CONTAINER_SELECTOR) .findObject(By.text(label)) .click(); } @@ -85,13 +86,13 @@ final class FloatingToolbarUtils { void clickFloatingToolbarOverflowItem(String label) { // TODO: There might be a benefit to combining this with "clickFloatingToolbarItem" method. waitForFloatingToolbarPopup(); - mDevice.findObject(By.desc(FLOATING_TOOLBAR_TAG)) + mDevice.findObject(TOOLBAR_CONTAINER_SELECTOR) .findObject(By.desc(str(R.string.floating_toolbar_open_overflow_description))) .click(); mDevice.wait( - Until.findObject(By.desc(FLOATING_TOOLBAR_TAG).hasDescendant(By.text(label))), + Until.findObject(TOOLBAR_CONTAINER_SELECTOR.hasDescendant(By.text(label))), 1000); - mDevice.findObject(By.desc(FLOATING_TOOLBAR_TAG)) + mDevice.findObject(TOOLBAR_CONTAINER_SELECTOR) .findObject(By.text(label)) .click(); } diff --git a/core/tests/coretests/src/android/window/BackNavigationTest.java b/core/tests/coretests/src/android/window/BackNavigationTest.java index bbbc4230903a..77d61d589015 100644 --- a/core/tests/coretests/src/android/window/BackNavigationTest.java +++ b/core/tests/coretests/src/android/window/BackNavigationTest.java @@ -92,7 +92,7 @@ public class BackNavigationTest { try { mInstrumentation.getUiAutomation().waitForIdle(500, 1000); BackNavigationInfo info = ActivityTaskManager.getService() - .startBackNavigation(true, null); + .startBackNavigation(true, null, null); assertNotNull("BackNavigationInfo is null", info); assertNotNull("OnBackInvokedCallback is null", info.getOnBackInvokedCallback()); info.getOnBackInvokedCallback().onBackInvoked(); diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java index 47f70ddf2d42..ad72d49d2d6d 100644 --- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java +++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java @@ -299,8 +299,8 @@ public class ActivityThreadClientTest { private void pauseActivity(ActivityClientRecord r) { mThread.handlePauseActivity(r, false /* finished */, - false /* userLeaving */, 0 /* configChanges */, null /* pendingActions */, - "test"); + false /* userLeaving */, 0 /* configChanges */, false /* autoEnteringPip */, + null /* pendingActions */, "test"); } private void stopActivity(ActivityClientRecord r) { diff --git a/data/etc/platform.xml b/data/etc/platform.xml index 6897c01844a8..9a1b8a90dbfd 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -171,10 +171,11 @@ <assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="audioserver" /> <assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="audioserver" /> <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="audioserver" /> - <assign-permission name="android.permission.INTERACT_ACROSS_USERS" uid="audioserver" /> + <assign-permission name="android.permission.INTERACT_ACROSS_USERS_FULL" uid="audioserver" /> <assign-permission name="android.permission.OBSERVE_SENSOR_PRIVACY" uid="audioserver" /> <assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="cameraserver" /> + <assign-permission name="android.permission.INTERACT_ACROSS_USERS_FULL" uid="cameraserver" /> <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="cameraserver" /> <assign-permission name="android.permission.WAKE_LOCK" uid="cameraserver" /> <assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="cameraserver" /> diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index e8873cd4a3e7..8e244f56828f 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -2041,6 +2041,12 @@ "group": "WM_DEBUG_CONFIGURATION", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "-108248992": { + "message": "Defer transition ready for TaskFragmentTransaction=%s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java" + }, "-106400104": { "message": "Preload recents with %s", "level": "DEBUG", @@ -2089,6 +2095,12 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/TaskFragment.java" }, + "-79016993": { + "message": "Continue transition ready for TaskFragmentTransaction=%s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java" + }, "-70719599": { "message": "Unregister remote animations for organizer=%s uid=%d pid=%d", "level": "VERBOSE", @@ -2635,6 +2647,12 @@ "group": "WM_DEBUG_ANIM", "at": "com\/android\/server\/wm\/WindowContainer.java" }, + "390947100": { + "message": "Screenshotting %s [%s]", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "397382873": { "message": "Moving to PAUSED: %s %s", "level": "VERBOSE", @@ -4159,6 +4177,12 @@ "group": "WM_DEBUG_FOCUS_LIGHT", "at": "com\/android\/server\/wm\/InputMonitor.java" }, + "2004282287": { + "message": "Override sync-method for %s because seamless rotating", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "2010476671": { "message": "Animation done in %s: reportedVisible=%b okToDisplay=%b okToAnimate=%b startingDisplayed=%b", "level": "VERBOSE", diff --git a/data/keyboards/Vendor_054c_Product_0ce6.kl b/data/keyboards/Vendor_054c_Product_0ce6.kl index 4d51a9ecdce2..411dd9521921 100644 --- a/data/keyboards/Vendor_054c_Product_0ce6.kl +++ b/data/keyboards/Vendor_054c_Product_0ce6.kl @@ -16,6 +16,8 @@ # Sony Playstation(R) DualSense Controller # +# Only use this key layout if we have HID_PLAYSTATION! +requires_kernel_config CONFIG_HID_PLAYSTATION # Mapping according to https://developer.android.com/training/game-controllers/controller-input.html diff --git a/data/keyboards/Vendor_054c_Product_0ce6_fallback.kl b/data/keyboards/Vendor_054c_Product_0ce6_fallback.kl new file mode 100644 index 000000000000..d1a364ce8c86 --- /dev/null +++ b/data/keyboards/Vendor_054c_Product_0ce6_fallback.kl @@ -0,0 +1,75 @@ +# Copyright (C) 2022 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Sony Playstation(R) DualSense Controller +# + +# Use this if HID_PLAYSTATION is not available + +# Mapping according to https://developer.android.com/training/game-controllers/controller-input.html + +# Square +key 304 BUTTON_X +# Cross +key 305 BUTTON_A +# Circle +key 306 BUTTON_B +# Triangle +key 307 BUTTON_Y + +key 308 BUTTON_L1 +key 309 BUTTON_R1 +key 310 BUTTON_L2 +key 311 BUTTON_R2 + +# L2 axis +axis 0x03 LTRIGGER +# R2 axis +axis 0x04 RTRIGGER + +# Left Analog Stick +axis 0x00 X +axis 0x01 Y +# Right Analog Stick +axis 0x02 Z +axis 0x05 RZ + +# Left stick click +key 314 BUTTON_THUMBL +# Right stick click +key 315 BUTTON_THUMBR + +# Hat +axis 0x10 HAT_X +axis 0x11 HAT_Y + +# Mapping according to https://www.kernel.org/doc/Documentation/input/gamepad.txt +# Share / "half-sun" +key 312 BUTTON_SELECT +# Options / three horizontal lines +key 313 BUTTON_START +# PS key +key 316 BUTTON_MODE + +# Touchpad press +key 317 BUTTON_1 + +# SENSORs +sensor 0x00 ACCELEROMETER X +sensor 0x01 ACCELEROMETER Y +sensor 0x02 ACCELEROMETER Z +sensor 0x03 GYROSCOPE X +sensor 0x04 GYROSCOPE Y +sensor 0x05 GYROSCOPE Z diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java index bdf703c9bd38..7e9c4189dabb 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java @@ -20,11 +20,14 @@ import android.app.ActivityThread; import android.content.Context; import androidx.annotation.NonNull; +import androidx.window.extensions.area.WindowAreaComponent; +import androidx.window.extensions.area.WindowAreaComponentImpl; import androidx.window.extensions.embedding.ActivityEmbeddingComponent; import androidx.window.extensions.embedding.SplitController; import androidx.window.extensions.layout.WindowLayoutComponent; import androidx.window.extensions.layout.WindowLayoutComponentImpl; + /** * The reference implementation of {@link WindowExtensions} that implements the initial API version. */ @@ -33,10 +36,12 @@ public class WindowExtensionsImpl implements WindowExtensions { private final Object mLock = new Object(); private volatile WindowLayoutComponent mWindowLayoutComponent; private volatile SplitController mSplitController; + private volatile WindowAreaComponent mWindowAreaComponent; + // TODO(b/241126279) Introduce constants to better version functionality @Override public int getVendorApiLevel() { - return 1; + return 2; } /** @@ -75,4 +80,23 @@ public class WindowExtensionsImpl implements WindowExtensions { } return mSplitController; } + + /** + * Returns a reference implementation of {@link WindowAreaComponent} if available, + * {@code null} otherwise. The implementation must match the API level reported in + * {@link WindowExtensions#getWindowAreaComponent()}. + * @return {@link WindowAreaComponent} OEM implementation. + */ + public WindowAreaComponent getWindowAreaComponent() { + if (mWindowAreaComponent == null) { + synchronized (mLock) { + if (mWindowAreaComponent == null) { + Context context = ActivityThread.currentApplication(); + mWindowAreaComponent = + new WindowAreaComponentImpl(context); + } + } + } + return mWindowAreaComponent; + } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java new file mode 100644 index 000000000000..3adae7006369 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions.area; + +import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE; + +import android.app.Activity; +import android.content.Context; +import android.hardware.devicestate.DeviceStateManager; +import android.hardware.devicestate.DeviceStateRequest; +import android.util.ArraySet; + +import androidx.annotation.NonNull; + +import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; + +import java.util.concurrent.Executor; +import java.util.function.Consumer; + +/** + * Reference implementation of androidx.window.extensions.area OEM interface for use with + * WindowManager Jetpack. + * + * This component currently supports Rear Display mode with the ability to add and remove + * status listeners for this mode. + * + * The public methods in this class are thread-safe. + **/ +public class WindowAreaComponentImpl implements WindowAreaComponent, + DeviceStateManager.DeviceStateCallback { + + private final Object mLock = new Object(); + + private final DeviceStateManager mDeviceStateManager; + private final Executor mExecutor; + + @GuardedBy("mLock") + private final ArraySet<Consumer<Integer>> mRearDisplayStatusListeners = new ArraySet<>(); + private final int mRearDisplayState; + @WindowAreaSessionState + private int mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_INACTIVE; + + @GuardedBy("mLock") + private int mCurrentDeviceState = INVALID_DEVICE_STATE; + @GuardedBy("mLock") + private int mCurrentDeviceBaseState = INVALID_DEVICE_STATE; + @GuardedBy("mLock") + private DeviceStateRequest mDeviceStateRequest; + + public WindowAreaComponentImpl(@NonNull Context context) { + mDeviceStateManager = context.getSystemService(DeviceStateManager.class); + mExecutor = context.getMainExecutor(); + + // TODO(b/236022708) Move rear display state to device state config file + mRearDisplayState = context.getResources().getInteger( + R.integer.config_deviceStateRearDisplay); + + mDeviceStateManager.registerCallback(mExecutor, this); + } + + /** + * Adds a listener interested in receiving updates on the RearDisplayStatus + * of the device. Because this is being called from the OEM provided + * extensions, we will post the result of the listener on the executor + * provided by the developer at the initial call site. + * + * Depending on the initial state of the device, we will return either + * {@link WindowAreaComponent#STATUS_AVAILABLE} or + * {@link WindowAreaComponent#STATUS_UNAVAILABLE} if the feature is supported or not in that + * state respectively. When the rear display feature is triggered, we update the status to be + * {@link WindowAreaComponent#STATUS_UNAVAILABLE}. TODO(b/240727590) Prefix with AREA_ + * + * TODO(b/239833099) Add a STATUS_ACTIVE option to let apps know if a feature is currently + * enabled. + * + * @param consumer {@link Consumer} interested in receiving updates to the status of + * rear display mode. + */ + public void addRearDisplayStatusListener( + @NonNull Consumer<@WindowAreaStatus Integer> consumer) { + synchronized (mLock) { + mRearDisplayStatusListeners.add(consumer); + + // If current device state is still invalid, we haven't gotten our initial value yet + if (mCurrentDeviceState == INVALID_DEVICE_STATE) { + return; + } + consumer.accept(getCurrentStatus()); + } + } + + /** + * Removes a listener no longer interested in receiving updates. + * @param consumer no longer interested in receiving updates to RearDisplayStatus + */ + public void removeRearDisplayStatusListener( + @NonNull Consumer<@WindowAreaStatus Integer> consumer) { + synchronized (mLock) { + mRearDisplayStatusListeners.remove(consumer); + } + } + + /** + * Creates and starts a rear display session and provides updates to the + * callback provided. Because this is being called from the OEM provided + * extensions, we will post the result of the listener on the executor + * provided by the developer at the initial call site. + * + * When we enable rear display mode, we submit a request to {@link DeviceStateManager} + * to override the device state to the state that corresponds to RearDisplay + * mode. When the {@link DeviceStateRequest} is activated, we let the + * consumer know that the session is active by sending + * {@link WindowAreaComponent#SESSION_STATE_ACTIVE}. + * + * @param activity to provide updates to the client on + * the status of the Session + * @param rearDisplaySessionCallback to provide updates to the client on + * the status of the Session + */ + public void startRearDisplaySession(@NonNull Activity activity, + @NonNull Consumer<@WindowAreaSessionState Integer> rearDisplaySessionCallback) { + synchronized (mLock) { + if (mDeviceStateRequest != null) { + // Rear display session is already active + throw new IllegalStateException( + "Unable to start new rear display session as one is already active"); + } + mDeviceStateRequest = DeviceStateRequest.newBuilder(mRearDisplayState).build(); + mDeviceStateManager.requestState( + mDeviceStateRequest, + mExecutor, + new DeviceStateRequestCallbackAdapter(rearDisplaySessionCallback) + ); + } + } + + /** + * Ends the current rear display session and provides updates to the + * callback provided. Because this is being called from the OEM provided + * extensions, we will post the result of the listener on the executor + * provided by the developer. + */ + public void endRearDisplaySession() { + synchronized (mLock) { + if (mDeviceStateRequest != null || isRearDisplayActive()) { + mDeviceStateRequest = null; + mDeviceStateManager.cancelStateRequest(); + } else { + throw new IllegalStateException( + "Unable to cancel a rear display session as there is no active session"); + } + } + } + + @Override + public void onBaseStateChanged(int state) { + synchronized (mLock) { + mCurrentDeviceBaseState = state; + if (state == mCurrentDeviceState) { + updateStatusConsumers(getCurrentStatus()); + } + } + } + + @Override + public void onStateChanged(int state) { + synchronized (mLock) { + mCurrentDeviceState = state; + updateStatusConsumers(getCurrentStatus()); + } + } + + @GuardedBy("mLock") + private int getCurrentStatus() { + if (mRearDisplaySessionStatus == WindowAreaComponent.SESSION_STATE_ACTIVE + || isRearDisplayActive()) { + return WindowAreaComponent.STATUS_UNAVAILABLE; + } + return WindowAreaComponent.STATUS_AVAILABLE; + } + + /** + * Helper method to determine if a rear display session is currently active by checking + * if the current device configuration matches that of rear display. This would be true + * if there is a device override currently active (base state != current state) and the current + * state is that which corresponds to {@code mRearDisplayState} + * @return {@code true} if the device is in rear display mode and {@code false} if not + */ + @GuardedBy("mLock") + private boolean isRearDisplayActive() { + return (mCurrentDeviceState != mCurrentDeviceBaseState) && (mCurrentDeviceState + == mRearDisplayState); + } + + @GuardedBy("mLock") + private void updateStatusConsumers(@WindowAreaStatus int windowAreaStatus) { + synchronized (mLock) { + for (int i = 0; i < mRearDisplayStatusListeners.size(); i++) { + mRearDisplayStatusListeners.valueAt(i).accept(windowAreaStatus); + } + } + } + + /** + * Callback for the {@link DeviceStateRequest} to be notified of when the request has been + * activated or cancelled. This callback provides information to the client library + * on the status of the RearDisplay session through {@code mRearDisplaySessionCallback} + */ + private class DeviceStateRequestCallbackAdapter implements DeviceStateRequest.Callback { + + private final Consumer<Integer> mRearDisplaySessionCallback; + + DeviceStateRequestCallbackAdapter(@NonNull Consumer<Integer> callback) { + mRearDisplaySessionCallback = callback; + } + + @Override + public void onRequestActivated(@NonNull DeviceStateRequest request) { + synchronized (mLock) { + if (request.equals(mDeviceStateRequest)) { + mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_ACTIVE; + mRearDisplaySessionCallback.accept(mRearDisplaySessionStatus); + updateStatusConsumers(getCurrentStatus()); + } + } + } + + @Override + public void onRequestCanceled(DeviceStateRequest request) { + synchronized (mLock) { + if (request.equals(mDeviceStateRequest)) { + mDeviceStateRequest = null; + } + mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_INACTIVE; + mRearDisplaySessionCallback.accept(mRearDisplaySessionStatus); + updateStatusConsumers(getCurrentStatus()); + } + } + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java index 9fb7d1988772..1335e5ea051f 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java @@ -271,56 +271,49 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { } @Override - public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); + public void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct, + @NonNull TaskFragmentInfo taskFragmentInfo) { final IBinder fragmentToken = taskFragmentInfo.getFragmentToken(); mFragmentInfos.put(fragmentToken, taskFragmentInfo); mCallback.onTaskFragmentAppeared(wct, taskFragmentInfo); - applyTransaction(wct); } @Override - public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); + public void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct, + @NonNull TaskFragmentInfo taskFragmentInfo) { final IBinder fragmentToken = taskFragmentInfo.getFragmentToken(); mFragmentInfos.put(fragmentToken, taskFragmentInfo); mCallback.onTaskFragmentInfoChanged(wct, taskFragmentInfo); - applyTransaction(wct); } @Override - public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); + public void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct, + @NonNull TaskFragmentInfo taskFragmentInfo) { mFragmentInfos.remove(taskFragmentInfo.getFragmentToken()); mCallback.onTaskFragmentVanished(wct, taskFragmentInfo); - applyTransaction(wct); } @Override - public void onTaskFragmentParentInfoChanged(int taskId, @NonNull Configuration parentConfig) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); + public void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct, + int taskId, @NonNull Configuration parentConfig) { mCallback.onTaskFragmentParentInfoChanged(wct, taskId, parentConfig); - applyTransaction(wct); } @Override - public void onActivityReparentedToTask(int taskId, @NonNull Intent activityIntent, - @NonNull IBinder activityToken) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); + public void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct, + int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken) { mCallback.onActivityReparentedToTask(wct, taskId, activityIntent, activityToken); - applyTransaction(wct); } @Override - public void onTaskFragmentError(@NonNull IBinder errorCallbackToken, + public void onTaskFragmentError(@NonNull WindowContainerTransaction wct, + @NonNull IBinder errorCallbackToken, @Nullable TaskFragmentInfo taskFragmentInfo, int opType, @NonNull Throwable exception) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); if (taskFragmentInfo != null) { final IBinder fragmentToken = taskFragmentInfo.getFragmentToken(); mFragmentInfos.put(fragmentToken, taskFragmentInfo); } mCallback.onTaskFragmentError(wct, taskFragmentInfo, opType); - applyTransaction(wct); } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java index 45e2cbe07a56..97d42391b6c4 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java @@ -26,6 +26,7 @@ import android.graphics.Rect; import android.os.Handler; import android.provider.Settings; import android.view.RemoteAnimationTarget; +import android.view.WindowManager; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.AnimationSet; @@ -68,10 +69,7 @@ class TaskFragmentAnimationSpec { // The transition animation should be adjusted based on the developer option. final ContentResolver resolver = mContext.getContentResolver(); - mTransitionAnimationScaleSetting = Settings.Global.getFloat(resolver, - Settings.Global.TRANSITION_ANIMATION_SCALE, - mContext.getResources().getFloat( - R.dimen.config_appTransitionAnimationDurationScaleDefault)); + mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting(); resolver.registerContentObserver( Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE), false, new SettingsObserver(handler)); @@ -223,6 +221,12 @@ class TaskFragmentAnimationSpec { return animation; } + private float getTransitionAnimationScaleSetting() { + return WindowManager.fixScale(Settings.Global.getFloat(mContext.getContentResolver(), + Settings.Global.TRANSITION_ANIMATION_SCALE, mContext.getResources().getFloat( + R.dimen.config_appTransitionAnimationDurationScaleDefault))); + } + private class SettingsObserver extends ContentObserver { SettingsObserver(@NonNull Handler handler) { super(handler); @@ -230,9 +234,7 @@ class TaskFragmentAnimationSpec { @Override public void onChange(boolean selfChange) { - mTransitionAnimationScaleSetting = Settings.Global.getFloat( - mContext.getContentResolver(), Settings.Global.TRANSITION_ANIMATION_SCALE, - mTransitionAnimationScaleSetting); + mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting(); } } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java index 6bfb16a3c22d..f24401f0cd53 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java @@ -20,21 +20,26 @@ import static android.view.Display.DEFAULT_DISPLAY; import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_FLAT; import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_HALF_OPENED; +import static androidx.window.util.ExtensionHelper.isZero; import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation; import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect; -import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityClient; import android.app.Application; import android.app.WindowConfiguration; +import android.content.ComponentCallbacks; import android.content.Context; +import android.content.res.Configuration; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; import android.util.ArrayMap; +import android.window.WindowContext; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.UiContext; import androidx.window.common.CommonFoldingFeature; import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; import androidx.window.common.EmptyLifecycleCallbacksAdapter; @@ -58,11 +63,14 @@ import java.util.function.Consumer; public class WindowLayoutComponentImpl implements WindowLayoutComponent { private static final String TAG = "SampleExtension"; - private final Map<Activity, Consumer<WindowLayoutInfo>> mWindowLayoutChangeListeners = + private final Map<Context, Consumer<WindowLayoutInfo>> mWindowLayoutChangeListeners = new ArrayMap<>(); private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer; + private final Map<IBinder, WindowContextConfigListener> mWindowContextConfigListeners = + new ArrayMap<>(); + public WindowLayoutComponentImpl(@NonNull Context context) { ((Application) context.getApplicationContext()) .registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged()); @@ -78,14 +86,42 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { * @param activity hosting a {@link android.view.Window} * @param consumer interested in receiving updates to {@link WindowLayoutInfo} */ + @Override public void addWindowLayoutInfoListener(@NonNull Activity activity, @NonNull Consumer<WindowLayoutInfo> consumer) { + addWindowLayoutInfoListener((Context) activity, consumer); + } + + /** + * Similar to {@link #addWindowLayoutInfoListener(Activity, Consumer)}, but takes a UI Context + * as a parameter. + */ + // TODO(b/204073440): Add @Override to hook the API in WM extensions library. + public void addWindowLayoutInfoListener(@NonNull @UiContext Context context, + @NonNull Consumer<WindowLayoutInfo> consumer) { + if (mWindowLayoutChangeListeners.containsKey(context) + || mWindowLayoutChangeListeners.containsValue(consumer)) { + // Early return if the listener or consumer has been registered. + return; + } + if (!context.isUiContext()) { + throw new IllegalArgumentException("Context must be a UI Context, which should be" + + " an Activity or a WindowContext"); + } mFoldingFeatureProducer.getData((features) -> { // Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer. - WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(activity, features); + WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(context, features); consumer.accept(newWindowLayout); }); - mWindowLayoutChangeListeners.put(activity, consumer); + mWindowLayoutChangeListeners.put(context, consumer); + + if (context instanceof WindowContext) { + final IBinder windowContextToken = context.getWindowContextToken(); + final WindowContextConfigListener listener = + new WindowContextConfigListener(windowContextToken); + context.registerComponentCallbacks(listener); + mWindowContextConfigListeners.put(windowContextToken, listener); + } } /** @@ -93,18 +129,30 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { * * @param consumer no longer interested in receiving updates to {@link WindowLayoutInfo} */ + @Override public void removeWindowLayoutInfoListener(@NonNull Consumer<WindowLayoutInfo> consumer) { + for (Context context : mWindowLayoutChangeListeners.keySet()) { + if (!mWindowLayoutChangeListeners.get(context).equals(consumer)) { + continue; + } + if (context instanceof WindowContext) { + final IBinder token = context.getWindowContextToken(); + context.unregisterComponentCallbacks(mWindowContextConfigListeners.get(token)); + mWindowContextConfigListeners.remove(token); + } + break; + } mWindowLayoutChangeListeners.values().remove(consumer); } @NonNull - Set<Activity> getActivitiesListeningForLayoutChanges() { + Set<Context> getContextsListeningForLayoutChanges() { return mWindowLayoutChangeListeners.keySet(); } private boolean isListeningForLayoutChanges(IBinder token) { - for (Activity activity: getActivitiesListeningForLayoutChanges()) { - if (token.equals(activity.getWindow().getAttributes().token)) { + for (Context context: getContextsListeningForLayoutChanges()) { + if (token.equals(Context.getToken(context))) { return true; } } @@ -138,10 +186,10 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { } private void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) { - for (Activity activity : getActivitiesListeningForLayoutChanges()) { + for (Context context : getContextsListeningForLayoutChanges()) { // Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer. - Consumer<WindowLayoutInfo> layoutConsumer = mWindowLayoutChangeListeners.get(activity); - WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(activity, storedFeatures); + Consumer<WindowLayoutInfo> layoutConsumer = mWindowLayoutChangeListeners.get(context); + WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(context, storedFeatures); layoutConsumer.accept(newWindowLayout); } } @@ -149,11 +197,12 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { /** * Translates the {@link DisplayFeature} into a {@link WindowLayoutInfo} when a * valid state is found. - * @param activity a proxy for the {@link android.view.Window} that contains the + * @param context a proxy for the {@link android.view.Window} that contains the + * {@link DisplayFeature}. */ - private WindowLayoutInfo getWindowLayoutInfo( - @NonNull Activity activity, List<CommonFoldingFeature> storedFeatures) { - List<DisplayFeature> displayFeatureList = getDisplayFeatures(activity, storedFeatures); + private WindowLayoutInfo getWindowLayoutInfo(@NonNull @UiContext Context context, + List<CommonFoldingFeature> storedFeatures) { + List<DisplayFeature> displayFeatureList = getDisplayFeatures(context, storedFeatures); return new WindowLayoutInfo(displayFeatureList); } @@ -170,18 +219,18 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { * bounds are not valid, constructing a {@link FoldingFeature} will throw an * {@link IllegalArgumentException} since this can cause negative UI effects down stream. * - * @param activity a proxy for the {@link android.view.Window} that contains the + * @param context a proxy for the {@link android.view.Window} that contains the * {@link DisplayFeature}. * are within the {@link android.view.Window} of the {@link Activity} */ private List<DisplayFeature> getDisplayFeatures( - @NonNull Activity activity, List<CommonFoldingFeature> storedFeatures) { + @NonNull @UiContext Context context, List<CommonFoldingFeature> storedFeatures) { List<DisplayFeature> features = new ArrayList<>(); - if (!shouldReportDisplayFeatures(activity)) { + if (!shouldReportDisplayFeatures(context)) { return features; } - int displayId = activity.getDisplay().getDisplayId(); + int displayId = context.getDisplay().getDisplayId(); for (CommonFoldingFeature baseFeature : storedFeatures) { Integer state = convertToExtensionState(baseFeature.getState()); if (state == null) { @@ -189,9 +238,9 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { } Rect featureRect = baseFeature.getRect(); rotateRectToDisplayRotation(displayId, featureRect); - transformToWindowSpaceRect(activity, featureRect); + transformToWindowSpaceRect(context, featureRect); - if (!isRectZero(featureRect)) { + if (!isZero(featureRect)) { // TODO(b/228641877): Remove guarding when fixed. features.add(new FoldingFeature(featureRect, baseFeature.getType(), state)); } @@ -203,15 +252,21 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { * Checks whether display features should be reported for the activity. * TODO(b/238948678): Support reporting display features in all windowing modes. */ - private boolean shouldReportDisplayFeatures(@NonNull Activity activity) { - int displayId = activity.getDisplay().getDisplayId(); + private boolean shouldReportDisplayFeatures(@NonNull @UiContext Context context) { + int displayId = context.getDisplay().getDisplayId(); if (displayId != DEFAULT_DISPLAY) { // Display features are not supported on secondary displays. return false; } - final int taskWindowingMode = ActivityClient.getInstance().getTaskWindowingMode( - activity.getActivityToken()); - if (taskWindowingMode == -1) { + final int windowingMode; + if (context instanceof Activity) { + windowingMode = ActivityClient.getInstance().getTaskWindowingMode( + context.getActivityToken()); + } else { + windowingMode = context.getResources().getConfiguration().windowConfiguration + .getWindowingMode(); + } + if (windowingMode == -1) { // If we cannot determine the task windowing mode for any reason, it is likely that we // won't be able to determine its position correctly as well. DisplayFeatures' bounds // in this case can't be computed correctly, so we should skip. @@ -219,36 +274,43 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { } // It is recommended not to report any display features in multi-window mode, since it // won't be possible to synchronize the display feature positions with window movement. - return !WindowConfiguration.inMultiWindowMode(taskWindowingMode); + return !WindowConfiguration.inMultiWindowMode(windowingMode); } - /** - * Returns {@link true} if a {@link Rect} has zero width and zero height, - * {@code false} otherwise. - */ - private boolean isRectZero(Rect rect) { - return rect.width() == 0 && rect.height() == 0; + private void onDisplayFeaturesChangedIfListening(@NonNull IBinder token) { + if (isListeningForLayoutChanges(token)) { + mFoldingFeatureProducer.getData( + WindowLayoutComponentImpl.this::onDisplayFeaturesChanged); + } } private final class NotifyOnConfigurationChanged extends EmptyLifecycleCallbacksAdapter { @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { super.onActivityCreated(activity, savedInstanceState); - onDisplayFeaturesChangedIfListening(activity); + onDisplayFeaturesChangedIfListening(activity.getActivityToken()); } @Override public void onActivityConfigurationChanged(Activity activity) { super.onActivityConfigurationChanged(activity); - onDisplayFeaturesChangedIfListening(activity); + onDisplayFeaturesChangedIfListening(activity.getActivityToken()); + } + } + + private final class WindowContextConfigListener implements ComponentCallbacks { + final IBinder mToken; + + WindowContextConfigListener(IBinder token) { + mToken = token; } - private void onDisplayFeaturesChangedIfListening(Activity activity) { - IBinder token = activity.getWindow().getAttributes().token; - if (token == null || isListeningForLayoutChanges(token)) { - mFoldingFeatureProducer.getData( - WindowLayoutComponentImpl.this::onDisplayFeaturesChanged); - } + @Override + public void onConfigurationChanged(@NonNull Configuration newConfig) { + onDisplayFeaturesChangedIfListening(mToken); } + + @Override + public void onLowMemory() {} } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java index 0da44ac36a6e..cbaa27712015 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java @@ -16,6 +16,7 @@ package androidx.window.util; +import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import java.util.LinkedHashSet; @@ -25,25 +26,45 @@ import java.util.function.Consumer; /** * Base class that provides the implementation for the callback mechanism of the - * {@link DataProducer} API. + * {@link DataProducer} API. This class is thread safe for adding, removing, and notifying + * consumers. * * @param <T> The type of data this producer returns through {@link DataProducer#getData}. */ public abstract class BaseDataProducer<T> implements DataProducer<T> { + + private final Object mLock = new Object(); + @GuardedBy("mLock") private final Set<Consumer<T>> mCallbacks = new LinkedHashSet<>(); + /** + * Adds a callback to the set of callbacks listening for data. Data is delivered through + * {@link BaseDataProducer#notifyDataChanged(Object)}. This method is thread safe. Callers + * should ensure that callbacks are thread safe. + * @param callback that will receive data from the producer. + */ @Override public final void addDataChangedCallback(@NonNull Consumer<T> callback) { - mCallbacks.add(callback); - Optional<T> currentData = getCurrentData(); - currentData.ifPresent(callback); - onListenersChanged(mCallbacks); + synchronized (mLock) { + mCallbacks.add(callback); + Optional<T> currentData = getCurrentData(); + currentData.ifPresent(callback); + onListenersChanged(mCallbacks); + } } + /** + * Removes a callback to the set of callbacks listening for data. This method is thread safe + * for adding. + * @param callback that was registered in + * {@link BaseDataProducer#addDataChangedCallback(Consumer)}. + */ @Override public final void removeDataChangedCallback(@NonNull Consumer<T> callback) { - mCallbacks.remove(callback); - onListenersChanged(mCallbacks); + synchronized (mLock) { + mCallbacks.remove(callback); + onListenersChanged(mCallbacks); + } } protected void onListenersChanged(Set<Consumer<T>> callbacks) {} @@ -56,11 +77,14 @@ public abstract class BaseDataProducer<T> implements DataProducer<T> { /** * Called to notify all registered consumers that the data provided - * by {@link DataProducer#getData} has changed. + * by {@link DataProducer#getData} has changed. Calls to this are thread save but callbacks need + * to ensure thread safety. */ protected void notifyDataChanged(T value) { - for (Consumer<T> callback : mCallbacks) { - callback.accept(value); + synchronized (mLock) { + for (Consumer<T> callback : mCallbacks) { + callback.accept(value); + } } } }
\ No newline at end of file diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java index 2a593f15a9de..31bf96313a95 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java @@ -21,14 +21,15 @@ import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; -import android.app.Activity; +import android.content.Context; import android.graphics.Rect; import android.hardware.display.DisplayManagerGlobal; import android.view.DisplayInfo; import android.view.Surface; +import android.view.WindowManager; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; +import androidx.annotation.UiContext; /** * Util class for both Sidecar and Extensions. @@ -86,12 +87,9 @@ public final class ExtensionHelper { } /** Transforms rectangle from absolute coordinate space to the window coordinate space. */ - public static void transformToWindowSpaceRect(Activity activity, Rect inOutRect) { - Rect windowRect = getWindowBounds(activity); - if (windowRect == null) { - inOutRect.setEmpty(); - return; - } + public static void transformToWindowSpaceRect(@NonNull @UiContext Context context, + Rect inOutRect) { + Rect windowRect = getWindowBounds(context); if (!Rect.intersects(inOutRect, windowRect)) { inOutRect.setEmpty(); return; @@ -103,9 +101,9 @@ public final class ExtensionHelper { /** * Gets the current window bounds in absolute coordinates. */ - @Nullable - private static Rect getWindowBounds(@NonNull Activity activity) { - return activity.getWindowManager().getCurrentWindowMetrics().getBounds(); + @NonNull + private static Rect getWindowBounds(@NonNull @UiContext Context context) { + return context.getSystemService(WindowManager.class).getCurrentWindowMetrics().getBounds(); } /** diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar Binary files differindex 918e514f4c89..e9a1721fba2a 100644 --- a/libs/WindowManager/Jetpack/window-extensions-release.aar +++ b/libs/WindowManager/Jetpack/window-extensions-release.aar diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java index 764e650a807c..b085b73d78ce 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java @@ -16,14 +16,20 @@ package com.android.wm.shell; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE; + +import android.app.WindowConfiguration; import android.util.SparseArray; import android.view.SurfaceControl; import android.window.DisplayAreaAppearedInfo; import android.window.DisplayAreaInfo; import android.window.DisplayAreaOrganizer; +import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; +import com.android.internal.protolog.common.ProtoLog; + import java.io.PrintWriter; import java.util.List; import java.util.concurrent.Executor; @@ -102,10 +108,44 @@ public class RootDisplayAreaOrganizer extends DisplayAreaOrganizer { mDisplayAreasInfo.put(displayId, displayAreaInfo); } + /** + * Create a {@link WindowContainerTransaction} to update display windowing mode. + * + * @param displayId display id to update windowing mode for + * @param windowingMode target {@link WindowConfiguration.WindowingMode} + * @return {@link WindowContainerTransaction} with pending operation to set windowing mode + */ + public WindowContainerTransaction prepareWindowingModeChange(int displayId, + @WindowConfiguration.WindowingMode int windowingMode) { + WindowContainerTransaction wct = new WindowContainerTransaction(); + DisplayAreaInfo displayAreaInfo = mDisplayAreasInfo.get(displayId); + if (displayAreaInfo == null) { + ProtoLog.e(WM_SHELL_DESKTOP_MODE, + "unable to update windowing mode for display %d display not found", displayId); + return wct; + } + + ProtoLog.d(WM_SHELL_DESKTOP_MODE, + "setWindowingMode: displayId=%d current wmMode=%d new wmMode=%d", displayId, + displayAreaInfo.configuration.windowConfiguration.getWindowingMode(), + windowingMode); + + wct.setWindowingMode(displayAreaInfo.token, windowingMode); + return wct; + } + public void dump(@NonNull PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; final String childPrefix = innerPrefix + " "; pw.println(prefix + this); + + for (int i = 0; i < mDisplayAreasInfo.size(); i++) { + int displayId = mDisplayAreasInfo.keyAt(i); + DisplayAreaInfo displayAreaInfo = mDisplayAreasInfo.get(displayId); + int windowingMode = + displayAreaInfo.configuration.windowConfiguration.getWindowingMode(); + pw.println(innerPrefix + "# displayId=" + displayId + " wmMode=" + windowingMode); + } } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index 6ae0f9ba0485..d5d4935f0529 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG; import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; @@ -46,6 +47,7 @@ import android.window.StartingWindowInfo; import android.window.StartingWindowRemovalInfo; import android.window.TaskAppearedInfo; import android.window.TaskOrganizer; +import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; @@ -690,6 +692,49 @@ public class ShellTaskOrganizer extends TaskOrganizer implements taskListener.reparentChildSurfaceToTask(taskId, sc, t); } + /** + * Create a {@link WindowContainerTransaction} to clear task bounds. + * + * @param displayId display id for tasks that will have bounds cleared + * @return {@link WindowContainerTransaction} with pending operations to clear bounds + */ + public WindowContainerTransaction prepareClearBoundsForTasks(int displayId) { + ProtoLog.d(WM_SHELL_DESKTOP_MODE, "prepareClearBoundsForTasks: displayId=%d", displayId); + WindowContainerTransaction wct = new WindowContainerTransaction(); + for (int i = 0; i < mTasks.size(); i++) { + RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo(); + if (taskInfo.displayId == displayId) { + ProtoLog.d(WM_SHELL_DESKTOP_MODE, "clearing bounds for token=%s taskInfo=%s", + taskInfo.token, taskInfo); + wct.setBounds(taskInfo.token, null); + } + } + return wct; + } + + /** + * Create a {@link WindowContainerTransaction} to clear task level freeform setting. + * + * @param displayId display id for tasks that will have windowing mode reset to {@link + * WindowConfiguration#WINDOWING_MODE_UNDEFINED} + * @return {@link WindowContainerTransaction} with pending operations to clear windowing mode + */ + public WindowContainerTransaction prepareClearFreeformForTasks(int displayId) { + ProtoLog.d(WM_SHELL_DESKTOP_MODE, "prepareClearFreeformForTasks: displayId=%d", displayId); + WindowContainerTransaction wct = new WindowContainerTransaction(); + for (int i = 0; i < mTasks.size(); i++) { + RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo(); + if (taskInfo.displayId == displayId + && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { + ProtoLog.d(WM_SHELL_DESKTOP_MODE, + "clearing windowing mode for token=%s taskInfo=%s", taskInfo.token, + taskInfo); + wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED); + } + } + return wct; + } + private void logSizeCompatRestartButtonEventReported(@NonNull TaskAppearedInfo info, int event) { ActivityInfo topActivityInfo = info.getTaskInfo().topActivityInfo; @@ -816,7 +861,14 @@ public class ShellTaskOrganizer extends TaskOrganizer implements final int key = mTasks.keyAt(i); final TaskAppearedInfo info = mTasks.valueAt(i); final TaskListener listener = getTaskListener(info.getTaskInfo()); - pw.println(innerPrefix + "#" + i + " task=" + key + " listener=" + listener); + final int windowingMode = info.getTaskInfo().getWindowingMode(); + String pkg = ""; + if (info.getTaskInfo().baseActivity != null) { + pkg = info.getTaskInfo().baseActivity.getPackageName(); + } + Rect bounds = info.getTaskInfo().getConfiguration().windowConfiguration.getBounds(); + pw.println(innerPrefix + "#" + i + " task=" + key + " listener=" + listener + + " wmMode=" + windowingMode + " pkg=" + pkg + " bounds=" + bounds); } pw.println(); @@ -826,6 +878,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements final TaskListener listener = mLaunchCookieToListener.valueAt(i); pw.println(innerPrefix + "#" + i + " cookie=" + key + " listener=" + listener); } + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java index d28a68a42b2b..a8764e05c3e2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java @@ -54,7 +54,10 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, /** Callback for listening task state. */ public interface Listener { - /** Called when the container is ready for launching activities. */ + /** + * Only called once when the surface has been created & the container is ready for + * launching activities. + */ default void onInitialized() {} /** Called when the container can no longer launch activities. */ @@ -80,12 +83,13 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, private final SyncTransactionQueue mSyncQueue; private final TaskViewTransitions mTaskViewTransitions; - private ActivityManager.RunningTaskInfo mTaskInfo; + protected ActivityManager.RunningTaskInfo mTaskInfo; private WindowContainerToken mTaskToken; private SurfaceControl mTaskLeash; private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); private boolean mSurfaceCreated; private boolean mIsInitialized; + private boolean mNotifiedForInitialized; private Listener mListener; private Executor mListenerExecutor; private Region mObscuredTouchRegion; @@ -110,6 +114,13 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, mGuard.open("release"); } + /** + * @return {@code True} when the TaskView's surface has been created, {@code False} otherwise. + */ + public boolean isInitialized() { + return mIsInitialized; + } + /** Until all users are converted, we may have mixed-use (eg. Car). */ private boolean isUsingShellTransitions() { return mTaskViewTransitions != null && Transitions.ENABLE_SHELL_TRANSITIONS; @@ -269,11 +280,17 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, resetTaskInfo(); }); mGuard.close(); - if (mListener != null && mIsInitialized) { + mIsInitialized = false; + notifyReleased(); + } + + /** Called when the {@link TaskView} has been released. */ + protected void notifyReleased() { + if (mListener != null && mNotifiedForInitialized) { mListenerExecutor.execute(() -> { mListener.onReleased(); }); - mIsInitialized = false; + mNotifiedForInitialized = false; } } @@ -407,12 +424,8 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, @Override public void surfaceCreated(SurfaceHolder holder) { mSurfaceCreated = true; - if (mListener != null && !mIsInitialized) { - mIsInitialized = true; - mListenerExecutor.execute(() -> { - mListener.onInitialized(); - }); - } + mIsInitialized = true; + notifyInitialized(); mShellExecutor.execute(() -> { if (mTaskToken == null) { // Nothing to update, task is not yet available @@ -430,6 +443,16 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, }); } + /** Called when the {@link TaskView} is initialized. */ + protected void notifyInitialized() { + if (mListener != null && !mNotifiedForInitialized) { + mNotifiedForInitialized = true; + mListenerExecutor.execute(() -> { + mListener.onInitialized(); + }); + } + } + @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { if (mTaskToken == null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java new file mode 100644 index 000000000000..cc4db933ec9f --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.activityembedding; + +import static android.graphics.Matrix.MSCALE_X; +import static android.graphics.Matrix.MTRANS_X; +import static android.graphics.Matrix.MTRANS_Y; + +import android.annotation.CallSuper; +import android.graphics.Point; +import android.graphics.Rect; +import android.view.Choreographer; +import android.view.SurfaceControl; +import android.view.animation.Animation; +import android.view.animation.Transformation; +import android.window.TransitionInfo; + +import androidx.annotation.NonNull; + +/** + * Wrapper to handle the ActivityEmbedding animation update in one + * {@link SurfaceControl.Transaction}. + */ +class ActivityEmbeddingAnimationAdapter { + + /** + * If {@link #mOverrideLayer} is set to this value, we don't want to override the surface layer. + */ + private static final int LAYER_NO_OVERRIDE = -1; + + final Animation mAnimation; + final TransitionInfo.Change mChange; + final SurfaceControl mLeash; + + final Transformation mTransformation = new Transformation(); + final float[] mMatrix = new float[9]; + final float[] mVecs = new float[4]; + final Rect mRect = new Rect(); + private boolean mIsFirstFrame = true; + private int mOverrideLayer = LAYER_NO_OVERRIDE; + + ActivityEmbeddingAnimationAdapter(@NonNull Animation animation, + @NonNull TransitionInfo.Change change) { + this(animation, change, change.getLeash()); + } + + /** + * @param leash the surface to animate, which is not necessary the same as + * {@link TransitionInfo.Change#getLeash()}, it can be a screenshot for example. + */ + ActivityEmbeddingAnimationAdapter(@NonNull Animation animation, + @NonNull TransitionInfo.Change change, @NonNull SurfaceControl leash) { + mAnimation = animation; + mChange = change; + mLeash = leash; + } + + /** + * Surface layer to be set at the first frame of the animation. We will not set the layer if it + * is set to {@link #LAYER_NO_OVERRIDE}. + */ + final void overrideLayer(int layer) { + mOverrideLayer = layer; + } + + /** Called on frame update. */ + final void onAnimationUpdate(@NonNull SurfaceControl.Transaction t, long currentPlayTime) { + if (mIsFirstFrame) { + t.show(mLeash); + if (mOverrideLayer != LAYER_NO_OVERRIDE) { + t.setLayer(mLeash, mOverrideLayer); + } + mIsFirstFrame = false; + } + + // Extract the transformation to the current time. + mAnimation.getTransformation(Math.min(currentPlayTime, mAnimation.getDuration()), + mTransformation); + t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId()); + onAnimationUpdateInner(t); + } + + /** To be overridden by subclasses to adjust the animation surface change. */ + void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) { + final Point offset = mChange.getEndRelOffset(); + mTransformation.getMatrix().postTranslate(offset.x, offset.y); + t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix); + t.setAlpha(mLeash, mTransformation.getAlpha()); + // Get current animation position. + final int positionX = Math.round(mMatrix[MTRANS_X]); + final int positionY = Math.round(mMatrix[MTRANS_Y]); + // The exiting surface starts at position: Change#getEndRelOffset() and moves with + // positionX varying. Offset our crop region by the amount we have slided so crop + // regions stays exactly on the original container in split. + final int cropOffsetX = offset.x - positionX; + final int cropOffsetY = offset.y - positionY; + final Rect cropRect = new Rect(); + cropRect.set(mChange.getEndAbsBounds()); + // Because window crop uses absolute position. + cropRect.offsetTo(0, 0); + cropRect.offset(cropOffsetX, cropOffsetY); + t.setCrop(mLeash, cropRect); + } + + /** Called after animation finished. */ + @CallSuper + void onAnimationEnd(@NonNull SurfaceControl.Transaction t) { + onAnimationUpdate(t, mAnimation.getDuration()); + } + + final long getDurationHint() { + return mAnimation.computeDurationHint(); + } + + /** + * Should be used when the {@link TransitionInfo.Change} is in split with others, and wants to + * animate together as one. This adapter will offset the animation leash to make the animate of + * two windows look like a single window. + */ + static class SplitAdapter extends ActivityEmbeddingAnimationAdapter { + private final boolean mIsLeftHalf; + private final int mWholeAnimationWidth; + + /** + * @param isLeftHalf whether this is the left half of the animation. + * @param wholeAnimationWidth the whole animation windows width. + */ + SplitAdapter(@NonNull Animation animation, @NonNull TransitionInfo.Change change, + boolean isLeftHalf, int wholeAnimationWidth) { + super(animation, change); + mIsLeftHalf = isLeftHalf; + mWholeAnimationWidth = wholeAnimationWidth; + if (wholeAnimationWidth == 0) { + throw new IllegalArgumentException("SplitAdapter must provide wholeAnimationWidth"); + } + } + + @Override + void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) { + final Point offset = mChange.getEndRelOffset(); + float posX = offset.x; + final float posY = offset.y; + // This window is half of the whole animation window. Offset left/right to make it + // look as one with the other half. + mTransformation.getMatrix().getValues(mMatrix); + final int changeWidth = mChange.getEndAbsBounds().width(); + final float scaleX = mMatrix[MSCALE_X]; + final float totalOffset = mWholeAnimationWidth * (1 - scaleX) / 2; + final float curOffset = changeWidth * (1 - scaleX) / 2; + final float offsetDiff = totalOffset - curOffset; + if (mIsLeftHalf) { + posX += offsetDiff; + } else { + posX -= offsetDiff; + } + mTransformation.getMatrix().postTranslate(posX, posY); + t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix); + t.setAlpha(mLeash, mTransformation.getAlpha()); + } + } + + /** + * Should be used for the animation of the snapshot of a {@link TransitionInfo.Change} that has + * size change. + */ + static class SnapshotAdapter extends ActivityEmbeddingAnimationAdapter { + + SnapshotAdapter(@NonNull Animation animation, @NonNull TransitionInfo.Change change, + @NonNull SurfaceControl snapshotLeash) { + super(animation, change, snapshotLeash); + } + + @Override + void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) { + // Snapshot should always be placed at the top left of the animation leash. + mTransformation.getMatrix().postTranslate(0, 0); + t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix); + t.setAlpha(mLeash, mTransformation.getAlpha()); + } + + @Override + void onAnimationEnd(@NonNull SurfaceControl.Transaction t) { + super.onAnimationEnd(t); + // Remove the screenshot leash after animation is finished. + t.remove(mLeash); + } + } + + /** + * Should be used for the animation of the {@link TransitionInfo.Change} that has size change. + */ + static class BoundsChangeAdapter extends ActivityEmbeddingAnimationAdapter { + + BoundsChangeAdapter(@NonNull Animation animation, @NonNull TransitionInfo.Change change) { + super(animation, change); + } + + @Override + void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) { + final Point offset = mChange.getEndRelOffset(); + mTransformation.getMatrix().postTranslate(offset.x, offset.y); + t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix); + t.setAlpha(mLeash, mTransformation.getAlpha()); + + // The following applies an inverse scale to the clip-rect so that it crops "after" the + // scale instead of before. + mVecs[1] = mVecs[2] = 0; + mVecs[0] = mVecs[3] = 1; + mTransformation.getMatrix().mapVectors(mVecs); + mVecs[0] = 1.f / mVecs[0]; + mVecs[3] = 1.f / mVecs[3]; + final Rect clipRect = mTransformation.getClipRect(); + mRect.left = (int) (clipRect.left * mVecs[0] + 0.5f); + mRect.right = (int) (clipRect.right * mVecs[0] + 0.5f); + mRect.top = (int) (clipRect.top * mVecs[3] + 0.5f); + mRect.bottom = (int) (clipRect.bottom * mVecs[3] + 0.5f); + t.setCrop(mLeash, mRect); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java new file mode 100644 index 000000000000..7e0795d11153 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.activityembedding; + +import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET; + +import android.animation.Animator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.IBinder; +import android.util.Log; +import android.view.SurfaceControl; +import android.view.animation.Animation; +import android.window.TransitionInfo; +import android.window.WindowContainerToken; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.wm.shell.common.ScreenshotUtils; +import com.android.wm.shell.transition.Transitions; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; + +/** To run the ActivityEmbedding animations. */ +class ActivityEmbeddingAnimationRunner { + + private static final String TAG = "ActivityEmbeddingAnimR"; + + private final ActivityEmbeddingController mController; + @VisibleForTesting + final ActivityEmbeddingAnimationSpec mAnimationSpec; + + ActivityEmbeddingAnimationRunner(@NonNull Context context, + @NonNull ActivityEmbeddingController controller) { + mController = controller; + mAnimationSpec = new ActivityEmbeddingAnimationSpec(context); + } + + /** Creates and starts animation for ActivityEmbedding transition. */ + void startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction) { + final Animator animator = createAnimator(info, startTransaction, finishTransaction, + () -> mController.onAnimationFinished(transition)); + startTransaction.apply(); + animator.start(); + } + + /** + * Sets transition animation scale settings value. + * @param scale The setting value of transition animation scale. + */ + void setAnimScaleSetting(float scale) { + mAnimationSpec.setAnimScaleSetting(scale); + } + + /** Creates the animator for the given {@link TransitionInfo}. */ + @VisibleForTesting + @NonNull + Animator createAnimator(@NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Runnable animationFinishCallback) { + final List<ActivityEmbeddingAnimationAdapter> adapters = + createAnimationAdapters(info, startTransaction); + long duration = 0; + for (ActivityEmbeddingAnimationAdapter adapter : adapters) { + duration = Math.max(duration, adapter.getDurationHint()); + } + final ValueAnimator animator = ValueAnimator.ofFloat(0, 1); + animator.setDuration(duration); + animator.addUpdateListener((anim) -> { + // Update all adapters in the same transaction. + final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + for (ActivityEmbeddingAnimationAdapter adapter : adapters) { + adapter.onAnimationUpdate(t, animator.getCurrentPlayTime()); + } + t.apply(); + }); + animator.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) {} + + @Override + public void onAnimationEnd(Animator animation) { + final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + for (ActivityEmbeddingAnimationAdapter adapter : adapters) { + adapter.onAnimationEnd(t); + } + t.apply(); + animationFinishCallback.run(); + } + + @Override + public void onAnimationCancel(Animator animation) {} + + @Override + public void onAnimationRepeat(Animator animation) {} + }); + return animator; + } + + /** + * Creates list of {@link ActivityEmbeddingAnimationAdapter} to handle animations on all window + * changes. + */ + @NonNull + private List<ActivityEmbeddingAnimationAdapter> createAnimationAdapters( + @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) { + for (TransitionInfo.Change change : info.getChanges()) { + if (change.getMode() == TRANSIT_CHANGE + && !change.getStartAbsBounds().equals(change.getEndAbsBounds())) { + return createChangeAnimationAdapters(info, startTransaction); + } + } + if (Transitions.isClosingType(info.getType())) { + return createCloseAnimationAdapters(info); + } + return createOpenAnimationAdapters(info); + } + + @NonNull + private List<ActivityEmbeddingAnimationAdapter> createOpenAnimationAdapters( + @NonNull TransitionInfo info) { + return createOpenCloseAnimationAdapters(info, true /* isOpening */, + mAnimationSpec::loadOpenAnimation); + } + + @NonNull + private List<ActivityEmbeddingAnimationAdapter> createCloseAnimationAdapters( + @NonNull TransitionInfo info) { + return createOpenCloseAnimationAdapters(info, false /* isOpening */, + mAnimationSpec::loadCloseAnimation); + } + + /** + * Creates {@link ActivityEmbeddingAnimationAdapter} for OPEN and CLOSE types of transition. + * @param isOpening {@code true} for OPEN type, {@code false} for CLOSE type. + */ + @NonNull + private List<ActivityEmbeddingAnimationAdapter> createOpenCloseAnimationAdapters( + @NonNull TransitionInfo info, boolean isOpening, + @NonNull BiFunction<TransitionInfo.Change, Rect, Animation> animationProvider) { + // We need to know if the change window is only a partial of the whole animation screen. + // If so, we will need to adjust it to make the whole animation screen looks like one. + final List<TransitionInfo.Change> openingChanges = new ArrayList<>(); + final List<TransitionInfo.Change> closingChanges = new ArrayList<>(); + final Rect openingWholeScreenBounds = new Rect(); + final Rect closingWholeScreenBounds = new Rect(); + for (TransitionInfo.Change change : info.getChanges()) { + final Rect bounds = new Rect(change.getEndAbsBounds()); + final Point offset = change.getEndRelOffset(); + bounds.offsetTo(offset.x, offset.y); + if (Transitions.isOpeningType(change.getMode())) { + openingChanges.add(change); + openingWholeScreenBounds.union(bounds); + } else { + closingChanges.add(change); + closingWholeScreenBounds.union(bounds); + } + } + + // For OPEN transition, open windows should be above close windows. + // For CLOSE transition, open windows should be below close windows. + int offsetLayer = TYPE_LAYER_OFFSET; + final List<ActivityEmbeddingAnimationAdapter> adapters = new ArrayList<>(); + for (TransitionInfo.Change change : openingChanges) { + final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter( + change, animationProvider, openingWholeScreenBounds); + if (isOpening) { + adapter.overrideLayer(offsetLayer++); + } + adapters.add(adapter); + } + for (TransitionInfo.Change change : closingChanges) { + final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter( + change, animationProvider, closingWholeScreenBounds); + if (!isOpening) { + adapter.overrideLayer(offsetLayer++); + } + adapters.add(adapter); + } + return adapters; + } + + @NonNull + private ActivityEmbeddingAnimationAdapter createOpenCloseAnimationAdapter( + @NonNull TransitionInfo.Change change, + @NonNull BiFunction<TransitionInfo.Change, Rect, Animation> animationProvider, + @NonNull Rect wholeAnimationBounds) { + final Animation animation = animationProvider.apply(change, wholeAnimationBounds); + final Rect bounds = new Rect(change.getEndAbsBounds()); + final Point offset = change.getEndRelOffset(); + bounds.offsetTo(offset.x, offset.y); + if (bounds.left == wholeAnimationBounds.left + && bounds.right != wholeAnimationBounds.right) { + // This is the left split of the whole animation window. + return new ActivityEmbeddingAnimationAdapter.SplitAdapter(animation, change, + true /* isLeftHalf */, wholeAnimationBounds.width()); + } else if (bounds.left != wholeAnimationBounds.left + && bounds.right == wholeAnimationBounds.right) { + // This is the right split of the whole animation window. + return new ActivityEmbeddingAnimationAdapter.SplitAdapter(animation, change, + false /* isLeftHalf */, wholeAnimationBounds.width()); + } + // Open/close window that fills the whole animation. + return new ActivityEmbeddingAnimationAdapter(animation, change); + } + + @NonNull + private List<ActivityEmbeddingAnimationAdapter> createChangeAnimationAdapters( + @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) { + final List<ActivityEmbeddingAnimationAdapter> adapters = new ArrayList<>(); + for (TransitionInfo.Change change : info.getChanges()) { + if (change.getMode() == TRANSIT_CHANGE + && !change.getStartAbsBounds().equals(change.getEndAbsBounds())) { + // This is the window with bounds change. + final WindowContainerToken parentToken = change.getParent(); + final Rect parentBounds; + if (parentToken != null) { + TransitionInfo.Change parentChange = info.getChange(parentToken); + parentBounds = parentChange != null + ? parentChange.getEndAbsBounds() + : change.getEndAbsBounds(); + } else { + parentBounds = change.getEndAbsBounds(); + } + final Animation[] animations = + mAnimationSpec.createChangeBoundsChangeAnimations(change, parentBounds); + // Adapter for the starting screenshot leash. + final SurfaceControl screenshotLeash = createScreenshot(change, startTransaction); + if (screenshotLeash != null) { + // The screenshot leash will be removed in SnapshotAdapter#onAnimationEnd + adapters.add(new ActivityEmbeddingAnimationAdapter.SnapshotAdapter( + animations[0], change, screenshotLeash)); + } else { + Log.e(TAG, "Failed to take screenshot for change=" + change); + } + // Adapter for the ending bounds changed leash. + adapters.add(new ActivityEmbeddingAnimationAdapter.BoundsChangeAdapter( + animations[1], change)); + continue; + } + + // These are the other windows that don't have bounds change in the same transition. + final Animation animation; + if (!TransitionInfo.isIndependent(change, info)) { + // No-op if it will be covered by the changing parent window. + animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change); + } else if (Transitions.isClosingType(change.getMode())) { + animation = mAnimationSpec.createChangeBoundsCloseAnimation(change); + } else { + animation = mAnimationSpec.createChangeBoundsOpenAnimation(change); + } + adapters.add(new ActivityEmbeddingAnimationAdapter(animation, change)); + } + return adapters; + } + + /** Takes a screenshot of the given {@link TransitionInfo.Change} surface. */ + @Nullable + private SurfaceControl createScreenshot(@NonNull TransitionInfo.Change change, + @NonNull SurfaceControl.Transaction startTransaction) { + final Rect cropBounds = new Rect(change.getStartAbsBounds()); + cropBounds.offsetTo(0, 0); + return ScreenshotUtils.takeScreenshot(startTransaction, change.getLeash(), cropBounds, + Integer.MAX_VALUE); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java new file mode 100644 index 000000000000..6f06f28caff2 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.activityembedding; + + +import android.content.Context; +import android.graphics.Point; +import android.graphics.Rect; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.view.animation.AnimationSet; +import android.view.animation.AnimationUtils; +import android.view.animation.ClipRectAnimation; +import android.view.animation.Interpolator; +import android.view.animation.LinearInterpolator; +import android.view.animation.ScaleAnimation; +import android.view.animation.TranslateAnimation; +import android.window.TransitionInfo; + +import androidx.annotation.NonNull; + +import com.android.internal.R; +import com.android.internal.policy.TransitionAnimation; +import com.android.wm.shell.transition.Transitions; + +/** Animation spec for ActivityEmbedding transition. */ +// TODO(b/206557124): provide an easier way to customize animation +class ActivityEmbeddingAnimationSpec { + + private static final String TAG = "ActivityEmbeddingAnimSpec"; + private static final int CHANGE_ANIMATION_DURATION = 517; + private static final int CHANGE_ANIMATION_FADE_DURATION = 80; + private static final int CHANGE_ANIMATION_FADE_OFFSET = 30; + + private final Context mContext; + private final TransitionAnimation mTransitionAnimation; + private final Interpolator mFastOutExtraSlowInInterpolator; + private final LinearInterpolator mLinearInterpolator; + private float mTransitionAnimationScaleSetting; + + ActivityEmbeddingAnimationSpec(@NonNull Context context) { + mContext = context; + mTransitionAnimation = new TransitionAnimation(mContext, false /* debug */, TAG); + mFastOutExtraSlowInInterpolator = AnimationUtils.loadInterpolator( + mContext, android.R.interpolator.fast_out_extra_slow_in); + mLinearInterpolator = new LinearInterpolator(); + } + + /** + * Sets transition animation scale settings value. + * @param scale The setting value of transition animation scale. + */ + void setAnimScaleSetting(float scale) { + mTransitionAnimationScaleSetting = scale; + } + + /** For window that doesn't need to be animated. */ + @NonNull + static Animation createNoopAnimation(@NonNull TransitionInfo.Change change) { + // Noop but just keep the window showing/hiding. + final float alpha = Transitions.isClosingType(change.getMode()) ? 0f : 1f; + return new AlphaAnimation(alpha, alpha); + } + + /** Animation for window that is opening in a change transition. */ + @NonNull + Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo.Change change) { + final Rect bounds = change.getEndAbsBounds(); + final Point offset = change.getEndRelOffset(); + // The window will be animated in from left or right depends on its position. + final int startLeft = offset.x == 0 ? -bounds.width() : bounds.width(); + + // The position should be 0-based as we will post translate in + // ActivityEmbeddingAnimationAdapter#onAnimationUpdate + final Animation animation = new TranslateAnimation(startLeft, 0, 0, 0); + animation.setInterpolator(mFastOutExtraSlowInInterpolator); + animation.setDuration(CHANGE_ANIMATION_DURATION); + animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height()); + animation.scaleCurrentDuration(mTransitionAnimationScaleSetting); + return animation; + } + + /** Animation for window that is closing in a change transition. */ + @NonNull + Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo.Change change) { + final Rect bounds = change.getEndAbsBounds(); + final Point offset = change.getEndRelOffset(); + // The window will be animated out to left or right depends on its position. + final int endLeft = offset.x == 0 ? -bounds.width() : bounds.width(); + + // The position should be 0-based as we will post translate in + // ActivityEmbeddingAnimationAdapter#onAnimationUpdate + final Animation animation = new TranslateAnimation(0, endLeft, 0, 0); + animation.setInterpolator(mFastOutExtraSlowInInterpolator); + animation.setDuration(CHANGE_ANIMATION_DURATION); + animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height()); + animation.scaleCurrentDuration(mTransitionAnimationScaleSetting); + return animation; + } + + /** + * Animation for window that is changing (bounds change) in a change transition. + * @return the return array always has two elements. The first one is for the start leash, and + * the second one is for the end leash. + */ + @NonNull + Animation[] createChangeBoundsChangeAnimations(@NonNull TransitionInfo.Change change, + @NonNull Rect parentBounds) { + // Both start bounds and end bounds are in screen coordinates. We will post translate + // to the local coordinates in ActivityEmbeddingAnimationAdapter#onAnimationUpdate + final Rect startBounds = change.getStartAbsBounds(); + final Rect endBounds = change.getEndAbsBounds(); + float scaleX = ((float) startBounds.width()) / endBounds.width(); + float scaleY = ((float) startBounds.height()) / endBounds.height(); + // Start leash is a child of the end leash. Reverse the scale so that the start leash won't + // be scaled up with its parent. + float startScaleX = 1.f / scaleX; + float startScaleY = 1.f / scaleY; + + // The start leash will be fade out. + final AnimationSet startSet = new AnimationSet(false /* shareInterpolator */); + final Animation startAlpha = new AlphaAnimation(1f, 0f); + startAlpha.setInterpolator(mLinearInterpolator); + startAlpha.setDuration(CHANGE_ANIMATION_FADE_DURATION); + startAlpha.setStartOffset(CHANGE_ANIMATION_FADE_OFFSET); + startSet.addAnimation(startAlpha); + final Animation startScale = new ScaleAnimation(startScaleX, startScaleX, startScaleY, + startScaleY); + startScale.setInterpolator(mFastOutExtraSlowInInterpolator); + startScale.setDuration(CHANGE_ANIMATION_DURATION); + startSet.addAnimation(startScale); + startSet.initialize(startBounds.width(), startBounds.height(), endBounds.width(), + endBounds.height()); + startSet.scaleCurrentDuration(mTransitionAnimationScaleSetting); + + // The end leash will be moved into the end position while scaling. + final AnimationSet endSet = new AnimationSet(true /* shareInterpolator */); + endSet.setInterpolator(mFastOutExtraSlowInInterpolator); + final Animation endScale = new ScaleAnimation(scaleX, 1, scaleY, 1); + endScale.setDuration(CHANGE_ANIMATION_DURATION); + endSet.addAnimation(endScale); + // The position should be 0-based as we will post translate in + // ActivityEmbeddingAnimationAdapter#onAnimationUpdate + final Animation endTranslate = new TranslateAnimation(startBounds.left - endBounds.left, 0, + 0, 0); + endTranslate.setDuration(CHANGE_ANIMATION_DURATION); + endSet.addAnimation(endTranslate); + // The end leash is resizing, we should update the window crop based on the clip rect. + final Rect startClip = new Rect(startBounds); + final Rect endClip = new Rect(endBounds); + startClip.offsetTo(0, 0); + endClip.offsetTo(0, 0); + final Animation clipAnim = new ClipRectAnimation(startClip, endClip); + clipAnim.setDuration(CHANGE_ANIMATION_DURATION); + endSet.addAnimation(clipAnim); + endSet.initialize(startBounds.width(), startBounds.height(), parentBounds.width(), + parentBounds.height()); + endSet.scaleCurrentDuration(mTransitionAnimationScaleSetting); + + return new Animation[]{startSet, endSet}; + } + + @NonNull + Animation loadOpenAnimation(@NonNull TransitionInfo.Change change, + @NonNull Rect wholeAnimationBounds) { + final boolean isEnter = Transitions.isOpeningType(change.getMode()); + final Animation animation; + // TODO(b/207070762): + // 1. Implement clearTop version: R.anim.task_fragment_clear_top_close_enter/exit + // 2. Implement edgeExtension version + animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter + ? R.anim.task_fragment_open_enter + : R.anim.task_fragment_open_exit); + final Rect bounds = change.getEndAbsBounds(); + animation.initialize(bounds.width(), bounds.height(), + wholeAnimationBounds.width(), wholeAnimationBounds.height()); + animation.scaleCurrentDuration(mTransitionAnimationScaleSetting); + return animation; + } + + @NonNull + Animation loadCloseAnimation(@NonNull TransitionInfo.Change change, + @NonNull Rect wholeAnimationBounds) { + final boolean isEnter = Transitions.isOpeningType(change.getMode()); + final Animation animation; + // TODO(b/207070762): + // 1. Implement clearTop version: R.anim.task_fragment_clear_top_close_enter/exit + // 2. Implement edgeExtension version + animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter + ? R.anim.task_fragment_close_enter + : R.anim.task_fragment_close_exit); + final Rect bounds = change.getEndAbsBounds(); + animation.initialize(bounds.width(), bounds.height(), + wholeAnimationBounds.width(), wholeAnimationBounds.height()); + animation.scaleCurrentDuration(mTransitionAnimationScaleSetting); + return animation; + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java index b305897b77ae..e0004fcaa060 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java @@ -18,8 +18,11 @@ package com.android.wm.shell.activityembedding; import static android.window.TransitionInfo.FLAG_IS_EMBEDDED; +import static java.util.Objects.requireNonNull; + import android.content.Context; import android.os.IBinder; +import android.util.ArrayMap; import android.view.SurfaceControl; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; @@ -28,6 +31,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.internal.annotations.VisibleForTesting; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; @@ -37,15 +41,37 @@ import com.android.wm.shell.transition.Transitions; public class ActivityEmbeddingController implements Transitions.TransitionHandler { private final Context mContext; - private final Transitions mTransitions; - - public ActivityEmbeddingController(Context context, ShellInit shellInit, - Transitions transitions) { - mContext = context; - mTransitions = transitions; - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - shellInit.addInitCallback(this::onInit, this); - } + @VisibleForTesting + final Transitions mTransitions; + @VisibleForTesting + final ActivityEmbeddingAnimationRunner mAnimationRunner; + + /** + * Keeps track of the currently-running transition callback associated with each transition + * token. + */ + private final ArrayMap<IBinder, Transitions.TransitionFinishCallback> mTransitionCallbacks = + new ArrayMap<>(); + + private ActivityEmbeddingController(@NonNull Context context, @NonNull ShellInit shellInit, + @NonNull Transitions transitions) { + mContext = requireNonNull(context); + mTransitions = requireNonNull(transitions); + mAnimationRunner = new ActivityEmbeddingAnimationRunner(context, this); + + shellInit.addInitCallback(this::onInit, this); + } + + /** + * Creates {@link ActivityEmbeddingController}, returns {@code null} if the feature is not + * supported. + */ + @Nullable + public static ActivityEmbeddingController create(@NonNull Context context, + @NonNull ShellInit shellInit, @NonNull Transitions transitions) { + return Transitions.ENABLE_SHELL_TRANSITIONS + ? new ActivityEmbeddingController(context, shellInit, transitions) + : null; } /** Registers to handle transitions. */ @@ -66,9 +92,9 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle } } - // TODO(b/207070762) Implement AE animation. - startTransaction.apply(); - finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); + // Start ActivityEmbedding animation. + mTransitionCallbacks.put(transition, finishCallback); + mAnimationRunner.startAnimation(transition, info, startTransaction, finishTransaction); return true; } @@ -79,6 +105,21 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle return null; } + @Override + public void setAnimScaleSetting(float scale) { + mAnimationRunner.setAnimScaleSetting(scale); + } + + /** Called when the animation is finished. */ + void onAnimationFinished(@NonNull IBinder transition) { + final Transitions.TransitionFinishCallback callback = + mTransitionCallbacks.remove(transition); + if (callback == null) { + throw new IllegalStateException("No finish callback found"); + } + callback.onTransitionFinished(null /* wct */, null /* wctCB */); + } + private static boolean isEmbedded(@NonNull TransitionInfo.Change change) { return (change.getFlags() & FLAG_IS_EMBEDDED) != 0; } 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 d3e46f82efe5..33ecdd88fad3 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 @@ -16,6 +16,9 @@ package com.android.wm.shell.back; +import static android.view.RemoteAnimationTarget.MODE_CLOSING; +import static android.view.RemoteAnimationTarget.MODE_OPENING; + import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW; @@ -27,8 +30,6 @@ import android.app.WindowConfiguration; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; -import android.graphics.Point; -import android.graphics.PointF; import android.hardware.HardwareBuffer; import android.hardware.input.InputManager; import android.net.Uri; @@ -47,8 +48,11 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; +import android.window.BackAnimationAdaptor; import android.window.BackEvent; import android.window.BackNavigationInfo; +import android.window.IBackAnimationRunner; +import android.window.IBackNaviAnimationController; import android.window.IOnBackInvokedCallback; import com.android.internal.annotations.VisibleForTesting; @@ -76,22 +80,15 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private static final int PROGRESS_THRESHOLD = SystemProperties .getInt(PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP, -1); private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false); + // TODO (b/241808055) Find a appropriate time to remove during refactor + private static final boolean USE_TRANSITION = + SystemProperties.getInt("persist.wm.debug.predictive_back_ani_trans", 1) != 0; /** * Max duration to wait for a transition to finish before accepting another gesture start * request. */ private static final long MAX_TRANSITION_DURATION = 2000; - /** - * Location of the initial touch event of the back gesture. - */ - private final PointF mInitTouchLocation = new PointF(); - - /** - * Raw delta between {@link #mInitTouchLocation} and the last touch location. - */ - private final Point mTouchEventDelta = new Point(); - /** True when a back gesture is ongoing */ private boolean mBackGestureStarted = false; @@ -119,6 +116,15 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mTransitionInProgress = false; }; + private RemoteAnimationTarget mAnimationTarget; + IBackAnimationRunner mIBackAnimationRunner; + private IBackNaviAnimationController mBackAnimationController; + private BackAnimationAdaptor mBackAnimationAdaptor; + + private boolean mWaitingAnimationStart; + private final TouchTracker mTouchTracker = new TouchTracker(); + private final CachingBackDispatcher mCachingBackDispatcher = new CachingBackDispatcher(); + @VisibleForTesting final IWindowFocusObserver mFocusObserver = new IWindowFocusObserver.Stub() { @Override @@ -137,6 +143,92 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } }; + /** + * Helper class to record the touch location for gesture start and latest. + */ + private static class TouchTracker { + /** + * Location of the latest touch event + */ + private float mLatestTouchX; + private float mLatestTouchY; + private int mSwipeEdge; + + /** + * Location of the initial touch event of the back gesture. + */ + private float mInitTouchX; + private float mInitTouchY; + + void update(float touchX, float touchY, int swipeEdge) { + mLatestTouchX = touchX; + mLatestTouchY = touchY; + mSwipeEdge = swipeEdge; + } + + void setGestureStartLocation(float touchX, float touchY) { + mInitTouchX = touchX; + mInitTouchY = touchY; + } + + int getDeltaFromGestureStart(float touchX) { + return Math.round(touchX - mInitTouchX); + } + + void reset() { + mInitTouchX = 0; + mInitTouchY = 0; + } + } + + /** + * Cache the temporary callback and trigger result if gesture was finish before received + * BackAnimationRunner#onAnimationStart/cancel, so there can continue play the animation. + */ + private class CachingBackDispatcher { + private IOnBackInvokedCallback mOnBackCallback; + private boolean mTriggerBack; + // Whether we are waiting to receive onAnimationStart + private boolean mWaitingAnimation; + + void startWaitingAnimation() { + mWaitingAnimation = true; + } + + boolean set(IOnBackInvokedCallback callback, boolean triggerBack) { + if (mWaitingAnimation) { + mOnBackCallback = callback; + mTriggerBack = triggerBack; + return true; + } + return false; + } + + boolean consume() { + boolean consumed = false; + if (mWaitingAnimation && mOnBackCallback != null) { + if (mTriggerBack) { + final BackEvent backFinish = new BackEvent( + mTouchTracker.mLatestTouchX, mTouchTracker.mLatestTouchY, 1, + mTouchTracker.mSwipeEdge, mAnimationTarget); + dispatchOnBackProgressed(mBackToLauncherCallback, backFinish); + dispatchOnBackInvoked(mOnBackCallback); + } else { + final BackEvent backFinish = new BackEvent( + mTouchTracker.mLatestTouchX, mTouchTracker.mLatestTouchY, 0, + mTouchTracker.mSwipeEdge, mAnimationTarget); + dispatchOnBackProgressed(mBackToLauncherCallback, backFinish); + dispatchOnBackCancelled(mOnBackCallback); + } + startTransition(); + consumed = true; + } + mOnBackCallback = null; + mWaitingAnimation = false; + return consumed; + } + } + public BackAnimationController( @NonNull ShellInit shellInit, @NonNull @ShellMainThread ShellExecutor shellExecutor, @@ -272,6 +364,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @VisibleForTesting void setBackToLauncherCallback(IOnBackInvokedCallback callback) { mBackToLauncherCallback = callback; + if (USE_TRANSITION) { + createAdaptor(); + } } private void clearBackToLauncherCallback() { @@ -280,15 +375,18 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @VisibleForTesting void onBackToLauncherAnimationFinished() { - if (mBackNavigationInfo != null) { - IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback(); - if (mTriggerBack) { + final boolean triggerBack = mTriggerBack; + IOnBackInvokedCallback callback = mBackNavigationInfo != null + ? mBackNavigationInfo.getOnBackInvokedCallback() : null; + // Make sure the notification sequence should be controller > client. + finishAnimation(); + if (callback != null) { + if (triggerBack) { dispatchOnBackInvoked(callback); } else { dispatchOnBackCancelled(callback); } } - finishAnimation(); } /** @@ -300,6 +398,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont if (mTransitionInProgress) { return; } + + mTouchTracker.update(touchX, touchY, swipeEdge); if (keyAction == MotionEvent.ACTION_DOWN) { if (!mBackGestureStarted) { mShouldStartOnNextMoveEvent = true; @@ -330,13 +430,13 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont finishAnimation(); } - mInitTouchLocation.set(touchX, touchY); + mTouchTracker.setGestureStartLocation(touchX, touchY); mBackGestureStarted = true; try { boolean requestAnimation = mEnableAnimations.get(); - mBackNavigationInfo = - mActivityTaskManager.startBackNavigation(requestAnimation, mFocusObserver); + mBackNavigationInfo = mActivityTaskManager.startBackNavigation(requestAnimation, + mFocusObserver, mBackAnimationAdaptor); onBackNavigationInfoReceived(mBackNavigationInfo); } catch (RemoteException remoteException) { Log.e(TAG, "Failed to initAnimation", remoteException); @@ -352,6 +452,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } int backType = backNavigationInfo.getType(); IOnBackInvokedCallback targetCallback = null; + final boolean dispatchToLauncher = shouldDispatchToLauncher(backType); if (backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY) { HardwareBuffer hardwareBuffer = backNavigationInfo.getScreenshotHardwareBuffer(); if (hardwareBuffer != null) { @@ -359,12 +460,17 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont backNavigationInfo.getTaskWindowConfiguration()); } mTransaction.apply(); - } else if (shouldDispatchToLauncher(backType)) { + } else if (dispatchToLauncher) { targetCallback = mBackToLauncherCallback; + if (USE_TRANSITION) { + mCachingBackDispatcher.startWaitingAnimation(); + } } else if (backType == BackNavigationInfo.TYPE_CALLBACK) { targetCallback = mBackNavigationInfo.getOnBackInvokedCallback(); } - dispatchOnBackStarted(targetCallback); + if (!USE_TRANSITION || !dispatchToLauncher) { + dispatchOnBackStarted(targetCallback); + } } /** @@ -403,24 +509,33 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont if (!mBackGestureStarted || mBackNavigationInfo == null) { return; } - int deltaX = Math.round(touchX - mInitTouchLocation.x); + int deltaX = mTouchTracker.getDeltaFromGestureStart(touchX); float progressThreshold = PROGRESS_THRESHOLD >= 0 ? PROGRESS_THRESHOLD : mProgressThreshold; float progress = Math.min(Math.max(Math.abs(deltaX) / progressThreshold, 0), 1); - int backType = mBackNavigationInfo.getType(); - RemoteAnimationTarget animationTarget = mBackNavigationInfo.getDepartingAnimationTarget(); - - BackEvent backEvent = new BackEvent( - touchX, touchY, progress, swipeEdge, animationTarget); - IOnBackInvokedCallback targetCallback = null; - if (shouldDispatchToLauncher(backType)) { - targetCallback = mBackToLauncherCallback; - } else if (backType == BackNavigationInfo.TYPE_CROSS_TASK - || backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY) { - // TODO(208427216) Run the actual animation - } else if (backType == BackNavigationInfo.TYPE_CALLBACK) { - targetCallback = mBackNavigationInfo.getOnBackInvokedCallback(); + if (USE_TRANSITION) { + if (mBackAnimationController != null && mAnimationTarget != null) { + final BackEvent backEvent = new BackEvent( + touchX, touchY, progress, swipeEdge, mAnimationTarget); + dispatchOnBackProgressed(mBackToLauncherCallback, backEvent); + } + } else { + int backType = mBackNavigationInfo.getType(); + RemoteAnimationTarget animationTarget = + mBackNavigationInfo.getDepartingAnimationTarget(); + + BackEvent backEvent = new BackEvent( + touchX, touchY, progress, swipeEdge, animationTarget); + IOnBackInvokedCallback targetCallback = null; + if (shouldDispatchToLauncher(backType)) { + targetCallback = mBackToLauncherCallback; + } else if (backType == BackNavigationInfo.TYPE_CROSS_TASK + || backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY) { + // TODO(208427216) Run the actual animation + } else if (backType == BackNavigationInfo.TYPE_CALLBACK) { + targetCallback = mBackNavigationInfo.getOnBackInvokedCallback(); + } + dispatchOnBackProgressed(targetCallback, backEvent); } - dispatchOnBackProgressed(targetCallback, backEvent); } private void injectBackKey() { @@ -474,6 +589,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont IOnBackInvokedCallback targetCallback = shouldDispatchToLauncher ? mBackToLauncherCallback : mBackNavigationInfo.getOnBackInvokedCallback(); + if (mCachingBackDispatcher.set(targetCallback, mTriggerBack)) { + return; + } if (shouldDispatchToLauncher) { startTransition(); } @@ -493,7 +611,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont && mBackToLauncherCallback != null && mEnableAnimations.get() && mBackNavigationInfo != null - && mBackNavigationInfo.getDepartingAnimationTarget() != null; + && ((USE_TRANSITION && mBackNavigationInfo.isPrepareRemoteAnimation()) + || mBackNavigationInfo.getDepartingAnimationTarget() != null); } private static void dispatchOnBackStarted(IOnBackInvokedCallback callback) { @@ -558,8 +677,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private void finishAnimation() { ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishAnimation()"); - mTouchEventDelta.set(0, 0); - mInitTouchLocation.set(0, 0); + mTouchTracker.reset(); BackNavigationInfo backNavigationInfo = mBackNavigationInfo; boolean triggerBack = mTriggerBack; mBackNavigationInfo = null; @@ -569,19 +687,33 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont return; } - RemoteAnimationTarget animationTarget = backNavigationInfo.getDepartingAnimationTarget(); - if (animationTarget != null) { - if (animationTarget.leash != null && animationTarget.leash.isValid()) { - mTransaction.remove(animationTarget.leash); + if (!USE_TRANSITION) { + RemoteAnimationTarget animationTarget = backNavigationInfo + .getDepartingAnimationTarget(); + if (animationTarget != null) { + if (animationTarget.leash != null && animationTarget.leash.isValid()) { + mTransaction.remove(animationTarget.leash); + } } + SurfaceControl screenshotSurface = backNavigationInfo.getScreenshotSurface(); + if (screenshotSurface != null && screenshotSurface.isValid()) { + mTransaction.remove(screenshotSurface); + } + mTransaction.apply(); } - SurfaceControl screenshotSurface = backNavigationInfo.getScreenshotSurface(); - if (screenshotSurface != null && screenshotSurface.isValid()) { - mTransaction.remove(screenshotSurface); - } - mTransaction.apply(); stopTransition(); backNavigationInfo.onBackNavigationFinished(triggerBack); + if (USE_TRANSITION) { + final IBackNaviAnimationController controller = mBackAnimationController; + if (controller != null) { + try { + controller.finish(triggerBack); + } catch (RemoteException r) { + // Oh no! + } + } + mBackAnimationController = null; + } } private void startTransition() { @@ -599,4 +731,50 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mShellExecutor.removeCallbacks(mResetTransitionRunnable); mTransitionInProgress = false; } + + private void createAdaptor() { + mIBackAnimationRunner = new IBackAnimationRunner.Stub() { + @Override + public void onAnimationCancelled() { + // no op for now + } + @Override // Binder interface + public void onAnimationStart(IBackNaviAnimationController controller, int type, + RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, + RemoteAnimationTarget[] nonApps) { + mShellExecutor.execute(() -> { + mBackAnimationController = controller; + for (int i = 0; i < apps.length; i++) { + final RemoteAnimationTarget target = apps[i]; + if (MODE_CLOSING == target.mode) { + mAnimationTarget = target; + } else if (MODE_OPENING == target.mode) { + // TODO Home activity should handle the visibility for itself + // once it finish relayout for orientation change + SurfaceControl.Transaction tx = + new SurfaceControl.Transaction(); + tx.setAlpha(target.leash, 1); + tx.apply(); + } + } + // TODO animation target should be passed at onBackStarted + dispatchOnBackStarted(mBackToLauncherCallback); + // TODO This is Workaround for LauncherBackAnimationController, there will need + // to dispatch onBackProgressed twice(startBack & updateBackProgress) to + // initialize the animation data, for now that would happen when onMove + // called, but there will no expected animation if the down -> up gesture + // happen very fast which ACTION_MOVE only happen once. + final BackEvent backInit = new BackEvent( + mTouchTracker.mLatestTouchX, mTouchTracker.mLatestTouchY, 0, + mTouchTracker.mSwipeEdge, mAnimationTarget); + dispatchOnBackProgressed(mBackToLauncherCallback, backInit); + if (!mCachingBackDispatcher.consume()) { + dispatchOnBackProgressed(mBackToLauncherCallback, backInit); + } + }); + } + }; + mBackAnimationAdaptor = new BackAnimationAdaptor(mIBackAnimationRunner, + BackNavigationInfo.TYPE_RETURN_TO_HOME); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index 2c02006c8ca5..99b8885acdef 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -821,7 +821,7 @@ public class Bubble implements BubbleViewProvider { /** * Description of current bubble state. */ - public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { + public void dump(@NonNull PrintWriter pw) { pw.print("key: "); pw.println(mKey); pw.print(" showInShade: "); pw.println(showInShade()); pw.print(" showDot: "); pw.println(showDot()); @@ -831,7 +831,7 @@ public class Bubble implements BubbleViewProvider { pw.print(" suppressNotif: "); pw.println(shouldSuppressNotification()); pw.print(" autoExpand: "); pw.println(shouldAutoExpand()); if (mExpandedView != null) { - mExpandedView.dump(pw, args); + mExpandedView.dump(pw); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index de26b54971ca..dcbb272feab8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -72,7 +72,6 @@ import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.RankingMap; import android.util.Log; import android.util.Pair; -import android.util.Slog; import android.util.SparseArray; import android.view.View; import android.view.ViewGroup; @@ -100,6 +99,7 @@ import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.onehanded.OneHandedTransitionCallback; import com.android.wm.shell.pip.PinnedStackListenerForwarder; import com.android.wm.shell.sysui.ConfigurationChangeListener; +import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; @@ -159,6 +159,7 @@ public class BubbleController implements ConfigurationChangeListener { private final TaskViewTransitions mTaskViewTransitions; private final SyncTransactionQueue mSyncQueue; private final ShellController mShellController; + private final ShellCommandHandler mShellCommandHandler; // Used to post to main UI thread private final ShellExecutor mMainExecutor; @@ -229,6 +230,7 @@ public class BubbleController implements ConfigurationChangeListener { public BubbleController(Context context, ShellInit shellInit, + ShellCommandHandler shellCommandHandler, ShellController shellController, BubbleData data, @Nullable BubbleStackView.SurfaceSynchronizer synchronizer, @@ -252,6 +254,7 @@ public class BubbleController implements ConfigurationChangeListener { TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) { mContext = context; + mShellCommandHandler = shellCommandHandler; mShellController = shellController; mLauncherApps = launcherApps; mBarService = statusBarService == null @@ -431,6 +434,7 @@ public class BubbleController implements ConfigurationChangeListener { mCurrentProfiles = userProfiles; mShellController.addConfigurationChangeListener(this); + mShellCommandHandler.addDumpCallback(this::dump, this); } @VisibleForTesting @@ -538,7 +542,6 @@ public class BubbleController implements ConfigurationChangeListener { if (mNotifEntryToExpandOnShadeUnlock != null) { expandStackAndSelectBubble(mNotifEntryToExpandOnShadeUnlock); - mNotifEntryToExpandOnShadeUnlock = null; } updateStack(); @@ -925,15 +928,6 @@ public class BubbleController implements ConfigurationChangeListener { return (isSummary && isSuppressedSummary) || isSuppressedBubble; } - private void removeSuppressedSummaryIfNecessary(String groupKey, Consumer<String> callback) { - if (mBubbleData.isSummarySuppressed(groupKey)) { - mBubbleData.removeSuppressedSummary(groupKey); - if (callback != null) { - callback.accept(mBubbleData.getSummaryKey(groupKey)); - } - } - } - /** Promote the provided bubble from the overflow view. */ public void promoteBubbleFromOverflow(Bubble bubble) { mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BACK_TO_STACK); @@ -1519,14 +1513,15 @@ public class BubbleController implements ConfigurationChangeListener { /** * Description of current bubble state. */ - private void dump(PrintWriter pw, String[] args) { + private void dump(PrintWriter pw, String prefix) { pw.println("BubbleController state:"); - mBubbleData.dump(pw, args); + mBubbleData.dump(pw); pw.println(); if (mStackView != null) { - mStackView.dump(pw, args); + mStackView.dump(pw); } pw.println(); + mImpl.mCachedState.dump(pw); } /** @@ -1711,28 +1706,12 @@ public class BubbleController implements ConfigurationChangeListener { } @Override - public boolean isStackExpanded() { - return mCachedState.isStackExpanded(); - } - - @Override @Nullable public Bubble getBubbleWithShortcutId(String shortcutId) { return mCachedState.getBubbleWithShortcutId(shortcutId); } @Override - public void removeSuppressedSummaryIfNecessary(String groupKey, Consumer<String> callback, - Executor callbackExecutor) { - mMainExecutor.execute(() -> { - Consumer<String> cb = callback != null - ? (key) -> callbackExecutor.execute(() -> callback.accept(key)) - : null; - BubbleController.this.removeSuppressedSummaryIfNecessary(groupKey, cb); - }); - } - - @Override public void collapseStack() { mMainExecutor.execute(() -> { BubbleController.this.collapseStack(); @@ -1761,13 +1740,6 @@ public class BubbleController implements ConfigurationChangeListener { } @Override - public void openBubbleOverflow() { - mMainExecutor.execute(() -> { - BubbleController.this.openBubbleOverflow(); - }); - } - - @Override public boolean handleDismissalInterception(BubbleEntry entry, @Nullable List<BubbleEntry> children, IntConsumer removeCallback, Executor callbackExecutor) { @@ -1882,18 +1854,6 @@ public class BubbleController implements ConfigurationChangeListener { mMainExecutor.execute( () -> BubbleController.this.onNotificationPanelExpandedChanged(expanded)); } - - @Override - public void dump(PrintWriter pw, String[] args) { - try { - mMainExecutor.executeBlocking(() -> { - BubbleController.this.dump(pw, args); - mCachedState.dump(pw); - }); - } catch (InterruptedException e) { - Slog.e(TAG, "Failed to dump BubbleController in 2s"); - } - } } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index fa86c8436647..c64133f0b668 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -1136,7 +1136,7 @@ public class BubbleData { /** * Description of current bubble data state. */ - public void dump(PrintWriter pw, String[] args) { + public void dump(PrintWriter pw) { pw.print("selected: "); pw.println(mSelectedBubble != null ? mSelectedBubble.getKey() @@ -1147,13 +1147,13 @@ public class BubbleData { pw.print("stack bubble count: "); pw.println(mBubbles.size()); for (Bubble bubble : mBubbles) { - bubble.dump(pw, args); + bubble.dump(pw); } pw.print("overflow bubble count: "); pw.println(mOverflowBubbles.size()); for (Bubble bubble : mOverflowBubbles) { - bubble.dump(pw, args); + bubble.dump(pw); } pw.print("summaryKeys: "); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index 4f225fff1451..840b2856270c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -1044,7 +1044,7 @@ public class BubbleExpandedView extends LinearLayout { /** * Description of current expanded view state. */ - public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { + public void dump(@NonNull PrintWriter pw) { pw.print("BubbleExpandedView"); pw.print(" taskId: "); pw.println(mTaskId); pw.print(" stackView: "); pw.println(mStackView); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 2d0be066beb5..aeaf6eda9809 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -111,6 +111,9 @@ public class BubbleStackView extends FrameLayout public static final boolean HOME_GESTURE_ENABLED = SystemProperties.getBoolean("persist.wm.debug.bubbles_home_gesture", true); + public static final boolean ENABLE_FLING_TO_DISMISS_BUBBLE = + SystemProperties.getBoolean("persist.wm.debug.fling_to_dismiss_bubble", true); + private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleStackView" : TAG_BUBBLES; /** How far the flyout needs to be dragged before it's dismissed regardless of velocity. */ @@ -299,7 +302,7 @@ public class BubbleStackView extends FrameLayout private BubblesNavBarGestureTracker mBubblesNavBarGestureTracker; /** Description of current animation controller state. */ - public void dump(PrintWriter pw, String[] args) { + public void dump(PrintWriter pw) { pw.println("Stack view state:"); String bubblesOnScreen = BubbleDebugConfig.formatBubblesString( @@ -313,8 +316,8 @@ public class BubbleStackView extends FrameLayout pw.print(" expandedContainerMatrix: "); pw.println(mExpandedViewContainer.getAnimationMatrix()); - mStackAnimationController.dump(pw, args); - mExpandedAnimationController.dump(pw, args); + mStackAnimationController.dump(pw); + mExpandedAnimationController.dump(pw); if (mExpandedBubble != null) { pw.println("Expanded bubble state:"); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index 37b96ffe5cd1..0e97e9e74114 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -35,7 +35,6 @@ import androidx.annotation.Nullable; import com.android.wm.shell.common.annotations.ExternalThread; -import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.util.HashMap; @@ -91,18 +90,6 @@ public interface Bubbles { */ boolean isBubbleExpanded(String key); - /** @return {@code true} if stack of bubbles is expanded or not. */ - boolean isStackExpanded(); - - /** - * Removes a group key indicating that the summary for this group should no longer be - * suppressed. - * - * @param callback If removed, this callback will be called with the summary key of the group - */ - void removeSuppressedSummaryIfNecessary(String groupKey, Consumer<String> callback, - Executor callbackExecutor); - /** Tell the stack of bubbles to collapse. */ void collapseStack(); @@ -130,9 +117,6 @@ public interface Bubbles { /** Called for any taskbar changes. */ void onTaskbarChanged(Bundle b); - /** Open the overflow view. */ - void openBubbleOverflow(); - /** * We intercept notification entries (including group summaries) dismissed by the user when * there is an active bubble associated with it. We do this so that developers can still @@ -252,9 +236,6 @@ public interface Bubbles { */ void onUserRemoved(int removedUserId); - /** Description of current bubble state. */ - void dump(PrintWriter pw, String[] args); - /** Listener to find out about stack expansion / collapse events. */ interface BubbleExpandListener { /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java index b521cb6a3d38..b91062f891e8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java @@ -19,6 +19,7 @@ package com.android.wm.shell.bubbles.animation; import static android.view.View.LAYOUT_DIRECTION_RTL; import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING; +import static com.android.wm.shell.bubbles.BubbleStackView.ENABLE_FLING_TO_DISMISS_BUBBLE; import static com.android.wm.shell.bubbles.BubbleStackView.HOME_GESTURE_ENABLED; import android.content.res.Resources; @@ -366,6 +367,7 @@ public class ExpandedAnimationController mMagnetizedBubbleDraggingOut.setMagnetListener(listener); mMagnetizedBubbleDraggingOut.setHapticsEnabled(true); mMagnetizedBubbleDraggingOut.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY); + mMagnetizedBubbleDraggingOut.setFlingToTargetEnabled(ENABLE_FLING_TO_DISMISS_BUBBLE); } private void springBubbleTo(View bubble, float x, float y) { @@ -468,7 +470,7 @@ public class ExpandedAnimationController } /** Description of current animation controller state. */ - public void dump(PrintWriter pw, String[] args) { + public void dump(PrintWriter pw) { pw.println("ExpandedAnimationController state:"); pw.print(" isActive: "); pw.println(isActiveController()); pw.print(" animatingExpand: "); pw.println(mAnimatingExpand); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java index 0a1b4d70fb2b..961722ba9bc0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java @@ -17,6 +17,7 @@ package com.android.wm.shell.bubbles.animation; import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING; +import static com.android.wm.shell.bubbles.BubbleStackView.ENABLE_FLING_TO_DISMISS_BUBBLE; import android.content.ContentResolver; import android.content.res.Resources; @@ -431,7 +432,7 @@ public class StackAnimationController extends } /** Description of current animation controller state. */ - public void dump(PrintWriter pw, String[] args) { + public void dump(PrintWriter pw) { pw.println("StackAnimationController state:"); pw.print(" isActive: "); pw.println(isActiveController()); pw.print(" restingStackPos: "); @@ -1028,6 +1029,7 @@ public class StackAnimationController extends }; mMagnetizedStack.setHapticsEnabled(true); mMagnetizedStack.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY); + mMagnetizedStack.setFlingToTargetEnabled(ENABLE_FLING_TO_DISMISS_BUBBLE); } final ContentResolver contentResolver = mLayout.getContext().getContentResolver(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java index d5875c03ccd2..e270edb800bd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java @@ -221,8 +221,7 @@ public class SystemWindows { } final Display display = mDisplayController.getDisplay(mDisplayId); SurfaceControlViewHost viewRoot = - new SurfaceControlViewHost( - view.getContext(), display, wwm, true /* useSfChoreographer */); + new SurfaceControlViewHost(view.getContext(), display, wwm); attrs.flags |= FLAG_HARDWARE_ACCELERATED; viewRoot.setView(view, attrs); mViewRoots.put(view, viewRoot); 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 85c8ebf454c9..83ba909e712d 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 @@ -80,7 +80,10 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange public static final int PARALLAX_DISMISSING = 1; public static final int PARALLAX_ALIGN_CENTER = 2; - private static final int FLING_ANIMATION_DURATION = 250; + private static final int FLING_RESIZE_DURATION = 250; + private static final int FLING_SWITCH_DURATION = 350; + private static final int FLING_ENTER_DURATION = 350; + private static final int FLING_EXIT_DURATION = 350; private final int mDividerWindowWidth; private final int mDividerInsets; @@ -93,6 +96,9 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange private final Rect mBounds1 = new Rect(); // Bounds2 final position should be always at bottom or right private final Rect mBounds2 = new Rect(); + // The temp bounds outside of display bounds for side stage when split screen inactive to avoid + // flicker next time active split screen. + private final Rect mInvisibleBounds = new Rect(); private final Rect mWinBounds1 = new Rect(); private final Rect mWinBounds2 = new Rect(); private final SplitLayoutHandler mSplitLayoutHandler; @@ -141,6 +147,10 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange resetDividerPosition(); mDimNonImeSide = resources.getBoolean(R.bool.config_dimNonImeAttachedSide); + + mInvisibleBounds.set(mRootBounds); + mInvisibleBounds.offset(isLandscape() ? mRootBounds.right : 0, + isLandscape() ? 0 : mRootBounds.bottom); } private int getDividerInsets(Resources resources, Display display) { @@ -239,6 +249,12 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange rect.offset(-mRootBounds.left, -mRootBounds.top); } + /** Gets bounds size equal to root bounds but outside of screen, used for position side stage + * when split inactive to avoid flicker when next time active. */ + public void getInvisibleBounds(Rect rect) { + rect.set(mInvisibleBounds); + } + /** Returns leash of the current divider bar. */ @Nullable public SurfaceControl getDividerLeash() { @@ -284,6 +300,10 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null); initDividerPosition(mTempRect); + mInvisibleBounds.set(mRootBounds); + mInvisibleBounds.offset(isLandscape() ? mRootBounds.right : 0, + isLandscape() ? 0 : mRootBounds.bottom); + return true; } @@ -405,6 +425,13 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange mFreezeDividerWindow = freezeDividerWindow; } + /** Update current layout as divider put on start or end position. */ + public void setDividerAtBorder(boolean start) { + final int pos = start ? mDividerSnapAlgorithm.getDismissStartTarget().position + : mDividerSnapAlgorithm.getDismissEndTarget().position; + setDividePosition(pos, false /* applyLayoutChange */); + } + /** * Updates bounds with the passing position. Usually used to update recording bounds while * performing animation or dragging divider bar to resize the splits. @@ -449,17 +476,17 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange public void snapToTarget(int currentPosition, DividerSnapAlgorithm.SnapTarget snapTarget) { switch (snapTarget.flag) { case FLAG_DISMISS_START: - flingDividePosition(currentPosition, snapTarget.position, + flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION, () -> mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */, EXIT_REASON_DRAG_DIVIDER)); break; case FLAG_DISMISS_END: - flingDividePosition(currentPosition, snapTarget.position, + flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION, () -> mSplitLayoutHandler.onSnappedToDismiss(true /* bottomOrRight */, EXIT_REASON_DRAG_DIVIDER)); break; default: - flingDividePosition(currentPosition, snapTarget.position, + flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION, () -> setDividePosition(snapTarget.position, true /* applyLayoutChange */)); break; } @@ -516,12 +543,20 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange public void flingDividerToDismiss(boolean toEnd, int reason) { final int target = toEnd ? mDividerSnapAlgorithm.getDismissEndTarget().position : mDividerSnapAlgorithm.getDismissStartTarget().position; - flingDividePosition(getDividePosition(), target, + flingDividePosition(getDividePosition(), target, FLING_EXIT_DURATION, () -> mSplitLayoutHandler.onSnappedToDismiss(toEnd, reason)); } + /** Fling divider from current position to center position. */ + public void flingDividerToCenter() { + final int pos = mDividerSnapAlgorithm.getMiddleTarget().position; + flingDividePosition(getDividePosition(), pos, FLING_ENTER_DURATION, + () -> setDividePosition(pos, true /* applyLayoutChange */)); + } + @VisibleForTesting - void flingDividePosition(int from, int to, @Nullable Runnable flingFinishedCallback) { + void flingDividePosition(int from, int to, int duration, + @Nullable Runnable flingFinishedCallback) { if (from == to) { // No animation run, still callback to stop resizing. mSplitLayoutHandler.onLayoutSizeChanged(this); @@ -531,7 +566,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange } ValueAnimator animator = ValueAnimator .ofInt(from, to) - .setDuration(FLING_ANIMATION_DURATION); + .setDuration(duration); animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); animator.addUpdateListener( animation -> updateDivideBounds((int) animation.getAnimatedValue())); @@ -588,7 +623,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange AnimatorSet set = new AnimatorSet(); set.playTogether(animator1, animator2, animator3); - set.setDuration(FLING_ANIMATION_DURATION); + set.setDuration(FLING_SWITCH_DURATION); set.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java index e22c9517f4ab..8022e9b1cd81 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java @@ -66,6 +66,7 @@ public abstract class TvPipModule { @Provides static Optional<Pip> providePip( Context context, + ShellInit shellInit, ShellController shellController, TvPipBoundsState tvPipBoundsState, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, @@ -84,6 +85,7 @@ public abstract class TvPipModule { return Optional.of( TvPipController.create( context, + shellInit, shellController, tvPipBoundsState, tvPipBoundsAlgorithm, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index a6a04cf67b3c..7a736ccab5d1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -85,6 +85,7 @@ import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.unfold.ShellUnfoldProgressProvider; import com.android.wm.shell.unfold.UnfoldAnimationController; import com.android.wm.shell.unfold.UnfoldTransitionHandler; +import com.android.wm.shell.windowdecor.WindowDecorViewModel; import java.util.Optional; @@ -294,25 +295,33 @@ public abstract class WMShellBaseModule { // Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride} @BindsOptionalOf @DynamicOverride - abstract FullscreenTaskListener optionalFullscreenTaskListener(); + abstract FullscreenTaskListener<?> optionalFullscreenTaskListener(); @WMSingleton @Provides - static FullscreenTaskListener provideFullscreenTaskListener( - @DynamicOverride Optional<FullscreenTaskListener> fullscreenTaskListener, + static FullscreenTaskListener<?> provideFullscreenTaskListener( + @DynamicOverride Optional<FullscreenTaskListener<?>> fullscreenTaskListener, ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, - Optional<RecentTasksController> recentTasksOptional) { + Optional<RecentTasksController> recentTasksOptional, + Optional<WindowDecorViewModel<?>> windowDecorViewModelOptional) { if (fullscreenTaskListener.isPresent()) { return fullscreenTaskListener.get(); } else { return new FullscreenTaskListener(shellInit, shellTaskOrganizer, syncQueue, - recentTasksOptional); + recentTasksOptional, windowDecorViewModelOptional); } } // + // Window Decoration + // + + @BindsOptionalOf + abstract WindowDecorViewModel<?> optionalWindowDecorViewModel(); + + // // Unfold transition // @@ -627,11 +636,12 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides - static ActivityEmbeddingController provideActivityEmbeddingController( + static Optional<ActivityEmbeddingController> provideActivityEmbeddingController( Context context, ShellInit shellInit, Transitions transitions) { - return new ActivityEmbeddingController(context, shellInit, transitions); + return Optional.ofNullable( + ActivityEmbeddingController.create(context, shellInit, transitions)); } // @@ -679,14 +689,14 @@ public abstract class WMShellBaseModule { Optional<SplitScreenController> splitScreenOptional, Optional<Pip> pipOptional, Optional<PipTouchHandler> pipTouchHandlerOptional, - FullscreenTaskListener fullscreenTaskListener, + FullscreenTaskListener<?> fullscreenTaskListener, Optional<UnfoldAnimationController> unfoldAnimationController, Optional<UnfoldTransitionHandler> unfoldTransitionHandler, Optional<FreeformComponents> freeformComponents, Optional<RecentTasksController> recentTasksOptional, Optional<OneHandedController> oneHandedControllerOptional, Optional<HideDisplayCutoutController> hideDisplayCutoutControllerOptional, - ActivityEmbeddingController activityEmbeddingOptional, + Optional<ActivityEmbeddingController> activityEmbeddingOptional, Transitions transitions, StartingWindowController startingWindow, @ShellCreateTriggerOverride Optional<Object> overriddenCreateTrigger) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java index 35a309a8352c..0cc545a7724a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java @@ -20,7 +20,6 @@ import static android.os.Process.THREAD_PRIORITY_BACKGROUND; import static android.os.Process.THREAD_PRIORITY_DISPLAY; import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST; -import android.animation.AnimationHandler; import android.content.Context; import android.os.Build; import android.os.Handler; @@ -31,11 +30,9 @@ import android.view.Choreographer; import androidx.annotation.Nullable; -import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.wm.shell.R; import com.android.wm.shell.common.HandlerExecutor; import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.common.annotations.ChoreographerSfVsync; import com.android.wm.shell.common.annotations.ExternalMainThread; import com.android.wm.shell.common.annotations.ShellAnimationThread; import com.android.wm.shell.common.annotations.ShellBackgroundThread; @@ -195,30 +192,6 @@ public abstract class WMShellConcurrencyModule { } /** - * Provide a Shell main-thread AnimationHandler. The AnimationHandler can be set on - * {@link android.animation.ValueAnimator}s and will ensure that the animation will run on - * the Shell main-thread with the SF vsync. - */ - @WMSingleton - @Provides - @ChoreographerSfVsync - public static AnimationHandler provideShellMainExecutorSfVsyncAnimationHandler( - @ShellMainThread ShellExecutor mainExecutor) { - try { - AnimationHandler handler = new AnimationHandler(); - mainExecutor.executeBlocking(() -> { - // This is called on the animation thread since it calls - // Choreographer.getSfInstance() which returns a thread-local Choreographer instance - // that uses the SF vsync - handler.setProvider(new SfVsyncFrameCallbackProvider()); - }); - return handler; - } catch (InterruptedException e) { - throw new RuntimeException("Failed to initialize SfVsync animation handler in 1s", e); - } - } - - /** * Provides a Shell background thread Handler for low priority background tasks. */ @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 2ca9c3be8a69..c64d1134a4ab 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -27,6 +27,7 @@ import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.UiEventLogger; import com.android.internal.statusbar.IStatusBarService; import com.android.launcher3.icons.IconProvider; +import com.android.wm.shell.RootDisplayAreaOrganizer; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.TaskViewTransitions; @@ -48,10 +49,13 @@ import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.annotations.ShellBackgroundThread; import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.desktopmode.DesktopModeConstants; +import com.android.wm.shell.desktopmode.DesktopModeController; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.freeform.FreeformComponents; import com.android.wm.shell.freeform.FreeformTaskListener; import com.android.wm.shell.freeform.FreeformTaskTransitionHandler; +import com.android.wm.shell.fullscreen.FullscreenTaskListener; import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipAnimationController; @@ -142,6 +146,7 @@ public abstract class WMShellModule { @Provides static BubbleController provideBubbleController(Context context, ShellInit shellInit, + ShellCommandHandler shellCommandHandler, ShellController shellController, BubbleData data, FloatingContentCoordinator floatingContentCoordinator, @@ -162,7 +167,7 @@ public abstract class WMShellModule { @ShellBackgroundThread ShellExecutor bgExecutor, TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) { - return new BubbleController(context, shellInit, shellController, data, + return new BubbleController(context, shellInit, shellCommandHandler, shellController, data, null /* synchronizer */, floatingContentCoordinator, new BubbleDataRepository(context, launcherApps, mainExecutor), statusBarService, windowManager, windowManagerShellWrapper, userManager, @@ -229,6 +234,7 @@ public abstract class WMShellModule { ShellInit shellInit, Transitions transitions, WindowDecorViewModel<?> windowDecorViewModel, + FullscreenTaskListener<?> fullscreenTaskListener, FreeformTaskListener<?> freeformTaskListener) { // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic // override for this controller from the base module @@ -236,7 +242,7 @@ public abstract class WMShellModule { ? shellInit : null; return new FreeformTaskTransitionHandler(init, transitions, - windowDecorViewModel, freeformTaskListener); + windowDecorViewModel, fullscreenTaskListener, freeformTaskListener); } // @@ -574,6 +580,27 @@ public abstract class WMShellModule { } // + // Desktop mode (optional feature) + // + + @WMSingleton + @Provides + static Optional<DesktopModeController> provideDesktopModeController( + Context context, ShellInit shellInit, + ShellTaskOrganizer shellTaskOrganizer, + RootDisplayAreaOrganizer rootDisplayAreaOrganizer, + @ShellMainThread Handler mainHandler + ) { + if (DesktopModeConstants.IS_FEATURE_ENABLED) { + return Optional.of(new DesktopModeController(context, shellInit, shellTaskOrganizer, + rootDisplayAreaOrganizer, + mainHandler)); + } else { + return Optional.empty(); + } + } + + // // Misc // @@ -583,7 +610,8 @@ public abstract class WMShellModule { @ShellCreateTriggerOverride @Provides static Object provideIndependentShellComponentsToCreate( - SplitscreenPipMixedHandler splitscreenPipMixedHandler) { + SplitscreenPipMixedHandler splitscreenPipMixedHandler, + Optional<DesktopModeController> desktopModeController) { return new Object(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/FakeConnectivityInfoCollector.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeConstants.java index 710e5f6eacd3..e62a63a910e7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/FakeConnectivityInfoCollector.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeConstants.java @@ -14,20 +14,18 @@ * limitations under the License. */ -package com.android.systemui.statusbar.pipeline +package com.android.wm.shell.desktopmode; -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow +import android.os.SystemProperties; /** - * A test-friendly implementation of [ConnectivityInfoCollector] that just emits whatever value it - * receives in [emitValue]. + * Constants for desktop mode feature */ -class FakeConnectivityInfoCollector : ConnectivityInfoCollector { - private val _rawConnectivityInfoFlow = MutableStateFlow(RawConnectivityInfo()) - override val rawConnectivityInfoFlow = _rawConnectivityInfoFlow.asStateFlow() +public class DesktopModeConstants { - suspend fun emitValue(value: RawConnectivityInfo) { - _rawConnectivityInfoFlow.emit(value) - } + /** + * Flag to indicate whether desktop mode is available on the device + */ + public static final boolean IS_FEATURE_ENABLED = SystemProperties.getBoolean( + "persist.wm.debug.desktop_mode", false); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java new file mode 100644 index 000000000000..5849e163f0e2 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.desktopmode; + +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; + +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE; + +import android.content.Context; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.os.UserHandle; +import android.provider.Settings; +import android.window.WindowContainerTransaction; + +import androidx.annotation.Nullable; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.RootDisplayAreaOrganizer; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.sysui.ShellInit; + +/** + * Handles windowing changes when desktop mode system setting changes + */ +public class DesktopModeController { + + private final Context mContext; + private final ShellTaskOrganizer mShellTaskOrganizer; + private final RootDisplayAreaOrganizer mRootDisplayAreaOrganizer; + private final SettingsObserver mSettingsObserver; + + public DesktopModeController(Context context, ShellInit shellInit, + ShellTaskOrganizer shellTaskOrganizer, + RootDisplayAreaOrganizer rootDisplayAreaOrganizer, + @ShellMainThread Handler mainHandler) { + mContext = context; + mShellTaskOrganizer = shellTaskOrganizer; + mRootDisplayAreaOrganizer = rootDisplayAreaOrganizer; + mSettingsObserver = new SettingsObserver(mContext, mainHandler); + shellInit.addInitCallback(this::onInit, this); + } + + private void onInit() { + ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopModeController"); + mSettingsObserver.observe(); + } + + @VisibleForTesting + void updateDesktopModeEnabled(boolean enabled) { + ProtoLog.d(WM_SHELL_DESKTOP_MODE, "updateDesktopModeState: enabled=%s", enabled); + + int displayId = mContext.getDisplayId(); + + WindowContainerTransaction wct = new WindowContainerTransaction(); + // Reset freeform windowing mode that is set per task level (tasks should inherit + // container value) + wct.merge(mShellTaskOrganizer.prepareClearFreeformForTasks(displayId), true /* transfer */); + int targetWindowingMode; + if (enabled) { + targetWindowingMode = WINDOWING_MODE_FREEFORM; + } else { + targetWindowingMode = WINDOWING_MODE_FULLSCREEN; + // Clear any resized bounds + wct.merge(mShellTaskOrganizer.prepareClearBoundsForTasks(displayId), + true /* transfer */); + } + wct.merge(mRootDisplayAreaOrganizer.prepareWindowingModeChange(displayId, + targetWindowingMode), true /* transfer */); + mRootDisplayAreaOrganizer.applyTransaction(wct); + } + + /** + * A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE} + */ + private final class SettingsObserver extends ContentObserver { + + private final Uri mDesktopModeSetting = Settings.System.getUriFor( + Settings.System.DESKTOP_MODE); + + private final Context mContext; + + SettingsObserver(Context context, Handler handler) { + super(handler); + mContext = context; + } + + public void observe() { + // TODO(b/242867463): listen for setting change for all users + mContext.getContentResolver().registerContentObserver(mDesktopModeSetting, + false /* notifyForDescendants */, this /* observer */, UserHandle.USER_CURRENT); + } + + @Override + public void onChange(boolean selfChange, @Nullable Uri uri) { + if (mDesktopModeSetting.equals(uri)) { + ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Received update for desktop mode setting"); + desktopModeSettingChanged(); + } + } + + private void desktopModeSettingChanged() { + boolean enabled = isDesktopModeEnabled(); + updateDesktopModeEnabled(enabled); + } + + private boolean isDesktopModeEnabled() { + try { + int result = Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.DESKTOP_MODE, UserHandle.USER_CURRENT); + ProtoLog.d(WM_SHELL_DESKTOP_MODE, "isDesktopModeEnabled=%s", result); + return result != 0; + } catch (Settings.SettingNotFoundException e) { + ProtoLog.e(WM_SHELL_DESKTOP_MODE, "Failed to read DESKTOP_MODE setting %s", e); + return false; + } + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java index ab66107399c6..8dcdda1895e6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java @@ -187,12 +187,14 @@ public class FreeformTaskListener<T extends AutoCloseable> RunningTaskInfo taskInfo, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { - T windowDecor = mWindowDecorOfVanishedTasks.get(taskInfo.taskId); - mWindowDecorOfVanishedTasks.remove(taskInfo.taskId); + T windowDecor; final State<T> state = mTasks.get(taskInfo.taskId); if (state != null) { - windowDecor = windowDecor == null ? state.mWindowDecoration : windowDecor; + windowDecor = state.mWindowDecoration; state.mWindowDecoration = null; + } else { + windowDecor = + mWindowDecorOfVanishedTasks.removeReturnOld(taskInfo.taskId); } mWindowDecorationViewModel.setupWindowDecorationForTransition( taskInfo, startT, finishT, windowDecor); @@ -231,7 +233,8 @@ public class FreeformTaskListener<T extends AutoCloseable> if (mWindowDecorOfVanishedTasks.size() == 0) { return; } - Log.w(TAG, "Clearing window decors of vanished tasks. There could be visual defects " + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + "Clearing window decors of vanished tasks. There could be visual defects " + "if any of them is used later in transitions."); for (int i = 0; i < mWindowDecorOfVanishedTasks.size(); ++i) { releaseWindowDecor(mWindowDecorOfVanishedTasks.valueAt(i)); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java index a1e9f938d280..30f625e24118 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java @@ -32,6 +32,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.wm.shell.fullscreen.FullscreenTaskListener; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.windowdecor.WindowDecorViewModel; @@ -50,6 +51,7 @@ public class FreeformTaskTransitionHandler private final Transitions mTransitions; private final FreeformTaskListener<?> mFreeformTaskListener; + private final FullscreenTaskListener<?> mFullscreenTaskListener; private final WindowDecorViewModel<?> mWindowDecorViewModel; private final List<IBinder> mPendingTransitionTokens = new ArrayList<>(); @@ -58,8 +60,10 @@ public class FreeformTaskTransitionHandler ShellInit shellInit, Transitions transitions, WindowDecorViewModel<?> windowDecorViewModel, + FullscreenTaskListener<?> fullscreenTaskListener, FreeformTaskListener<?> freeformTaskListener) { mTransitions = transitions; + mFullscreenTaskListener = fullscreenTaskListener; mFreeformTaskListener = freeformTaskListener; mWindowDecorViewModel = windowDecorViewModel; if (shellInit != null && Transitions.ENABLE_SHELL_TRANSITIONS) { @@ -150,10 +154,16 @@ public class FreeformTaskTransitionHandler TransitionInfo.Change change, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { - if (change.getTaskInfo().getWindowingMode() != WINDOWING_MODE_FREEFORM) { - return false; + switch (change.getTaskInfo().getWindowingMode()){ + case WINDOWING_MODE_FREEFORM: + mFreeformTaskListener.createWindowDecoration(change, startT, finishT); + break; + case WINDOWING_MODE_FULLSCREEN: + mFullscreenTaskListener.createWindowDecoration(change, startT, finishT); + break; + default: + return false; } - mFreeformTaskListener.createWindowDecoration(change, startT, finishT); // Intercepted transition to manage the window decorations. Let other handlers animate. return false; @@ -164,15 +174,22 @@ public class FreeformTaskTransitionHandler ArrayList<AutoCloseable> windowDecors, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { - if (change.getTaskInfo().getWindowingMode() != WINDOWING_MODE_FREEFORM) { - return false; + final AutoCloseable windowDecor; + switch (change.getTaskInfo().getWindowingMode()) { + case WINDOWING_MODE_FREEFORM: + windowDecor = mFreeformTaskListener.giveWindowDecoration(change.getTaskInfo(), + startT, finishT); + break; + case WINDOWING_MODE_FULLSCREEN: + windowDecor = mFullscreenTaskListener.giveWindowDecoration(change.getTaskInfo(), + startT, finishT); + break; + default: + windowDecor = null; } - final AutoCloseable windowDecor = - mFreeformTaskListener.giveWindowDecoration(change.getTaskInfo(), startT, finishT); if (windowDecor != null) { windowDecors.add(windowDecor); } - // Intercepted transition to manage the window decorations. Let other handlers animate. return false; } @@ -197,24 +214,29 @@ public class FreeformTaskTransitionHandler } boolean handled = false; + boolean adopted = false; final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); if (type == Transitions.TRANSIT_MAXIMIZE && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { handled = true; windowDecor = mFreeformTaskListener.giveWindowDecoration( change.getTaskInfo(), startT, finishT); - // TODO(b/235638450): Let fullscreen task listener adopt the window decor. + adopted = mFullscreenTaskListener.adoptWindowDecoration(change, + startT, finishT, windowDecor); } if (type == Transitions.TRANSIT_RESTORE_FROM_MAXIMIZE && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { handled = true; - // TODO(b/235638450): Let fullscreen task listener transfer the window decor. - mFreeformTaskListener.adoptWindowDecoration(change, startT, finishT, windowDecor); + windowDecor = mFullscreenTaskListener.giveWindowDecoration( + change.getTaskInfo(), startT, finishT); + adopted = mFreeformTaskListener.adoptWindowDecoration(change, + startT, finishT, windowDecor); } - releaseWindowDecor(windowDecor); - + if (!adopted) { + releaseWindowDecor(windowDecor); + } return handled; } @@ -225,7 +247,7 @@ public class FreeformTaskTransitionHandler releaseWindowDecor(windowDecor); } mFreeformTaskListener.onTaskTransitionFinished(); - // TODO(b/235638450): Dispatch it to fullscreen task listener. + mFullscreenTaskListener.onTaskTransitionFinished(); finishCallback.onTransitionFinished(null, null); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java index 0ba4afc24c45..0d75bc451b72 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java @@ -16,16 +16,21 @@ package com.android.wm.shell.fullscreen; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; + import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN; import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString; +import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; import android.graphics.Point; -import android.util.Slog; +import android.util.Log; import android.util.SparseArray; import android.view.SurfaceControl; +import android.window.TransitionInfo; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.ShellTaskOrganizer; @@ -34,36 +39,49 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.windowdecor.WindowDecorViewModel; import java.io.PrintWriter; import java.util.Optional; /** * Organizes tasks presented in {@link android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN}. + * @param <T> the type of window decoration instance */ -public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { +public class FullscreenTaskListener<T extends AutoCloseable> + implements ShellTaskOrganizer.TaskListener { private static final String TAG = "FullscreenTaskListener"; private final ShellTaskOrganizer mShellTaskOrganizer; - private final SyncTransactionQueue mSyncQueue; - private final Optional<RecentTasksController> mRecentTasksOptional; - private final SparseArray<TaskData> mDataByTaskId = new SparseArray<>(); + private final SparseArray<State<T>> mTasks = new SparseArray<>(); + private final SparseArray<T> mWindowDecorOfVanishedTasks = new SparseArray<>(); + private static class State<T extends AutoCloseable> { + RunningTaskInfo mTaskInfo; + SurfaceControl mLeash; + T mWindowDecoration; + } + private final SyncTransactionQueue mSyncQueue; + private final Optional<RecentTasksController> mRecentTasksOptional; + private final Optional<WindowDecorViewModel<T>> mWindowDecorViewModelOptional; /** * This constructor is used by downstream products. */ public FullscreenTaskListener(SyncTransactionQueue syncQueue) { - this(null /* shellInit */, null /* shellTaskOrganizer */, syncQueue, Optional.empty()); + this(null /* shellInit */, null /* shellTaskOrganizer */, syncQueue, Optional.empty(), + Optional.empty()); } public FullscreenTaskListener(ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, - Optional<RecentTasksController> recentTasksOptional) { + Optional<RecentTasksController> recentTasksOptional, + Optional<WindowDecorViewModel<T>> windowDecorViewModelOptional) { mShellTaskOrganizer = shellTaskOrganizer; mSyncQueue = syncQueue; mRecentTasksOptional = recentTasksOptional; + mWindowDecorViewModelOptional = windowDecorViewModelOptional; // Note: Some derivative FullscreenTaskListener implementations do not use ShellInit if (shellInit != null) { shellInit.addInitCallback(this::onInit, this); @@ -76,55 +94,204 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { @Override public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { - if (mDataByTaskId.get(taskInfo.taskId) != null) { + if (mTasks.get(taskInfo.taskId) != null) { throw new IllegalStateException("Task appeared more than once: #" + taskInfo.taskId); } ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Appeared: #%d", taskInfo.taskId); final Point positionInParent = taskInfo.positionInParent; - mDataByTaskId.put(taskInfo.taskId, new TaskData(leash, positionInParent)); - - if (Transitions.ENABLE_SHELL_TRANSITIONS) return; - mSyncQueue.runInSync(t -> { - // Reset several properties back to fullscreen (PiP, for example, leaves all these - // properties in a bad state). - t.setWindowCrop(leash, null); - t.setPosition(leash, positionInParent.x, positionInParent.y); - t.setAlpha(leash, 1f); - t.setMatrix(leash, 1, 0, 0, 1); - t.show(leash); - }); + final State<T> state = new State(); + state.mLeash = leash; + state.mTaskInfo = taskInfo; + mTasks.put(taskInfo.taskId, state); updateRecentsForVisibleFullscreenTask(taskInfo); + if (Transitions.ENABLE_SHELL_TRANSITIONS) return; + if (shouldShowWindowDecor(taskInfo) && mWindowDecorViewModelOptional.isPresent()) { + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + state.mWindowDecoration = + mWindowDecorViewModelOptional.get().createWindowDecoration(taskInfo, + leash, t, t); + t.apply(); + } else { + mSyncQueue.runInSync(t -> { + // Reset several properties back to fullscreen (PiP, for example, leaves all these + // properties in a bad state). + t.setWindowCrop(leash, null); + t.setPosition(leash, positionInParent.x, positionInParent.y); + t.setAlpha(leash, 1f); + t.setMatrix(leash, 1, 0, 0, 1); + t.show(leash); + }); + } } @Override public void onTaskInfoChanged(RunningTaskInfo taskInfo) { - if (Transitions.ENABLE_SHELL_TRANSITIONS) return; - + final State<T> state = mTasks.get(taskInfo.taskId); + final Point oldPositionInParent = state.mTaskInfo.positionInParent; + state.mTaskInfo = taskInfo; + if (state.mWindowDecoration != null) { + mWindowDecorViewModelOptional.get().onTaskInfoChanged( + state.mTaskInfo, state.mWindowDecoration); + } updateRecentsForVisibleFullscreenTask(taskInfo); + if (Transitions.ENABLE_SHELL_TRANSITIONS) return; - final TaskData data = mDataByTaskId.get(taskInfo.taskId); - final Point positionInParent = taskInfo.positionInParent; - if (!positionInParent.equals(data.positionInParent)) { - data.positionInParent.set(positionInParent.x, positionInParent.y); + final Point positionInParent = state.mTaskInfo.positionInParent; + if (!oldPositionInParent.equals(state.mTaskInfo.positionInParent)) { mSyncQueue.runInSync(t -> { - t.setPosition(data.surface, positionInParent.x, positionInParent.y); + t.setPosition(state.mLeash, positionInParent.x, positionInParent.y); }); } } @Override - public void onTaskVanished(RunningTaskInfo taskInfo) { - if (mDataByTaskId.get(taskInfo.taskId) == null) { - Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId); + public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { + final State<T> state = mTasks.get(taskInfo.taskId); + if (state == null) { + // This is possible if the transition happens before this method. return; } - - mDataByTaskId.remove(taskInfo.taskId); - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Vanished: #%d", taskInfo.taskId); + mTasks.remove(taskInfo.taskId); + + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + // Save window decorations of closing tasks so that we can hand them over to the + // transition system if this method happens before the transition. In case where the + // transition didn't happen, it'd be cleared when the next transition finished. + if (state.mWindowDecoration != null) { + mWindowDecorOfVanishedTasks.put(taskInfo.taskId, state.mWindowDecoration); + } + return; + } + releaseWindowDecor(state.mWindowDecoration); + } + + /** + * Creates a window decoration for a transition. + * + * @param change the change of this task transition that needs to have the task layer as the + * leash + */ + public void createWindowDecoration(TransitionInfo.Change change, + SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { + final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash()); + if (!mWindowDecorViewModelOptional.isPresent() + || !shouldShowWindowDecor(state.mTaskInfo)) { + return; + } + + state.mWindowDecoration = mWindowDecorViewModelOptional.get().createWindowDecoration( + state.mTaskInfo, state.mLeash, startT, finishT); + } + + /** + * Adopt the incoming window decoration and lets the window decoration prepare for a transition. + * + * @param change the change of this task transition that needs to have the task layer as the + * leash + * @param startT the start transaction of this transition + * @param finishT the finish transaction of this transition + * @param windowDecor the window decoration to adopt + * @return {@code true} if it adopts the window decoration; {@code false} otherwise + */ + public boolean adoptWindowDecoration( + TransitionInfo.Change change, + SurfaceControl.Transaction startT, + SurfaceControl.Transaction finishT, + @Nullable AutoCloseable windowDecor) { + if (!mWindowDecorViewModelOptional.isPresent() + || !shouldShowWindowDecor(change.getTaskInfo())) { + return false; + } + final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash()); + state.mWindowDecoration = mWindowDecorViewModelOptional.get().adoptWindowDecoration( + windowDecor); + if (state.mWindowDecoration != null) { + mWindowDecorViewModelOptional.get().setupWindowDecorationForTransition( + state.mTaskInfo, startT, finishT, state.mWindowDecoration); + return true; + } else { + state.mWindowDecoration = mWindowDecorViewModelOptional.get().createWindowDecoration( + state.mTaskInfo, state.mLeash, startT, finishT); + return false; + } + } + + /** + * Clear window decors of vanished tasks. + */ + public void onTaskTransitionFinished() { + if (mWindowDecorOfVanishedTasks.size() == 0) { + return; + } + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + "Clearing window decors of vanished tasks. There could be visual defects " + + "if any of them is used later in transitions."); + for (int i = 0; i < mWindowDecorOfVanishedTasks.size(); ++i) { + releaseWindowDecor(mWindowDecorOfVanishedTasks.valueAt(i)); + } + mWindowDecorOfVanishedTasks.clear(); + } + + /** + * Gives out the ownership of the task's window decoration. The given task is leaving (of has + * left) this task listener. This is the transition system asking for the ownership. + * + * @param taskInfo the maximizing task + * @return the window decor of the maximizing task if any + */ + public T giveWindowDecoration( + ActivityManager.RunningTaskInfo taskInfo, + SurfaceControl.Transaction startT, + SurfaceControl.Transaction finishT) { + T windowDecor; + final State<T> state = mTasks.get(taskInfo.taskId); + if (state != null) { + windowDecor = state.mWindowDecoration; + state.mWindowDecoration = null; + } else { + windowDecor = + mWindowDecorOfVanishedTasks.removeReturnOld(taskInfo.taskId); + } + if (mWindowDecorViewModelOptional.isPresent()) { + mWindowDecorViewModelOptional.get().setupWindowDecorationForTransition( + taskInfo, startT, finishT, windowDecor); + } + + return windowDecor; + } + + private State<T> createOrUpdateTaskState(ActivityManager.RunningTaskInfo taskInfo, + SurfaceControl leash) { + State<T> state = mTasks.get(taskInfo.taskId); + if (state != null) { + updateTaskInfo(taskInfo); + return state; + } + + state = new State<T>(); + state.mTaskInfo = taskInfo; + state.mLeash = leash; + mTasks.put(taskInfo.taskId, state); + + return state; + } + + private State<T> updateTaskInfo(ActivityManager.RunningTaskInfo taskInfo) { + final State<T> state = mTasks.get(taskInfo.taskId); + state.mTaskInfo = taskInfo; + return state; + } + + private void releaseWindowDecor(T windowDecor) { + try { + windowDecor.close(); + } catch (Exception e) { + Log.e(TAG, "Failed to release window decoration.", e); + } } private void updateRecentsForVisibleFullscreenTask(RunningTaskInfo taskInfo) { @@ -148,17 +315,17 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { } private SurfaceControl findTaskSurface(int taskId) { - if (!mDataByTaskId.contains(taskId)) { + if (!mTasks.contains(taskId)) { throw new IllegalArgumentException("There is no surface for taskId=" + taskId); } - return mDataByTaskId.get(taskId).surface; + return mTasks.get(taskId).mLeash; } @Override public void dump(@NonNull PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; pw.println(prefix + this); - pw.println(innerPrefix + mDataByTaskId.size() + " Tasks"); + pw.println(innerPrefix + mTasks.size() + " Tasks"); } @Override @@ -166,16 +333,10 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { return TAG + ":" + taskListenerTypeToString(TASK_LISTENER_TYPE_FULLSCREEN); } - /** - * Per-task data for each managed task. - */ - private static class TaskData { - public final SurfaceControl surface; - public final Point positionInParent; - - public TaskData(SurfaceControl surface, Point positionInParent) { - this.surface = surface; - this.positionInParent = positionInParent; - } + private static boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) { + return taskInfo.getConfiguration().windowConfiguration.getDisplayWindowingMode() + == WINDOWING_MODE_FREEFORM; } + + } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java index 76c0f41997ad..7129165a78dc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java @@ -37,16 +37,6 @@ public interface OneHanded { } /** - * Return one handed settings enabled or not. - */ - boolean isOneHandedEnabled(); - - /** - * Return swipe to notification settings enabled or not. - */ - boolean isSwipeToNotificationEnabled(); - - /** * Enters one handed mode. */ void startOneHanded(); @@ -80,9 +70,4 @@ public interface OneHanded { * transition start or finish */ void registerTransitionCallback(OneHandedTransitionCallback callback); - - /** - * Notifies when user switch complete - */ - void onUserSwitch(int userId); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java index 9149204b94ce..e0c4fe8c4fba 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java @@ -59,6 +59,7 @@ import com.android.wm.shell.sysui.KeyguardChangeListener; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.sysui.UserChangeListener; import java.io.PrintWriter; @@ -67,7 +68,7 @@ import java.io.PrintWriter; */ public class OneHandedController implements RemoteCallable<OneHandedController>, DisplayChangeController.OnDisplayChangingListener, ConfigurationChangeListener, - KeyguardChangeListener { + KeyguardChangeListener, UserChangeListener { private static final String TAG = "OneHandedController"; private static final String ONE_HANDED_MODE_OFFSET_PERCENTAGE = @@ -76,8 +77,8 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, public static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode"; - private volatile boolean mIsOneHandedEnabled; - private volatile boolean mIsSwipeToNotificationEnabled; + private boolean mIsOneHandedEnabled; + private boolean mIsSwipeToNotificationEnabled; private boolean mIsShortcutEnabled; private boolean mTaskChangeToExit; private boolean mLockedDisabled; @@ -294,6 +295,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, mState.addSListeners(mTutorialHandler); mShellController.addConfigurationChangeListener(this); mShellController.addKeyguardChangeListener(this); + mShellController.addUserChangeListener(this); } public OneHanded asOneHanded() { @@ -627,7 +629,8 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, stopOneHanded(); } - private void onUserSwitch(int newUserId) { + @Override + public void onUserChanged(int newUserId, @NonNull Context userContext) { unregisterSettingObservers(); mUserId = newUserId; registerSettingObservers(newUserId); @@ -718,18 +721,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, } @Override - public boolean isOneHandedEnabled() { - // This is volatile so return directly - return mIsOneHandedEnabled; - } - - @Override - public boolean isSwipeToNotificationEnabled() { - // This is volatile so return directly - return mIsSwipeToNotificationEnabled; - } - - @Override public void startOneHanded() { mMainExecutor.execute(() -> { OneHandedController.this.startOneHanded(); @@ -770,13 +761,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, OneHandedController.this.registerTransitionCallback(callback); }); } - - @Override - public void onUserSwitch(int userId) { - mMainExecutor.execute(() -> { - OneHandedController.this.onUserSwitch(userId); - }); - } } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java index 93172f82edd1..c06881ae6ad7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java @@ -51,12 +51,6 @@ public interface Pip { } /** - * Registers the session listener for the current user. - */ - default void registerSessionListenerForCurrentUser() { - } - - /** * Sets both shelf visibility and its height. * * @param visible visibility of shelf. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java index 4942987742a0..281ea530e9e1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java @@ -31,8 +31,6 @@ import android.os.RemoteException; import android.util.Size; import android.view.MotionEvent; import android.view.SurfaceControl; -import android.view.SyncRtSurfaceTransactionApplier; -import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; import android.view.WindowManagerGlobal; import com.android.internal.protolog.common.ProtoLog; @@ -42,6 +40,7 @@ import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipMediaController.ActionListener; import com.android.wm.shell.pip.PipMenuController; +import com.android.wm.shell.pip.PipSurfaceTransactionHelper; import com.android.wm.shell.pip.PipUiEventLogger; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -115,6 +114,10 @@ public class PhonePipMenuController implements PipMenuController { private final ShellExecutor mMainExecutor; private final Handler mMainHandler; + private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory + mSurfaceControlTransactionFactory; + private final float[] mTmpTransform = new float[9]; + private final ArrayList<Listener> mListeners = new ArrayList<>(); private final SystemWindows mSystemWindows; private final Optional<SplitScreenController> mSplitScreenController; @@ -124,7 +127,6 @@ public class PhonePipMenuController implements PipMenuController { private RemoteAction mCloseAction; private List<RemoteAction> mMediaActions; - private SyncRtSurfaceTransactionApplier mApplier; private int mMenuState; private PipMenuView mPipMenuView; @@ -150,6 +152,9 @@ public class PhonePipMenuController implements PipMenuController { mMainHandler = mainHandler; mSplitScreenController = splitScreenOptional; mPipUiEventLogger = pipUiEventLogger; + + mSurfaceControlTransactionFactory = + new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory(); } public boolean isMenuVisible() { @@ -194,7 +199,6 @@ public class PhonePipMenuController implements PipMenuController { return; } - mApplier = null; mSystemWindows.removeView(mPipMenuView); mPipMenuView = null; } @@ -289,7 +293,7 @@ public class PhonePipMenuController implements PipMenuController { willResizeMenu, withDelay, showResizeHandle, Debug.getCallers(5, " ")); } - if (!maybeCreateSyncApplier()) { + if (!checkPipMenuState()) { return; } @@ -312,7 +316,7 @@ public class PhonePipMenuController implements PipMenuController { return; } - if (!maybeCreateSyncApplier()) { + if (!checkPipMenuState()) { return; } @@ -328,18 +332,15 @@ public class PhonePipMenuController implements PipMenuController { mTmpSourceRectF.set(mTmpSourceBounds); mTmpDestinationRectF.set(destinationBounds); mMoveTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL); - SurfaceControl surfaceControl = getSurfaceControl(); - SurfaceParams params = new SurfaceParams.Builder(surfaceControl) - .withMatrix(mMoveTransform) - .build(); + final SurfaceControl surfaceControl = getSurfaceControl(); + final SurfaceControl.Transaction menuTx = + mSurfaceControlTransactionFactory.getTransaction(); + menuTx.setMatrix(surfaceControl, mMoveTransform, mTmpTransform); if (pipLeash != null && t != null) { - SurfaceParams pipParams = new SurfaceParams.Builder(pipLeash) - .withMergeTransaction(t) - .build(); - mApplier.scheduleApply(params, pipParams); - } else { - mApplier.scheduleApply(params); + // Merge the two transactions, vsyncId has been set on menuTx. + menuTx.merge(t); } + menuTx.apply(); } /** @@ -353,36 +354,29 @@ public class PhonePipMenuController implements PipMenuController { return; } - if (!maybeCreateSyncApplier()) { + if (!checkPipMenuState()) { return; } - SurfaceControl surfaceControl = getSurfaceControl(); - SurfaceParams params = new SurfaceParams.Builder(surfaceControl) - .withWindowCrop(destinationBounds) - .build(); + final SurfaceControl surfaceControl = getSurfaceControl(); + final SurfaceControl.Transaction menuTx = + mSurfaceControlTransactionFactory.getTransaction(); + menuTx.setCrop(surfaceControl, destinationBounds); if (pipLeash != null && t != null) { - SurfaceParams pipParams = new SurfaceParams.Builder(pipLeash) - .withMergeTransaction(t) - .build(); - mApplier.scheduleApply(params, pipParams); - } else { - mApplier.scheduleApply(params); + // Merge the two transactions, vsyncId has been set on menuTx. + menuTx.merge(t); } + menuTx.apply(); } - private boolean maybeCreateSyncApplier() { + private boolean checkPipMenuState() { if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: Not going to move PiP, either menu or its parent is not created.", TAG); return false; } - if (mApplier == null) { - mApplier = new SyncRtSurfaceTransactionApplier(mPipMenuView); - } - - return mApplier != null; + return true; } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index fc97f310ad4e..ac3407dd1ca1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -92,6 +92,7 @@ import com.android.wm.shell.sysui.KeyguardChangeListener; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.sysui.UserChangeListener; import com.android.wm.shell.transition.Transitions; import java.io.PrintWriter; @@ -105,7 +106,8 @@ import java.util.function.Consumer; * Manages the picture-in-picture (PIP) UI and states for Phones. */ public class PipController implements PipTransitionController.PipTransitionCallback, - RemoteCallable<PipController>, ConfigurationChangeListener, KeyguardChangeListener { + RemoteCallable<PipController>, ConfigurationChangeListener, KeyguardChangeListener, + UserChangeListener { private static final String TAG = "PipController"; private Context mContext; @@ -528,7 +530,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb }); mOneHandedController.ifPresent(controller -> { - controller.asOneHanded().registerTransitionCallback( + controller.registerTransitionCallback( new OneHandedTransitionCallback() { @Override public void onStartFinished(Rect bounds) { @@ -542,8 +544,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb }); }); + mMediaController.registerSessionListenerForCurrentUser(); + mShellController.addConfigurationChangeListener(this); mShellController.addKeyguardChangeListener(this); + mShellController.addUserChangeListener(this); } @Override @@ -557,6 +562,12 @@ public class PipController implements PipTransitionController.PipTransitionCallb } @Override + public void onUserChanged(int newUserId, @NonNull Context userContext) { + // Re-register the media session listener when switching users + mMediaController.registerSessionListenerForCurrentUser(); + } + + @Override public void onConfigurationChanged(Configuration newConfig) { mPipBoundsAlgorithm.onConfigurationChanged(mContext); mTouchHandler.onConfigurationChanged(); @@ -644,10 +655,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb } } - private void registerSessionListenerForCurrentUser() { - mMediaController.registerSessionListenerForCurrentUser(); - } - private void onSystemUiStateChanged(boolean isValidState, int flag) { mTouchHandler.onSystemUiStateChanged(isValidState); } @@ -968,13 +975,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb } @Override - public void registerSessionListenerForCurrentUser() { - mMainExecutor.execute(() -> { - PipController.this.registerSessionListenerForCurrentUser(); - }); - } - - @Override public void setShelfHeight(boolean visible, int height) { mMainExecutor.execute(() -> { PipController.this.setShelfHeight(visible, height); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java index 0f3ff36601fb..8e3376f163c1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java @@ -146,11 +146,8 @@ public class PipInputConsumer { "%s: Failed to create input consumer, %s", TAG, e); } mMainExecutor.execute(() -> { - // Choreographer.getSfInstance() must be called on the thread that the input event - // receiver should be receiving events - // TODO(b/222697646): remove getSfInstance usage and use vsyncId for transactions mInputEventReceiver = new InputEventReceiver(inputChannel, - Looper.myLooper(), Choreographer.getSfInstance()); + Looper.myLooper(), Choreographer.getInstance()); if (mRegistrationListener != null) { mRegistrationListener.onRegistrationChanged(true /* isRegistered */); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java index 44d22029a5e9..afb64c9eec41 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java @@ -33,6 +33,7 @@ import android.content.Context; import android.graphics.PointF; import android.graphics.Rect; import android.os.Debug; +import android.os.SystemProperties; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; @@ -58,6 +59,8 @@ import kotlin.jvm.functions.Function0; public class PipMotionHelper implements PipAppOpsListener.Callback, FloatingContentCoordinator.FloatingContent { + public static final boolean ENABLE_FLING_TO_DISMISS_PIP = + SystemProperties.getBoolean("persist.wm.debug.fling_to_dismiss_pip", true); private static final String TAG = "PipMotionHelper"; private static final boolean DEBUG = false; @@ -704,6 +707,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, loc[1] = animatedPipBounds.top; } }; + mMagnetizedPip.setFlingToTargetEnabled(ENABLE_FLING_TO_DISMISS_PIP); } return mMagnetizedPip; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java index abf1a9500e6d..89d85e4b292d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java @@ -625,8 +625,7 @@ public class PipResizeGestureHandler { class PipResizeInputEventReceiver extends BatchedInputEventReceiver { PipResizeInputEventReceiver(InputChannel channel, Looper looper) { - // TODO(b/222697646): remove getSfInstance usage and use vsyncId for transactions - super(channel, looper, Choreographer.getSfInstance()); + super(channel, looper, Choreographer.getInstance()); } public void onInputEvent(InputEvent event) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java index a24d9618032d..4e1b0469eb96 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java @@ -32,6 +32,8 @@ import android.graphics.Rect; import android.os.RemoteException; import android.view.Gravity; +import androidx.annotation.NonNull; + import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.WindowManagerShellWrapper; @@ -51,6 +53,8 @@ import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.sysui.ConfigurationChangeListener; import com.android.wm.shell.sysui.ShellController; +import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.sysui.UserChangeListener; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -64,7 +68,7 @@ import java.util.Set; public class TvPipController implements PipTransitionController.PipTransitionCallback, TvPipBoundsController.PipBoundsListener, TvPipMenuController.Delegate, TvPipNotificationController.Delegate, DisplayController.OnDisplaysChangedListener, - ConfigurationChangeListener { + ConfigurationChangeListener, UserChangeListener { private static final String TAG = "TvPipController"; static final boolean DEBUG = false; @@ -105,6 +109,11 @@ public class TvPipController implements PipTransitionController.PipTransitionCal private final PipMediaController mPipMediaController; private final TvPipNotificationController mPipNotificationController; private final TvPipMenuController mTvPipMenuController; + private final PipTransitionController mPipTransitionController; + private final TaskStackListenerImpl mTaskStackListener; + private final PipParamsChangedForwarder mPipParamsChangedForwarder; + private final DisplayController mDisplayController; + private final WindowManagerShellWrapper mWmShellWrapper; private final ShellExecutor mMainExecutor; private final TvPipImpl mImpl = new TvPipImpl(); @@ -121,6 +130,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal public static Pip create( Context context, + ShellInit shellInit, ShellController shellController, TvPipBoundsState tvPipBoundsState, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, @@ -138,6 +148,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal ShellExecutor mainExecutor) { return new TvPipController( context, + shellInit, shellController, tvPipBoundsState, tvPipBoundsAlgorithm, @@ -157,6 +168,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal private TvPipController( Context context, + ShellInit shellInit, ShellController shellController, TvPipBoundsState tvPipBoundsState, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, @@ -170,11 +182,12 @@ public class TvPipController implements PipTransitionController.PipTransitionCal TaskStackListenerImpl taskStackListener, PipParamsChangedForwarder pipParamsChangedForwarder, DisplayController displayController, - WindowManagerShellWrapper wmShell, + WindowManagerShellWrapper wmShellWrapper, ShellExecutor mainExecutor) { mContext = context; mMainExecutor = mainExecutor; mShellController = shellController; + mDisplayController = displayController; mTvPipBoundsState = tvPipBoundsState; mTvPipBoundsState.setDisplayId(context.getDisplayId()); @@ -193,16 +206,32 @@ public class TvPipController implements PipTransitionController.PipTransitionCal mAppOpsListener = pipAppOpsListener; mPipTaskOrganizer = pipTaskOrganizer; - pipTransitionController.registerPipTransitionCallback(this); + mPipTransitionController = pipTransitionController; + mPipParamsChangedForwarder = pipParamsChangedForwarder; + mTaskStackListener = taskStackListener; + mWmShellWrapper = wmShellWrapper; + shellInit.addInitCallback(this::onInit, this); + } + + private void onInit() { + mPipTransitionController.registerPipTransitionCallback(this); loadConfigurations(); - registerPipParamsChangedListener(pipParamsChangedForwarder); - registerTaskStackListenerCallback(taskStackListener); - registerWmShellPinnedStackListener(wmShell); - displayController.addDisplayWindowListener(this); + registerPipParamsChangedListener(mPipParamsChangedForwarder); + registerTaskStackListenerCallback(mTaskStackListener); + registerWmShellPinnedStackListener(mWmShellWrapper); + registerSessionListenerForCurrentUser(); + mDisplayController.addDisplayWindowListener(this); mShellController.addConfigurationChangeListener(this); + mShellController.addUserChangeListener(this); + } + + @Override + public void onUserChanged(int newUserId, @NonNull Context userContext) { + // Re-register the media session listener when switching users + registerSessionListenerForCurrentUser(); } @Override @@ -679,11 +708,6 @@ public class TvPipController implements PipTransitionController.PipTransitionCal } private class TvPipImpl implements Pip { - @Override - public void registerSessionListenerForCurrentUser() { - mMainExecutor.execute(() -> { - TvPipController.this.registerSessionListenerForCurrentUser(); - }); - } + // Not used } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java index b2961518b66a..93c75299a64b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java @@ -46,6 +46,8 @@ public enum ShellProtoLogGroup implements IProtoLogGroup { Consts.TAG_WM_SHELL), WM_SHELL_SYSUI_EVENTS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_SHELL), + WM_SHELL_DESKTOP_MODE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, + Consts.TAG_WM_SHELL), TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest"); private final boolean mEnabled; 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 7e83d2fa0a0b..21fc01e554c8 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 @@ -25,6 +25,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; +import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.RemoteAnimationTarget.MODE_OPENING; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; @@ -488,13 +489,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final WindowContainerTransaction wct = new WindowContainerTransaction(); options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, wct); - // If split still not active, apply windows bounds first to avoid surface reset to - // wrong pos by SurfaceAnimator from wms. - // TODO(b/223325631): check is it still necessary after improve enter transition done. - if (!mMainStage.isActive()) { - updateWindowBounds(mSplitLayout, wct); - } - wct.sendPendingIntent(intent, fillInIntent, options); mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct); } @@ -641,7 +635,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, wct.startTask(sideTaskId, sideOptions); } // Using legacy transitions, so we can't use blast sync since it conflicts. - mTaskOrganizer.applyTransaction(wct); + mSyncQueue.queue(wct); mSyncQueue.runInSync(t -> { setDividerVisibility(true, t); updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */); @@ -893,10 +887,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mShouldUpdateRecents = false; mIsDividerRemoteAnimating = false; + mSplitLayout.getInvisibleBounds(mTempRect1); if (childrenToTop == null) { mSideStage.removeAllTasks(wct, false /* toTop */); mMainStage.deactivate(wct, false /* toTop */); wct.reorder(mRootTaskInfo.token, false /* onTop */); + wct.setForceTranslucent(mRootTaskInfo.token, true); + wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1); onTransitionAnimationComplete(); } else { // Expand to top side split as full screen for fading out decor animation and dismiss @@ -907,27 +904,32 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, ? mSideStage : mMainStage; tempFullStage.resetBounds(wct); wct.setSmallestScreenWidthDp(tempFullStage.mRootTaskInfo.token, - mRootTaskInfo.configuration.smallestScreenWidthDp); + SMALLEST_SCREEN_WIDTH_DP_UNDEFINED); dismissStage.dismiss(wct, false /* toTop */); } mSyncQueue.queue(wct); mSyncQueue.runInSync(t -> { t.setWindowCrop(mMainStage.mRootLeash, null) .setWindowCrop(mSideStage.mRootLeash, null); - t.setPosition(mMainStage.mRootLeash, 0, 0) - .setPosition(mSideStage.mRootLeash, 0, 0); t.hide(mMainStage.mDimLayer).hide(mSideStage.mDimLayer); setDividerVisibility(false, t); - // In this case, exit still under progress, fade out the split decor after first WCT - // done and do remaining WCT after animation finished. - if (childrenToTop != null) { + if (childrenToTop == null) { + t.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.right); + } else { + // In this case, exit still under progress, fade out the split decor after first WCT + // done and do remaining WCT after animation finished. childrenToTop.fadeOutDecor(() -> { WindowContainerTransaction finishedWCT = new WindowContainerTransaction(); mIsExiting = false; childrenToTop.dismiss(finishedWCT, true /* toTop */); finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */); - mTaskOrganizer.applyTransaction(finishedWCT); + finishedWCT.setForceTranslucent(mRootTaskInfo.token, true); + finishedWCT.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1); + mSyncQueue.queue(finishedWCT); + mSyncQueue.runInSync(at -> { + at.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.right); + }); onTransitionAnimationComplete(); }); } @@ -996,6 +998,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mMainStage.activate(wct, true /* includingTopTask */); updateWindowBounds(mSplitLayout, wct); wct.reorder(mRootTaskInfo.token, true); + wct.setForceTranslucent(mRootTaskInfo.token, false); } void finishEnterSplitScreen(SurfaceControl.Transaction t) { @@ -1221,7 +1224,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Make the stages adjacent to each other so they occlude what's behind them. wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token); wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token); - mTaskOrganizer.applyTransaction(wct); + wct.setForceTranslucent(mRootTaskInfo.token, true); + mSplitLayout.getInvisibleBounds(mTempRect1); + wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1); + mSyncQueue.queue(wct); + mSyncQueue.runInSync(t -> { + t.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.top); + }); } private void onRootTaskVanished() { @@ -1377,10 +1386,17 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // TODO (b/238697912) : Add the validation to prevent entering non-recovered status final WindowContainerTransaction wct = new WindowContainerTransaction(); mSplitLayout.init(); - prepareEnterSplitScreen(wct); + mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT); + mMainStage.activate(wct, true /* includingTopTask */); + updateWindowBounds(mSplitLayout, wct); + wct.reorder(mRootTaskInfo.token, true); + wct.setForceTranslucent(mRootTaskInfo.token, false); mSyncQueue.queue(wct); - mSyncQueue.runInSync(t -> - updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */)); + mSyncQueue.runInSync(t -> { + updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */); + + mSplitLayout.flingDividerToCenter(); + }); } if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) { mShouldUpdateRecents = true; @@ -1822,6 +1838,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // properly for the animation itself. mSplitLayout.release(); mSplitLayout.resetDividerPosition(); + mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT; mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.java index 1c0b35894acd..9df863163b50 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.java @@ -21,13 +21,13 @@ package com.android.wm.shell.sysui; */ public interface KeyguardChangeListener { /** - * Notifies the Shell that the keyguard is showing (and if so, whether it is occluded). + * Called when the keyguard is showing (and if so, whether it is occluded). */ default void onKeyguardVisibilityChanged(boolean visible, boolean occluded, boolean animatingDismiss) {} /** - * Notifies the Shell when the keyguard dismiss animation has finished. + * Called when the keyguard dismiss animation has finished. * * TODO(b/206741900) deprecate this path once we're able to animate the PiP window as part of * keyguard dismiss animation. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java index 52ffb46bb39c..57993948886b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java @@ -25,7 +25,9 @@ import static android.content.pm.ActivityInfo.CONFIG_UI_MODE; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SYSUI_EVENTS; +import android.content.Context; import android.content.pm.ActivityInfo; +import android.content.pm.UserInfo; import android.content.res.Configuration; import androidx.annotation.NonNull; @@ -36,6 +38,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.annotations.ExternalThread; import java.io.PrintWriter; +import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; /** @@ -53,6 +56,9 @@ public class ShellController { new CopyOnWriteArrayList<>(); private final CopyOnWriteArrayList<KeyguardChangeListener> mKeyguardChangeListeners = new CopyOnWriteArrayList<>(); + private final CopyOnWriteArrayList<UserChangeListener> mUserChangeListeners = + new CopyOnWriteArrayList<>(); + private Configuration mLastConfiguration; @@ -102,6 +108,22 @@ public class ShellController { mKeyguardChangeListeners.remove(listener); } + /** + * Adds a new user-change listener. The user change callbacks are not made in any + * particular order. + */ + public void addUserChangeListener(UserChangeListener listener) { + mUserChangeListeners.remove(listener); + mUserChangeListeners.add(listener); + } + + /** + * Removes an existing user-change listener. + */ + public void removeUserChangeListener(UserChangeListener listener) { + mUserChangeListeners.remove(listener); + } + @VisibleForTesting void onConfigurationChanged(Configuration newConfig) { // The initial config is send on startup and doesn't trigger listener callbacks @@ -144,6 +166,8 @@ public class ShellController { @VisibleForTesting void onKeyguardVisibilityChanged(boolean visible, boolean occluded, boolean animatingDismiss) { + ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Keyguard visibility changed: visible=%b " + + "occluded=%b animatingDismiss=%b", visible, occluded, animatingDismiss); for (KeyguardChangeListener listener : mKeyguardChangeListeners) { listener.onKeyguardVisibilityChanged(visible, occluded, animatingDismiss); } @@ -151,17 +175,35 @@ public class ShellController { @VisibleForTesting void onKeyguardDismissAnimationFinished() { + ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Keyguard dismiss animation finished"); for (KeyguardChangeListener listener : mKeyguardChangeListeners) { listener.onKeyguardDismissAnimationFinished(); } } + @VisibleForTesting + void onUserChanged(int newUserId, @NonNull Context userContext) { + ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "User changed: id=%d", newUserId); + for (UserChangeListener listener : mUserChangeListeners) { + listener.onUserChanged(newUserId, userContext); + } + } + + @VisibleForTesting + void onUserProfilesChanged(@NonNull List<UserInfo> profiles) { + ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "User profiles changed"); + for (UserChangeListener listener : mUserChangeListeners) { + listener.onUserProfilesChanged(profiles); + } + } + public void dump(@NonNull PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; pw.println(prefix + TAG); pw.println(innerPrefix + "mConfigChangeListeners=" + mConfigChangeListeners.size()); pw.println(innerPrefix + "mLastConfiguration=" + mLastConfiguration); pw.println(innerPrefix + "mKeyguardChangeListeners=" + mKeyguardChangeListeners.size()); + pw.println(innerPrefix + "mUserChangeListeners=" + mUserChangeListeners.size()); } /** @@ -220,5 +262,17 @@ public class ShellController { mMainExecutor.execute(() -> ShellController.this.onKeyguardDismissAnimationFinished()); } + + @Override + public void onUserChanged(int newUserId, @NonNull Context userContext) { + mMainExecutor.execute(() -> + ShellController.this.onUserChanged(newUserId, userContext)); + } + + @Override + public void onUserProfilesChanged(@NonNull List<UserInfo> profiles) { + mMainExecutor.execute(() -> + ShellController.this.onUserProfilesChanged(profiles)); + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java index 254c253b0042..2108c824ac6f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java @@ -16,9 +16,14 @@ package com.android.wm.shell.sysui; +import android.content.Context; +import android.content.pm.UserInfo; import android.content.res.Configuration; +import androidx.annotation.NonNull; + import java.io.PrintWriter; +import java.util.List; /** * General interface for notifying the Shell of common SysUI events like configuration or keyguard @@ -59,4 +64,14 @@ public interface ShellInterface { * Notifies the Shell when the keyguard dismiss animation has finished. */ default void onKeyguardDismissAnimationFinished() {} + + /** + * Notifies the Shell when the user changes. + */ + default void onUserChanged(int newUserId, @NonNull Context userContext) {} + + /** + * Notifies the Shell when a profile belonging to the user changes. + */ + default void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {} } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/UserChangeListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/UserChangeListener.java new file mode 100644 index 000000000000..3d0909f6128d --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/UserChangeListener.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.sysui; + +import android.content.Context; +import android.content.pm.UserInfo; + +import androidx.annotation.NonNull; + +import java.util.List; + +/** + * Callbacks for when the user or user's profiles changes. + */ +public interface UserChangeListener { + /** + * Called when the current (parent) user changes. + */ + default void onUserChanged(int newUserId, @NonNull Context userContext) {} + + /** + * Called when a profile belonging to the user changes. + */ + default void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {} +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 6c659667a4a7..cff60f5e5b6c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -44,6 +44,8 @@ import static android.view.WindowManager.TRANSIT_RELAUNCH; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.transitTypeToString; +import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL; +import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL; import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS; import static android.window.TransitionInfo.FLAG_IS_DISPLAY; import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION; @@ -903,11 +905,10 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { private void attachThumbnail(@NonNull ArrayList<Animator> animations, @NonNull Runnable finishCallback, TransitionInfo.Change change, TransitionInfo.AnimationOptions options, float cornerRadius) { - final boolean isTask = change.getTaskInfo() != null; final boolean isOpen = Transitions.isOpeningType(change.getMode()); final boolean isClose = Transitions.isClosingType(change.getMode()); if (isOpen) { - if (options.getType() == ANIM_OPEN_CROSS_PROFILE_APPS && isTask) { + if (options.getType() == ANIM_OPEN_CROSS_PROFILE_APPS) { attachCrossProfileThumbnailAnimation(animations, finishCallback, change, cornerRadius); } else if (options.getType() == ANIM_THUMBNAIL_SCALE_UP) { @@ -922,8 +923,13 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { @NonNull Runnable finishCallback, TransitionInfo.Change change, float cornerRadius) { final Rect bounds = change.getEndAbsBounds(); // Show the right drawable depending on the user we're transitioning to. - final Drawable thumbnailDrawable = change.getTaskInfo().userId == mCurrentUserId - ? mContext.getDrawable(R.drawable.ic_account_circle) : mEnterpriseThumbnailDrawable; + final Drawable thumbnailDrawable = change.hasFlags(FLAG_CROSS_PROFILE_OWNER_THUMBNAIL) + ? mContext.getDrawable(R.drawable.ic_account_circle) + : change.hasFlags(FLAG_CROSS_PROFILE_WORK_THUMBNAIL) + ? mEnterpriseThumbnailDrawable : null; + if (thumbnailDrawable == null) { + return; + } final HardwareBuffer thumbnail = mTransitionAnimation.createCrossProfileAppsThumbnail( thumbnailDrawable, bounds); if (thumbnail == null) { 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 9335438cea50..29d25bc39223 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 @@ -23,6 +23,7 @@ import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; +import static android.view.WindowManager.fixScale; import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; @@ -167,10 +168,7 @@ public class Transitions implements RemoteCallable<Transitions> { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote"); ContentResolver resolver = mContext.getContentResolver(); - mTransitionAnimationScaleSetting = Settings.Global.getFloat(resolver, - Settings.Global.TRANSITION_ANIMATION_SCALE, - mContext.getResources().getFloat( - R.dimen.config_appTransitionAnimationDurationScaleDefault)); + mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting(); dispatchAnimScaleSetting(mTransitionAnimationScaleSetting); resolver.registerContentObserver( @@ -185,6 +183,12 @@ public class Transitions implements RemoteCallable<Transitions> { } } + private float getTransitionAnimationScaleSetting() { + return fixScale(Settings.Global.getFloat(mContext.getContentResolver(), + Settings.Global.TRANSITION_ANIMATION_SCALE, mContext.getResources().getFloat( + R.dimen.config_appTransitionAnimationDurationScaleDefault))); + } + public ShellTransitions asRemoteTransitions() { return mImpl; } @@ -215,6 +219,8 @@ public class Transitions implements RemoteCallable<Transitions> { + "use ShellInit callbacks to ensure proper ordering"); } mHandlers.add(handler); + // Set initial scale settings. + handler.setAnimScaleSetting(mTransitionAnimationScaleSetting); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: %s", handler.getClass().getSimpleName()); } @@ -963,9 +969,7 @@ public class Transitions implements RemoteCallable<Transitions> { @Override public void onChange(boolean selfChange) { super.onChange(selfChange); - mTransitionAnimationScaleSetting = Settings.Global.getFloat( - mContext.getContentResolver(), Settings.Global.TRANSITION_ANIMATION_SCALE, - mTransitionAnimationScaleSetting); + mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting(); mMainExecutor.execute(() -> dispatchAnimScaleSetting(mTransitionAnimationScaleSetting)); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index 98b5ee9f0cfb..8b13721ef428 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -34,6 +34,7 @@ import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.desktopmode.DesktopModeConstants; /** * Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with @@ -141,7 +142,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL return; } - if (oldDecorationSurface != mDecorationContainerSurface) { + if (oldDecorationSurface != mDecorationContainerSurface || mDragResizeListener == null) { closeDragResizeListener(); mDragResizeListener = new DragResizeInputListener( mContext, @@ -163,7 +164,13 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL View caption = mResult.mRootView.findViewById(R.id.caption); caption.setOnTouchListener(mOnCaptionTouchListener); View maximize = caption.findViewById(R.id.maximize_window); - maximize.setOnClickListener(mOnCaptionButtonClickListener); + if (DesktopModeConstants.IS_FEATURE_ENABLED) { + // Hide maximize button when desktop mode is available + maximize.setVisibility(View.GONE); + } else { + maximize.setVisibility(View.VISIBLE); + maximize.setOnClickListener(mOnCaptionButtonClickListener); + } View close = caption.findViewById(R.id.close_window); close.setOnClickListener(mOnCaptionButtonClickListener); View minimize = caption.findViewById(R.id.minimize_window); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index 506a4c0f90f3..5e64a06e0326 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -248,7 +248,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> lp.setTrustedOverlay(); if (mViewHost == null) { mViewHost = mSurfaceControlViewHostFactory.create(mDecorWindowContext, mDisplay, - mCaptionWindowManager, true); + mCaptionWindowManager); mViewHost.setView(outResult.mRootView, lp); } else { mViewHost.relayout(lp); @@ -345,9 +345,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> } interface SurfaceControlViewHostFactory { - default SurfaceControlViewHost create( - Context c, Display d, WindowlessWindowManager wmm, boolean useSfChoreographer) { - return new SurfaceControlViewHost(c, d, wmm, useSfChoreographer); + default SurfaceControlViewHost create(Context c, Display d, WindowlessWindowManager wmm) { + return new SurfaceControlViewHost(c, d, wmm); } } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java index f865649b6bbc..b29c436d0d51 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java @@ -16,9 +16,11 @@ package com.android.wm.shell; +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_PINNED; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -30,6 +32,8 @@ import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIO import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assume.assumeFalse; @@ -38,9 +42,11 @@ import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.app.ActivityManager.RunningTaskInfo; import android.app.TaskInfo; +import android.app.WindowConfiguration; import android.content.LocusId; import android.content.pm.ParceledListSlice; import android.os.Binder; @@ -53,6 +59,8 @@ import android.window.ITaskOrganizer; import android.window.ITaskOrganizerController; import android.window.TaskAppearedInfo; import android.window.WindowContainerToken; +import android.window.WindowContainerTransaction; +import android.window.WindowContainerTransaction.Change; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -628,6 +636,71 @@ public class ShellTaskOrganizerTests extends ShellTestCase { verify(mTaskOrganizerController).restartTaskTopActivityProcessIfVisible(task1.token); } + @Test + public void testPrepareClearBoundsForTasks() { + RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_UNDEFINED); + task1.displayId = 1; + MockToken token1 = new MockToken(); + task1.token = token1.token(); + mOrganizer.onTaskAppeared(task1, null); + + RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_UNDEFINED); + task2.displayId = 1; + MockToken token2 = new MockToken(); + task2.token = token2.token(); + mOrganizer.onTaskAppeared(task2, null); + + RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_UNDEFINED); + otherDisplayTask.displayId = 2; + MockToken otherDisplayToken = new MockToken(); + otherDisplayTask.token = otherDisplayToken.token(); + mOrganizer.onTaskAppeared(otherDisplayTask, null); + + WindowContainerTransaction wct = mOrganizer.prepareClearBoundsForTasks(1); + + assertEquals(wct.getChanges().size(), 2); + Change boundsChange1 = wct.getChanges().get(token1.binder()); + assertNotNull(boundsChange1); + assertNotEquals( + (boundsChange1.getWindowSetMask() & WindowConfiguration.WINDOW_CONFIG_BOUNDS), 0); + assertTrue(boundsChange1.getConfiguration().windowConfiguration.getBounds().isEmpty()); + + Change boundsChange2 = wct.getChanges().get(token2.binder()); + assertNotNull(boundsChange2); + assertNotEquals( + (boundsChange2.getWindowSetMask() & WindowConfiguration.WINDOW_CONFIG_BOUNDS), 0); + assertTrue(boundsChange2.getConfiguration().windowConfiguration.getBounds().isEmpty()); + } + + @Test + public void testPrepareClearFreeformForTasks() { + RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_FREEFORM); + task1.displayId = 1; + MockToken token1 = new MockToken(); + task1.token = token1.token(); + mOrganizer.onTaskAppeared(task1, null); + + RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_MULTI_WINDOW); + task2.displayId = 1; + MockToken token2 = new MockToken(); + task2.token = token2.token(); + mOrganizer.onTaskAppeared(task2, null); + + RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_FREEFORM); + otherDisplayTask.displayId = 2; + MockToken otherDisplayToken = new MockToken(); + otherDisplayTask.token = otherDisplayToken.token(); + mOrganizer.onTaskAppeared(otherDisplayTask, null); + + WindowContainerTransaction wct = mOrganizer.prepareClearFreeformForTasks(1); + + // Only task with freeform windowing mode and the right display should be updated + assertEquals(wct.getChanges().size(), 1); + Change wmModeChange1 = wct.getChanges().get(token1.binder()); + assertNotNull(wmModeChange1); + assertEquals(wmModeChange1.getWindowingMode(), WINDOWING_MODE_UNDEFINED); + } + private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) { RunningTaskInfo taskInfo = new RunningTaskInfo(); taskInfo.taskId = taskId; @@ -635,4 +708,22 @@ public class ShellTaskOrganizerTests extends ShellTestCase { return taskInfo; } + private static class MockToken { + private final WindowContainerToken mToken; + private final IBinder mBinder; + + MockToken() { + mToken = mock(WindowContainerToken.class); + mBinder = mock(IBinder.class); + when(mToken.asBinder()).thenReturn(mBinder); + } + + WindowContainerToken token() { + return mToken; + } + + IBinder binder() { + return mBinder; + } + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java index 32f1587752cb..ff1d2990a82a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java @@ -169,6 +169,7 @@ public class TaskViewTest extends ShellTestCase { mTaskView.onTaskAppeared(mTaskInfo, mLeash); verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any()); + assertThat(mTaskView.isInitialized()).isTrue(); verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean()); } @@ -178,6 +179,7 @@ public class TaskViewTest extends ShellTestCase { mTaskView.surfaceCreated(mock(SurfaceHolder.class)); verify(mViewListener).onInitialized(); + assertThat(mTaskView.isInitialized()).isTrue(); // No task, no visibility change verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean()); } @@ -189,6 +191,7 @@ public class TaskViewTest extends ShellTestCase { mTaskView.surfaceCreated(mock(SurfaceHolder.class)); verify(mViewListener).onInitialized(); + assertThat(mTaskView.isInitialized()).isTrue(); verify(mViewListener).onTaskVisibilityChanged(eq(mTaskInfo.taskId), eq(true)); } @@ -223,6 +226,7 @@ public class TaskViewTest extends ShellTestCase { verify(mOrganizer).removeListener(eq(mTaskView)); verify(mViewListener).onReleased(); + assertThat(mTaskView.isInitialized()).isFalse(); } @Test @@ -270,6 +274,7 @@ public class TaskViewTest extends ShellTestCase { verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any()); verify(mViewListener, never()).onInitialized(); + assertThat(mTaskView.isInitialized()).isFalse(); // If there's no surface the task should be made invisible verify(mViewListener).onTaskVisibilityChanged(eq(mTaskInfo.taskId), eq(false)); } @@ -281,6 +286,7 @@ public class TaskViewTest extends ShellTestCase { verify(mTaskViewTransitions, never()).setTaskViewVisible(any(), anyBoolean()); verify(mViewListener).onInitialized(); + assertThat(mTaskView.isInitialized()).isTrue(); // No task, no visibility change verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean()); } @@ -353,6 +359,7 @@ public class TaskViewTest extends ShellTestCase { verify(mOrganizer).removeListener(eq(mTaskView)); verify(mViewListener).onReleased(); + assertThat(mTaskView.isInitialized()).isFalse(); verify(mTaskViewTransitions).removeTaskView(eq(mTaskView)); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java index da95c77d2b89..fe8b305093d7 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java @@ -48,9 +48,10 @@ public class TestShellExecutor implements ShellExecutor { } public void flushAll() { - for (Runnable r : mRunnables) { + final ArrayList<Runnable> tmpRunnable = new ArrayList<>(mRunnables); + mRunnables.clear(); + for (Runnable r : tmpRunnable) { r.run(); } - mRunnables.clear(); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java new file mode 100644 index 000000000000..b2e45a6b3a5c --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.activityembedding; + +import static android.view.WindowManager.TRANSIT_OPEN; +import static android.window.TransitionInfo.FLAG_IS_EMBEDDED; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import android.window.TransitionInfo; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; + +/** + * Tests for {@link ActivityEmbeddingAnimationRunner}. + * + * Build/Install/Run: + * atest WMShellUnitTests:ActivityEmbeddingAnimationRunnerTests + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnimationTestBase { + + @Before + public void setup() { + super.setUp(); + doNothing().when(mController).onAnimationFinished(any()); + } + + @Test + public void testStartAnimation() { + final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); + final TransitionInfo.Change embeddingChange = createChange(); + embeddingChange.setFlags(FLAG_IS_EMBEDDED); + info.addChange(embeddingChange); + doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any()); + + mAnimRunner.startAnimation(mTransition, info, mStartTransaction, mFinishTransaction); + + final ArgumentCaptor<Runnable> finishCallback = ArgumentCaptor.forClass(Runnable.class); + verify(mAnimRunner).createAnimator(eq(info), eq(mStartTransaction), eq(mFinishTransaction), + finishCallback.capture()); + verify(mStartTransaction).apply(); + verify(mAnimator).start(); + verifyNoMoreInteractions(mFinishTransaction); + verify(mController, never()).onAnimationFinished(any()); + + // Call onAnimationFinished() when the animation is finished. + finishCallback.getValue().run(); + + verify(mController).onAnimationFinished(mTransition); + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java new file mode 100644 index 000000000000..84befdddabdb --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.activityembedding; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assume.assumeTrue; +import static org.mockito.Mockito.mock; + +import android.animation.Animator; +import android.annotation.CallSuper; +import android.os.IBinder; +import android.view.SurfaceControl; +import android.window.TransitionInfo; +import android.window.WindowContainerToken; + +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.Transitions; + +import org.junit.Before; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** TestBase for ActivityEmbedding animation. */ +abstract class ActivityEmbeddingAnimationTestBase extends ShellTestCase { + + @Mock + ShellInit mShellInit; + @Mock + Transitions mTransitions; + @Mock + IBinder mTransition; + @Mock + SurfaceControl.Transaction mStartTransaction; + @Mock + SurfaceControl.Transaction mFinishTransaction; + @Mock + Transitions.TransitionFinishCallback mFinishCallback; + @Mock + Animator mAnimator; + + ActivityEmbeddingController mController; + ActivityEmbeddingAnimationRunner mAnimRunner; + ActivityEmbeddingAnimationSpec mAnimSpec; + + @CallSuper + @Before + public void setUp() { + assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); + MockitoAnnotations.initMocks(this); + mController = ActivityEmbeddingController.create(mContext, mShellInit, mTransitions); + assertNotNull(mController); + mAnimRunner = mController.mAnimationRunner; + assertNotNull(mAnimRunner); + mAnimSpec = mAnimRunner.mAnimationSpec; + assertNotNull(mAnimSpec); + spyOn(mController); + spyOn(mAnimRunner); + spyOn(mAnimSpec); + } + + /** Creates a mock {@link TransitionInfo.Change}. */ + static TransitionInfo.Change createChange() { + return new TransitionInfo.Change(mock(WindowContainerToken.class), + mock(SurfaceControl.class)); + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java index bfe3b5468085..cf43b0030d2a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java @@ -16,52 +16,117 @@ package com.android.wm.shell.activityembedding; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; +import static android.view.WindowManager.TRANSIT_OPEN; +import static android.window.TransitionInfo.FLAG_IS_EMBEDDED; -import static org.junit.Assume.assumeTrue; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.times; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; -import android.content.Context; +import android.window.TransitionInfo; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import com.android.wm.shell.ShellTestCase; -import com.android.wm.shell.sysui.ShellInit; -import com.android.wm.shell.transition.Transitions; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; /** - * Tests for the activity embedding controller. + * Tests for {@link ActivityEmbeddingController}. * * Build/Install/Run: * atest WMShellUnitTests:ActivityEmbeddingControllerTests */ @SmallTest @RunWith(AndroidJUnit4.class) -public class ActivityEmbeddingControllerTests extends ShellTestCase { - - private @Mock Context mContext; - private @Mock ShellInit mShellInit; - private @Mock Transitions mTransitions; - private ActivityEmbeddingController mController; +public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimationTestBase { @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mController = spy(new ActivityEmbeddingController(mContext, mShellInit, mTransitions)); + public void setup() { + super.setUp(); + doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any()); } @Test - public void instantiate_addInitCallback() { - assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); - verify(mShellInit, times(1)).addInitCallback(any(), any()); + public void testInstantiate() { + verify(mShellInit).addInitCallback(any(), any()); + } + + @Test + public void testOnInit() { + mController.onInit(); + + verify(mTransitions).addHandler(mController); + } + + @Test + public void testSetAnimScaleSetting() { + mController.setAnimScaleSetting(1.0f); + + verify(mAnimRunner).setAnimScaleSetting(1.0f); + verify(mAnimSpec).setAnimScaleSetting(1.0f); + } + + @Test + public void testStartAnimation_containsNonActivityEmbeddingChange() { + final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); + final TransitionInfo.Change embeddingChange = createChange(); + embeddingChange.setFlags(FLAG_IS_EMBEDDED); + final TransitionInfo.Change nonEmbeddingChange = createChange(); + info.addChange(embeddingChange); + info.addChange(nonEmbeddingChange); + + // No-op + assertFalse(mController.startAnimation(mTransition, info, mStartTransaction, + mFinishTransaction, mFinishCallback)); + verify(mAnimRunner, never()).startAnimation(any(), any(), any(), any()); + verifyNoMoreInteractions(mStartTransaction); + verifyNoMoreInteractions(mFinishTransaction); + verifyNoMoreInteractions(mFinishCallback); + } + + @Test + public void testStartAnimation_onlyActivityEmbeddingChange() { + final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); + final TransitionInfo.Change embeddingChange = createChange(); + embeddingChange.setFlags(FLAG_IS_EMBEDDED); + info.addChange(embeddingChange); + + // No-op + assertTrue(mController.startAnimation(mTransition, info, mStartTransaction, + mFinishTransaction, mFinishCallback)); + verify(mAnimRunner).startAnimation(mTransition, info, mStartTransaction, + mFinishTransaction); + verify(mStartTransaction).apply(); + verifyNoMoreInteractions(mFinishTransaction); + } + + @Test + public void testOnAnimationFinished() { + // Should not call finish when there is no transition. + assertThrows(IllegalStateException.class, + () -> mController.onAnimationFinished(mTransition)); + + final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); + final TransitionInfo.Change embeddingChange = createChange(); + embeddingChange.setFlags(FLAG_IS_EMBEDDED); + info.addChange(embeddingChange); + mController.startAnimation(mTransition, info, mStartTransaction, + mFinishTransaction, mFinishCallback); + + verify(mFinishCallback, never()).onTransitionFinished(any(), any()); + mController.onAnimationFinished(mTransition); + verify(mFinishCallback).onTransitionFinished(any(), any()); + + // Should not call finish when the finish has already been called. + assertThrows(IllegalStateException.class, + () -> mController.onAnimationFinished(mTransition)); } } 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 5b3b8fd7ad71..90a377309edd 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 @@ -54,6 +54,7 @@ import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; import android.window.BackEvent; import android.window.BackNavigationInfo; +import android.window.IBackNaviAnimationController; import android.window.IOnBackInvokedCallback; import androidx.test.filters.SmallTest; @@ -98,6 +99,9 @@ public class BackAnimationControllerTest extends ShellTestCase { @Mock private IOnBackInvokedCallback mIOnBackInvokedCallback; + @Mock + private IBackNaviAnimationController mIBackNaviAnimationController; + private BackAnimationController mController; private int mEventTime = 0; @@ -127,7 +131,7 @@ public class BackAnimationControllerTest extends ShellTestCase { SurfaceControl screenshotSurface, HardwareBuffer hardwareBuffer, int backType, - IOnBackInvokedCallback onBackInvokedCallback) { + IOnBackInvokedCallback onBackInvokedCallback, boolean prepareAnimation) { BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder() .setType(backType) .setDepartingAnimationTarget(topAnimationTarget) @@ -135,7 +139,8 @@ public class BackAnimationControllerTest extends ShellTestCase { .setScreenshotBuffer(hardwareBuffer) .setTaskWindowConfiguration(new WindowConfiguration()) .setOnBackNavigationDone(new RemoteCallback((bundle) -> {})) - .setOnBackInvokedCallback(onBackInvokedCallback); + .setOnBackInvokedCallback(onBackInvokedCallback) + .setPrepareAnimation(prepareAnimation); createNavigationInfo(builder); } @@ -143,7 +148,7 @@ public class BackAnimationControllerTest extends ShellTestCase { private void createNavigationInfo(BackNavigationInfo.Builder builder) { try { doReturn(builder.build()).when(mActivityTaskManager) - .startBackNavigation(anyBoolean(), any()); + .startBackNavigation(anyBoolean(), any(), any()); } catch (RemoteException ex) { ex.rethrowFromSystemServer(); } @@ -175,7 +180,7 @@ public class BackAnimationControllerTest extends ShellTestCase { SurfaceControl screenshotSurface = new SurfaceControl(); HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class); createNavigationInfo(createAnimationTarget(), screenshotSurface, hardwareBuffer, - BackNavigationInfo.TYPE_CROSS_ACTIVITY, null); + BackNavigationInfo.TYPE_CROSS_ACTIVITY, null, true); doMotionEvent(MotionEvent.ACTION_DOWN, 0); verify(mTransaction).setBuffer(screenshotSurface, hardwareBuffer); verify(mTransaction).setVisibility(screenshotSurface, true); @@ -188,7 +193,7 @@ public class BackAnimationControllerTest extends ShellTestCase { HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class); RemoteAnimationTarget animationTarget = createAnimationTarget(); createNavigationInfo(animationTarget, screenshotSurface, hardwareBuffer, - BackNavigationInfo.TYPE_CROSS_ACTIVITY, null); + BackNavigationInfo.TYPE_CROSS_ACTIVITY, null, true); doMotionEvent(MotionEvent.ACTION_DOWN, 0); doMotionEvent(MotionEvent.ACTION_MOVE, 100); // b/207481538, we check that the surface is not moved for now, we can re-enable this once @@ -222,15 +227,16 @@ public class BackAnimationControllerTest extends ShellTestCase { mController.setBackToLauncherCallback(mIOnBackInvokedCallback); RemoteAnimationTarget animationTarget = createAnimationTarget(); createNavigationInfo(animationTarget, null, null, - BackNavigationInfo.TYPE_RETURN_TO_HOME, null); + BackNavigationInfo.TYPE_RETURN_TO_HOME, null, true); doMotionEvent(MotionEvent.ACTION_DOWN, 0); // Check that back start and progress is dispatched when first move. doMotionEvent(MotionEvent.ACTION_MOVE, 100); + simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget); verify(mIOnBackInvokedCallback).onBackStarted(); ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class); - verify(mIOnBackInvokedCallback).onBackProgressed(backEventCaptor.capture()); + verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(backEventCaptor.capture()); assertEquals(animationTarget, backEventCaptor.getValue().getDepartingAnimationTarget()); // Check that back invocation is dispatched. @@ -255,7 +261,7 @@ public class BackAnimationControllerTest extends ShellTestCase { IOnBackInvokedCallback appCallback = mock(IOnBackInvokedCallback.class); ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class); createNavigationInfo(animationTarget, null, null, - BackNavigationInfo.TYPE_RETURN_TO_HOME, appCallback); + BackNavigationInfo.TYPE_RETURN_TO_HOME, appCallback, false); triggerBackGesture(); @@ -273,9 +279,10 @@ public class BackAnimationControllerTest extends ShellTestCase { mController.setBackToLauncherCallback(mIOnBackInvokedCallback); RemoteAnimationTarget animationTarget = createAnimationTarget(); createNavigationInfo(animationTarget, null, null, - BackNavigationInfo.TYPE_RETURN_TO_HOME, null); + BackNavigationInfo.TYPE_RETURN_TO_HOME, null, true); triggerBackGesture(); + simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget); // Check that back invocation is dispatched. verify(mIOnBackInvokedCallback).onBackInvoked(); @@ -294,6 +301,7 @@ public class BackAnimationControllerTest extends ShellTestCase { // Verify that we start accepting gestures again once transition finishes. doMotionEvent(MotionEvent.ACTION_DOWN, 0); doMotionEvent(MotionEvent.ACTION_MOVE, 100); + simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget); verify(mIOnBackInvokedCallback).onBackStarted(); } @@ -302,15 +310,17 @@ public class BackAnimationControllerTest extends ShellTestCase { mController.setBackToLauncherCallback(mIOnBackInvokedCallback); RemoteAnimationTarget animationTarget = createAnimationTarget(); createNavigationInfo(animationTarget, null, null, - BackNavigationInfo.TYPE_RETURN_TO_HOME, null); + BackNavigationInfo.TYPE_RETURN_TO_HOME, null, true); triggerBackGesture(); + simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget); reset(mIOnBackInvokedCallback); // Simulate transition timeout. mShellExecutor.flushAll(); doMotionEvent(MotionEvent.ACTION_DOWN, 0); doMotionEvent(MotionEvent.ACTION_MOVE, 100); + simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget); verify(mIOnBackInvokedCallback).onBackStarted(); } @@ -321,11 +331,12 @@ public class BackAnimationControllerTest extends ShellTestCase { RemoteAnimationTarget animationTarget = createAnimationTarget(); createNavigationInfo(animationTarget, null, null, - BackNavigationInfo.TYPE_RETURN_TO_HOME, null); + BackNavigationInfo.TYPE_RETURN_TO_HOME, null, true); doMotionEvent(MotionEvent.ACTION_DOWN, 0); // Check that back start and progress is dispatched when first move. doMotionEvent(MotionEvent.ACTION_MOVE, 100); + simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget); verify(mIOnBackInvokedCallback).onBackStarted(); // Check that back invocation is dispatched. @@ -349,4 +360,14 @@ public class BackAnimationControllerTest extends ShellTestCase { BackEvent.EDGE_LEFT); mEventTime += 10; } + + private void simulateRemoteAnimationStart(int type, RemoteAnimationTarget animationTarget) + throws RemoteException { + if (mController.mIBackAnimationRunner != null) { + final RemoteAnimationTarget[] targets = new RemoteAnimationTarget[]{animationTarget}; + mController.mIBackAnimationRunner.onAnimationStart(mIBackNaviAnimationController, type, + targets, null, null); + mShellExecutor.flushAll(); + } + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java index 95725bbfd855..695550dd8fa5 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java @@ -159,7 +159,8 @@ public class SplitLayoutTests extends ShellTestCase { } private void waitDividerFlingFinished() { - verify(mSplitLayout).flingDividePosition(anyInt(), anyInt(), mRunnableCaptor.capture()); + verify(mSplitLayout).flingDividePosition(anyInt(), anyInt(), anyInt(), + mRunnableCaptor.capture()); mRunnableCaptor.getValue().run(); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java new file mode 100644 index 000000000000..58f20da34943 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.desktopmode; + +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; + +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.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.WindowConfiguration; +import android.os.Handler; +import android.os.IBinder; +import android.testing.AndroidTestingRunner; +import android.window.WindowContainerToken; +import android.window.WindowContainerTransaction; +import android.window.WindowContainerTransaction.Change; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.RootDisplayAreaOrganizer; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.sysui.ShellInit; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class DesktopModeControllerTest extends ShellTestCase { + + @Mock + private ShellTaskOrganizer mShellTaskOrganizer; + @Mock + private RootDisplayAreaOrganizer mRootDisplayAreaOrganizer; + @Mock + private ShellExecutor mTestExecutor; + @Mock + private Handler mMockHandler; + + private DesktopModeController mController; + private ShellInit mShellInit; + + @Before + public void setUp() { + mShellInit = Mockito.spy(new ShellInit(mTestExecutor)); + + mController = new DesktopModeController(mContext, mShellInit, mShellTaskOrganizer, + mRootDisplayAreaOrganizer, mMockHandler); + + mShellInit.init(); + } + + @Test + public void instantiate_addInitCallback() { + verify(mShellInit, times(1)).addInitCallback(any(), any()); + } + + @Test + public void testDesktopModeEnabled_taskWmClearedDisplaySetToFreeform() { + // Create a fake WCT to simulate setting task windowing mode to undefined + WindowContainerTransaction taskWct = new WindowContainerTransaction(); + MockToken taskMockToken = new MockToken(); + taskWct.setWindowingMode(taskMockToken.token(), WINDOWING_MODE_UNDEFINED); + when(mShellTaskOrganizer.prepareClearFreeformForTasks(mContext.getDisplayId())).thenReturn( + taskWct); + + // Create a fake WCT to simulate setting display windowing mode to freeform + WindowContainerTransaction displayWct = new WindowContainerTransaction(); + MockToken displayMockToken = new MockToken(); + displayWct.setWindowingMode(displayMockToken.token(), WINDOWING_MODE_FREEFORM); + when(mRootDisplayAreaOrganizer.prepareWindowingModeChange(mContext.getDisplayId(), + WINDOWING_MODE_FREEFORM)).thenReturn(displayWct); + + // The test + mController.updateDesktopModeEnabled(true); + + ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass( + WindowContainerTransaction.class); + verify(mRootDisplayAreaOrganizer).applyTransaction(arg.capture()); + + // WCT should have 2 changes - clear task wm mode and set display wm mode + WindowContainerTransaction wct = arg.getValue(); + assertThat(wct.getChanges()).hasSize(2); + + // Verify executed WCT has a change for setting task windowing mode to undefined + Change taskWmModeChange = wct.getChanges().get(taskMockToken.binder()); + assertThat(taskWmModeChange).isNotNull(); + assertThat(taskWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED); + + // Verify executed WCT has a change for setting display windowing mode to freeform + Change displayWmModeChange = wct.getChanges().get(displayMockToken.binder()); + assertThat(displayWmModeChange).isNotNull(); + assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FREEFORM); + } + + @Test + public void testDesktopModeDisabled_taskWmAndBoundsClearedDisplaySetToFullscreen() { + // Create a fake WCT to simulate setting task windowing mode to undefined + WindowContainerTransaction taskWmWct = new WindowContainerTransaction(); + MockToken taskWmMockToken = new MockToken(); + taskWmWct.setWindowingMode(taskWmMockToken.token(), WINDOWING_MODE_UNDEFINED); + when(mShellTaskOrganizer.prepareClearFreeformForTasks(mContext.getDisplayId())).thenReturn( + taskWmWct); + + // Create a fake WCT to simulate clearing task bounds + WindowContainerTransaction taskBoundsWct = new WindowContainerTransaction(); + MockToken taskBoundsMockToken = new MockToken(); + taskBoundsWct.setBounds(taskBoundsMockToken.token(), null); + when(mShellTaskOrganizer.prepareClearBoundsForTasks(mContext.getDisplayId())).thenReturn( + taskBoundsWct); + + // Create a fake WCT to simulate setting display windowing mode to fullscreen + WindowContainerTransaction displayWct = new WindowContainerTransaction(); + MockToken displayMockToken = new MockToken(); + displayWct.setWindowingMode(displayMockToken.token(), WINDOWING_MODE_FULLSCREEN); + when(mRootDisplayAreaOrganizer.prepareWindowingModeChange(mContext.getDisplayId(), + WINDOWING_MODE_FULLSCREEN)).thenReturn(displayWct); + + // The test + mController.updateDesktopModeEnabled(false); + + ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass( + WindowContainerTransaction.class); + verify(mRootDisplayAreaOrganizer).applyTransaction(arg.capture()); + + // WCT should have 3 changes - clear task wm mode and bounds and set display wm mode + WindowContainerTransaction wct = arg.getValue(); + assertThat(wct.getChanges()).hasSize(3); + + // Verify executed WCT has a change for setting task windowing mode to undefined + Change taskWmModeChange = wct.getChanges().get(taskWmMockToken.binder()); + assertThat(taskWmModeChange).isNotNull(); + assertThat(taskWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED); + + // Verify executed WCT has a change for clearing task bounds + Change taskBoundsChange = wct.getChanges().get(taskBoundsMockToken.binder()); + assertThat(taskBoundsChange).isNotNull(); + assertThat(taskBoundsChange.getWindowSetMask() + & WindowConfiguration.WINDOW_CONFIG_BOUNDS).isNotEqualTo(0); + assertThat(taskBoundsChange.getConfiguration().windowConfiguration.getBounds().isEmpty()) + .isTrue(); + + // Verify executed WCT has a change for setting display windowing mode to fullscreen + Change displayWmModeChange = wct.getChanges().get(displayMockToken.binder()); + assertThat(displayWmModeChange).isNotNull(); + assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FULLSCREEN); + } + + private static class MockToken { + private final WindowContainerToken mToken; + private final IBinder mBinder; + + MockToken() { + mToken = mock(WindowContainerToken.class); + mBinder = mock(IBinder.class); + when(mToken.asBinder()).thenReturn(mBinder); + } + + WindowContainerToken token() { + return mToken; + } + + IBinder binder() { + return mBinder; + } + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java index 90645ce4747d..cf8297eec061 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java @@ -171,6 +171,11 @@ public class OneHandedControllerTest extends OneHandedTestCase { } @Test + public void testControllerRegistersUserChangeListener() { + verify(mMockShellController, times(1)).addUserChangeListener(any()); + } + + @Test public void testDefaultShouldNotInOneHanded() { // Assert default transition state is STATE_NONE assertThat(mSpiedTransitionState.getState()).isEqualTo(STATE_NONE); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java index 9ed8d84d665f..eb5726bebb74 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java @@ -23,6 +23,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -77,9 +78,9 @@ import java.util.Set; public class PipControllerTest extends ShellTestCase { private PipController mPipController; private ShellInit mShellInit; + private ShellController mShellController; @Mock private ShellCommandHandler mMockShellCommandHandler; - @Mock private ShellController mMockShellController; @Mock private DisplayController mMockDisplayController; @Mock private PhonePipMenuController mMockPhonePipMenuController; @Mock private PipAppOpsListener mMockPipAppOpsListener; @@ -110,8 +111,10 @@ public class PipControllerTest extends ShellTestCase { return null; }).when(mMockExecutor).execute(any()); mShellInit = spy(new ShellInit(mMockExecutor)); + mShellController = spy(new ShellController(mShellInit, mMockShellCommandHandler, + mMockExecutor)); mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler, - mMockShellController, mMockDisplayController, mMockPipAppOpsListener, + mShellController, mMockDisplayController, mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm, mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState, @@ -135,12 +138,22 @@ public class PipControllerTest extends ShellTestCase { @Test public void instantiatePipController_registerConfigChangeListener() { - verify(mMockShellController, times(1)).addConfigurationChangeListener(any()); + verify(mShellController, times(1)).addConfigurationChangeListener(any()); } @Test public void instantiatePipController_registerKeyguardChangeListener() { - verify(mMockShellController, times(1)).addKeyguardChangeListener(any()); + verify(mShellController, times(1)).addKeyguardChangeListener(any()); + } + + @Test + public void instantiatePipController_registerUserChangeListener() { + verify(mShellController, times(1)).addUserChangeListener(any()); + } + + @Test + public void instantiatePipController_registerMediaListener() { + verify(mMockPipMediaController, times(1)).registerSessionListenerForCurrentUser(); } @Test @@ -167,7 +180,7 @@ public class PipControllerTest extends ShellTestCase { ShellInit shellInit = new ShellInit(mMockExecutor); assertNull(PipController.create(spyContext, shellInit, mMockShellCommandHandler, - mMockShellController, mMockDisplayController, mMockPipAppOpsListener, + mShellController, mMockDisplayController, mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm, mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState, @@ -264,4 +277,11 @@ public class PipControllerTest extends ShellTestCase { verify(mMockPipBoundsState).setKeepClearAreas(Set.of(keepClearArea), Set.of()); } + + @Test + public void onUserChangeRegisterMediaListener() { + reset(mMockPipMediaController); + mShellController.asShell().onUserChanged(100, mContext); + verify(mMockPipMediaController, times(1)).registerSessionListenerForCurrentUser(); + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java index 39e58ffcf9c7..d6ddba9e927d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java @@ -17,11 +17,15 @@ package com.android.wm.shell.sysui; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import android.content.Context; +import android.content.pm.UserInfo; import android.content.res.Configuration; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; @@ -35,6 +39,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; @SmallTest @@ -42,22 +48,29 @@ import java.util.Locale; @TestableLooper.RunWithLooper(setAsMainLooper = true) public class ShellControllerTest extends ShellTestCase { + private static final int TEST_USER_ID = 100; + @Mock private ShellInit mShellInit; @Mock private ShellCommandHandler mShellCommandHandler; @Mock private ShellExecutor mExecutor; + @Mock + private Context mTestUserContext; private ShellController mController; private TestConfigurationChangeListener mConfigChangeListener; private TestKeyguardChangeListener mKeyguardChangeListener; + private TestUserChangeListener mUserChangeListener; + @Before public void setUp() { MockitoAnnotations.initMocks(this); mKeyguardChangeListener = new TestKeyguardChangeListener(); mConfigChangeListener = new TestConfigurationChangeListener(); + mUserChangeListener = new TestUserChangeListener(); mController = new ShellController(mShellInit, mShellCommandHandler, mExecutor); mController.onConfigurationChanged(getConfigurationCopy()); } @@ -68,6 +81,46 @@ public class ShellControllerTest extends ShellTestCase { } @Test + public void testAddUserChangeListener_ensureCallback() { + mController.addUserChangeListener(mUserChangeListener); + + mController.onUserChanged(TEST_USER_ID, mTestUserContext); + assertTrue(mUserChangeListener.userChanged == 1); + assertTrue(mUserChangeListener.lastUserContext == mTestUserContext); + } + + @Test + public void testDoubleAddUserChangeListener_ensureSingleCallback() { + mController.addUserChangeListener(mUserChangeListener); + mController.addUserChangeListener(mUserChangeListener); + + mController.onUserChanged(TEST_USER_ID, mTestUserContext); + assertTrue(mUserChangeListener.userChanged == 1); + assertTrue(mUserChangeListener.lastUserContext == mTestUserContext); + } + + @Test + public void testAddRemoveUserChangeListener_ensureNoCallback() { + mController.addUserChangeListener(mUserChangeListener); + mController.removeUserChangeListener(mUserChangeListener); + + mController.onUserChanged(TEST_USER_ID, mTestUserContext); + assertTrue(mUserChangeListener.userChanged == 0); + assertTrue(mUserChangeListener.lastUserContext == null); + } + + @Test + public void testUserProfilesChanged() { + mController.addUserChangeListener(mUserChangeListener); + + ArrayList<UserInfo> profiles = new ArrayList<>(); + profiles.add(mock(UserInfo.class)); + profiles.add(mock(UserInfo.class)); + mController.onUserProfilesChanged(profiles); + assertTrue(mUserChangeListener.lastUserProfiles.equals(profiles)); + } + + @Test public void testAddKeyguardChangeListener_ensureCallback() { mController.addKeyguardChangeListener(mKeyguardChangeListener); @@ -332,4 +385,27 @@ public class ShellControllerTest extends ShellTestCase { dismissAnimationFinished++; } } + + private class TestUserChangeListener implements UserChangeListener { + // Counts of number of times each of the callbacks are called + public int userChanged; + public int lastUserId; + public Context lastUserContext; + public int userProfilesChanged; + public List<? extends UserInfo> lastUserProfiles; + + + @Override + public void onUserChanged(int newUserId, @NonNull Context userContext) { + userChanged++; + lastUserId = newUserId; + lastUserContext = userContext; + } + + @Override + public void onUserProfilesChanged(@NonNull List<UserInfo> profiles) { + userProfilesChanged++; + lastUserProfiles = profiles; + } + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java index 226843eca64e..e11be31aa40e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java @@ -24,7 +24,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.argThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; @@ -107,7 +106,7 @@ public class WindowDecorationTests extends ShellTestCase { mMockSurfaceControlFinishT = createMockSurfaceControlTransaction(); doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory) - .create(any(), any(), any(), anyBoolean()); + .create(any(), any(), any()); } @Test @@ -148,8 +147,7 @@ public class WindowDecorationTests extends ShellTestCase { verify(decorContainerSurfaceBuilder, never()).build(); verify(taskBackgroundSurfaceBuilder, never()).build(); - verify(mMockSurfaceControlViewHostFactory, never()) - .create(any(), any(), any(), anyBoolean()); + verify(mMockSurfaceControlViewHostFactory, never()).create(any(), any(), any()); verify(mMockSurfaceControlFinishT).hide(taskSurface); @@ -207,8 +205,7 @@ public class WindowDecorationTests extends ShellTestCase { verify(mMockSurfaceControlStartT).setLayer(taskBackgroundSurface, -1); verify(mMockSurfaceControlStartT).show(taskBackgroundSurface); - verify(mMockSurfaceControlViewHostFactory) - .create(any(), eq(defaultDisplay), any(), anyBoolean()); + verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any()); verify(mMockSurfaceControlViewHost) .setView(same(mMockView), argThat(lp -> lp.height == 64 @@ -326,8 +323,7 @@ public class WindowDecorationTests extends ShellTestCase { verify(mMockDisplayController).removeDisplayWindowListener(same(listener)); assertThat(mRelayoutResult.mRootView).isSameInstanceAs(mMockView); - verify(mMockSurfaceControlViewHostFactory) - .create(any(), eq(mockDisplay), any(), anyBoolean()); + verify(mMockSurfaceControlViewHostFactory).create(any(), eq(mockDisplay), any()); verify(mMockSurfaceControlViewHost).setView(same(mMockView), any()); } diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index c666140765aa..650f36059495 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -1299,8 +1299,8 @@ public class AudioSystem /** @hide */ public static final String DEVICE_OUT_REMOTE_SUBMIX_NAME = "remote_submix"; /** @hide */ public static final String DEVICE_OUT_TELEPHONY_TX_NAME = "telephony_tx"; /** @hide */ public static final String DEVICE_OUT_LINE_NAME = "line"; - /** @hide */ public static final String DEVICE_OUT_HDMI_ARC_NAME = "hmdi_arc"; - /** @hide */ public static final String DEVICE_OUT_HDMI_EARC_NAME = "hmdi_earc"; + /** @hide */ public static final String DEVICE_OUT_HDMI_ARC_NAME = "hdmi_arc"; + /** @hide */ public static final String DEVICE_OUT_HDMI_EARC_NAME = "hdmi_earc"; /** @hide */ public static final String DEVICE_OUT_SPDIF_NAME = "spdif"; /** @hide */ public static final String DEVICE_OUT_FM_NAME = "fm_transmitter"; /** @hide */ public static final String DEVICE_OUT_AUX_LINE_NAME = "aux_line"; diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index 8756f1e2266d..c08f5a293e1d 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -45,6 +45,7 @@ import java.nio.ByteOrder; import java.nio.ReadOnlyBufferException; import java.util.ArrayList; import java.util.Arrays; +import java.util.BitSet; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -1803,7 +1804,7 @@ final public class MediaCodec { synchronized(mBufferLock) { switch (mBufferMode) { case BUFFER_MODE_LEGACY: - validateInputByteBuffer(mCachedInputBuffers, index); + validateInputByteBufferLocked(mCachedInputBuffers, index); break; case BUFFER_MODE_BLOCK: while (mQueueRequests.size() <= index) { @@ -1832,7 +1833,7 @@ final public class MediaCodec { synchronized(mBufferLock) { switch (mBufferMode) { case BUFFER_MODE_LEGACY: - validateOutputByteBuffer(mCachedOutputBuffers, index, info); + validateOutputByteBufferLocked(mCachedOutputBuffers, index, info); break; case BUFFER_MODE_BLOCK: while (mOutputFrames.size() <= index) { @@ -2320,10 +2321,6 @@ final public class MediaCodec { */ public final void start() { native_start(); - synchronized(mBufferLock) { - cacheBuffers(true /* input */); - cacheBuffers(false /* input */); - } } private native final void native_start(); @@ -2380,8 +2377,10 @@ final public class MediaCodec { */ public final void flush() { synchronized(mBufferLock) { - invalidateByteBuffers(mCachedInputBuffers); - invalidateByteBuffers(mCachedOutputBuffers); + invalidateByteBuffersLocked(mCachedInputBuffers); + invalidateByteBuffersLocked(mCachedOutputBuffers); + mValidInputIndices.clear(); + mValidOutputIndices.clear(); mDequeuedInputBuffers.clear(); mDequeuedOutputBuffers.clear(); } @@ -2665,14 +2664,14 @@ final public class MediaCodec { + "is not compatible with CONFIGURE_FLAG_USE_BLOCK_MODEL. " + "Please use getQueueRequest() to queue buffers"); } - invalidateByteBuffer(mCachedInputBuffers, index); + invalidateByteBufferLocked(mCachedInputBuffers, index, true /* input */); mDequeuedInputBuffers.remove(index); } try { native_queueInputBuffer( index, offset, size, presentationTimeUs, flags); } catch (CryptoException | IllegalStateException e) { - revalidateByteBuffer(mCachedInputBuffers, index); + revalidateByteBuffer(mCachedInputBuffers, index, true /* input */); throw e; } } @@ -2935,14 +2934,14 @@ final public class MediaCodec { + "is not compatible with CONFIGURE_FLAG_USE_BLOCK_MODEL. " + "Please use getQueueRequest() to queue buffers"); } - invalidateByteBuffer(mCachedInputBuffers, index); + invalidateByteBufferLocked(mCachedInputBuffers, index, true /* input */); mDequeuedInputBuffers.remove(index); } try { native_queueSecureInputBuffer( index, offset, info, presentationTimeUs, flags); } catch (CryptoException | IllegalStateException e) { - revalidateByteBuffer(mCachedInputBuffers, index); + revalidateByteBuffer(mCachedInputBuffers, index, true /* input */); throw e; } } @@ -2976,7 +2975,7 @@ final public class MediaCodec { int res = native_dequeueInputBuffer(timeoutUs); if (res >= 0) { synchronized(mBufferLock) { - validateInputByteBuffer(mCachedInputBuffers, res); + validateInputByteBufferLocked(mCachedInputBuffers, res); } } return res; @@ -3573,10 +3572,10 @@ final public class MediaCodec { int res = native_dequeueOutputBuffer(info, timeoutUs); synchronized (mBufferLock) { if (res == INFO_OUTPUT_BUFFERS_CHANGED) { - cacheBuffers(false /* input */); + cacheBuffersLocked(false /* input */); } else if (res >= 0) { - validateOutputByteBuffer(mCachedOutputBuffers, res, info); - if (mHasSurface) { + validateOutputByteBufferLocked(mCachedOutputBuffers, res, info); + if (mHasSurface || mCachedOutputBuffers == null) { mDequeuedOutputInfos.put(res, info.dup()); } } @@ -3670,9 +3669,9 @@ final public class MediaCodec { synchronized(mBufferLock) { switch (mBufferMode) { case BUFFER_MODE_LEGACY: - invalidateByteBuffer(mCachedOutputBuffers, index); + invalidateByteBufferLocked(mCachedOutputBuffers, index, false /* input */); mDequeuedOutputBuffers.remove(index); - if (mHasSurface) { + if (mHasSurface || mCachedOutputBuffers == null) { info = mDequeuedOutputInfos.remove(index); } break; @@ -3824,15 +3823,24 @@ final public class MediaCodec { private ByteBuffer[] mCachedInputBuffers; private ByteBuffer[] mCachedOutputBuffers; + private BitSet mValidInputIndices = new BitSet(); + private BitSet mValidOutputIndices = new BitSet(); + private final BufferMap mDequeuedInputBuffers = new BufferMap(); private final BufferMap mDequeuedOutputBuffers = new BufferMap(); private final Map<Integer, BufferInfo> mDequeuedOutputInfos = new HashMap<Integer, BufferInfo>(); final private Object mBufferLock; - private final void invalidateByteBuffer( - @Nullable ByteBuffer[] buffers, int index) { - if (buffers != null && index >= 0 && index < buffers.length) { + private void invalidateByteBufferLocked( + @Nullable ByteBuffer[] buffers, int index, boolean input) { + if (buffers == null) { + if (index < 0) { + throw new IllegalStateException("index is negative (" + index + ")"); + } + BitSet indices = input ? mValidInputIndices : mValidOutputIndices; + indices.clear(index); + } else if (index >= 0 && index < buffers.length) { ByteBuffer buffer = buffers[index]; if (buffer != null) { buffer.setAccessible(false); @@ -3840,9 +3848,14 @@ final public class MediaCodec { } } - private final void validateInputByteBuffer( + private void validateInputByteBufferLocked( @Nullable ByteBuffer[] buffers, int index) { - if (buffers != null && index >= 0 && index < buffers.length) { + if (buffers == null) { + if (index < 0) { + throw new IllegalStateException("index is negative (" + index + ")"); + } + mValidInputIndices.set(index); + } else if (index >= 0 && index < buffers.length) { ByteBuffer buffer = buffers[index]; if (buffer != null) { buffer.setAccessible(true); @@ -3851,10 +3864,16 @@ final public class MediaCodec { } } - private final void revalidateByteBuffer( - @Nullable ByteBuffer[] buffers, int index) { + private void revalidateByteBuffer( + @Nullable ByteBuffer[] buffers, int index, boolean input) { synchronized(mBufferLock) { - if (buffers != null && index >= 0 && index < buffers.length) { + if (buffers == null) { + if (index < 0) { + throw new IllegalStateException("index is negative (" + index + ")"); + } + BitSet indices = input ? mValidInputIndices : mValidOutputIndices; + indices.set(index); + } else if (index >= 0 && index < buffers.length) { ByteBuffer buffer = buffers[index]; if (buffer != null) { buffer.setAccessible(true); @@ -3863,9 +3882,14 @@ final public class MediaCodec { } } - private final void validateOutputByteBuffer( + private void validateOutputByteBufferLocked( @Nullable ByteBuffer[] buffers, int index, @NonNull BufferInfo info) { - if (buffers != null && index >= 0 && index < buffers.length) { + if (buffers == null) { + if (index < 0) { + throw new IllegalStateException("index is negative (" + index + ")"); + } + mValidOutputIndices.set(index); + } else if (index >= 0 && index < buffers.length) { ByteBuffer buffer = buffers[index]; if (buffer != null) { buffer.setAccessible(true); @@ -3874,7 +3898,7 @@ final public class MediaCodec { } } - private final void invalidateByteBuffers(@Nullable ByteBuffer[] buffers) { + private void invalidateByteBuffersLocked(@Nullable ByteBuffer[] buffers) { if (buffers != null) { for (ByteBuffer buffer: buffers) { if (buffer != null) { @@ -3884,27 +3908,29 @@ final public class MediaCodec { } } - private final void freeByteBuffer(@Nullable ByteBuffer buffer) { + private void freeByteBufferLocked(@Nullable ByteBuffer buffer) { if (buffer != null /* && buffer.isDirect() */) { // all of our ByteBuffers are direct java.nio.NioUtils.freeDirectBuffer(buffer); } } - private final void freeByteBuffers(@Nullable ByteBuffer[] buffers) { + private void freeByteBuffersLocked(@Nullable ByteBuffer[] buffers) { if (buffers != null) { for (ByteBuffer buffer: buffers) { - freeByteBuffer(buffer); + freeByteBufferLocked(buffer); } } } - private final void freeAllTrackedBuffers() { + private void freeAllTrackedBuffers() { synchronized(mBufferLock) { - freeByteBuffers(mCachedInputBuffers); - freeByteBuffers(mCachedOutputBuffers); + freeByteBuffersLocked(mCachedInputBuffers); + freeByteBuffersLocked(mCachedOutputBuffers); mCachedInputBuffers = null; mCachedOutputBuffers = null; + mValidInputIndices.clear(); + mValidOutputIndices.clear(); mDequeuedInputBuffers.clear(); mDequeuedOutputBuffers.clear(); mQueueRequests.clear(); @@ -3912,14 +3938,31 @@ final public class MediaCodec { } } - private final void cacheBuffers(boolean input) { + private void cacheBuffersLocked(boolean input) { ByteBuffer[] buffers = null; try { buffers = getBuffers(input); - invalidateByteBuffers(buffers); + invalidateByteBuffersLocked(buffers); } catch (IllegalStateException e) { // we don't get buffers in async mode } + if (buffers != null) { + BitSet indices = input ? mValidInputIndices : mValidOutputIndices; + for (int i = 0; i < buffers.length; ++i) { + ByteBuffer buffer = buffers[i]; + if (buffer == null || !indices.get(i)) { + continue; + } + buffer.setAccessible(true); + if (!input) { + BufferInfo info = mDequeuedOutputInfos.get(i); + if (info != null) { + buffer.limit(info.offset + info.size).position(info.offset); + } + } + } + indices.clear(); + } if (input) { mCachedInputBuffers = buffers; } else { @@ -3955,6 +3998,9 @@ final public class MediaCodec { + "objects and attach to QueueRequest objects."); } if (mCachedInputBuffers == null) { + cacheBuffersLocked(true /* input */); + } + if (mCachedInputBuffers == null) { throw new IllegalStateException(); } // FIXME: check codec status @@ -3993,6 +4039,9 @@ final public class MediaCodec { + "Please use getOutputFrame to get output frames."); } if (mCachedOutputBuffers == null) { + cacheBuffersLocked(false /* input */); + } + if (mCachedOutputBuffers == null) { throw new IllegalStateException(); } // FIXME: check codec status @@ -4030,7 +4079,7 @@ final public class MediaCodec { } ByteBuffer newBuffer = getBuffer(true /* input */, index); synchronized (mBufferLock) { - invalidateByteBuffer(mCachedInputBuffers, index); + invalidateByteBufferLocked(mCachedInputBuffers, index, true /* input */); mDequeuedInputBuffers.put(index, newBuffer); } return newBuffer; @@ -4067,7 +4116,7 @@ final public class MediaCodec { } Image newImage = getImage(true /* input */, index); synchronized (mBufferLock) { - invalidateByteBuffer(mCachedInputBuffers, index); + invalidateByteBufferLocked(mCachedInputBuffers, index, true /* input */); mDequeuedInputBuffers.put(index, newImage); } return newImage; @@ -4103,7 +4152,7 @@ final public class MediaCodec { } ByteBuffer newBuffer = getBuffer(false /* input */, index); synchronized (mBufferLock) { - invalidateByteBuffer(mCachedOutputBuffers, index); + invalidateByteBufferLocked(mCachedOutputBuffers, index, false /* input */); mDequeuedOutputBuffers.put(index, newBuffer); } return newBuffer; @@ -4138,7 +4187,7 @@ final public class MediaCodec { } Image newImage = getImage(false /* input */, index); synchronized (mBufferLock) { - invalidateByteBuffer(mCachedOutputBuffers, index); + invalidateByteBufferLocked(mCachedOutputBuffers, index, false /* input */); mDequeuedOutputBuffers.put(index, newImage); } return newImage; diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml index 35d13230a3b7..2aa26e321a91 100644 --- a/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml +++ b/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml @@ -20,6 +20,11 @@ android:layout_height="wrap_content" android:layout_width="match_parent" android:background="?android:attr/colorBackground" + android:minHeight="?android:attr/listPreferredItemHeight" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingTop="@dimen/settingslib_switchbar_margin" + android:paddingBottom="@dimen/settingslib_switchbar_margin" android:orientation="vertical"> <LinearLayout @@ -27,7 +32,6 @@ android:minHeight="@dimen/settingslib_min_switch_bar_height" android:layout_height="wrap_content" android:layout_width="match_parent" - android:layout_margin="@dimen/settingslib_switchbar_margin" android:paddingStart="@dimen/settingslib_switchbar_padding_left" android:paddingEnd="@dimen/settingslib_switchbar_padding_right"> diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml index dd36b49e824d..481d26d15141 100644 --- a/packages/SettingsLib/res/values-af/strings.xml +++ b/packages/SettingsLib/res/values-af/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-oudio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD oudio"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Gehoortoestelle"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE-oudio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Gekoppel aan gehoortoestelle"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Gekoppel aan LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Gekoppel aan LE-oudio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Gekoppel aan media-oudio"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Gekoppel aan foonoudio"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Gekoppel aan lêeroordragbediener"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Maak die groottes van alle aktiwiteite verstelbaar vir veelvuldige vensters, ongeag manifeswaardes."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Aktiveer vormvrye vensters"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Aktiveer steun vir eksperimentele vormvrye vensters."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Rekenaarmodus"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Werkskerm-rugsteunwagwoord"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Volle rekenaarrugsteune word nie tans beskerm nie"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Tik om die wagwoord vir volledige rekenaarrugsteune te verander of te verwyder"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> oor tot vol"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> oor tot vol"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> – Laaiproses word tydelik beperk"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Onbekend"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Laai"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Laai tans vinnig"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Laai tans stadig"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Laai tans draadloos"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Laaidok"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Laai nie"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Gekoppel, laai nie"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Gelaai"</string> diff --git a/packages/SettingsLib/res/values-am/arrays.xml b/packages/SettingsLib/res/values-am/arrays.xml index a900d1379027..7bef7fae5cdf 100644 --- a/packages/SettingsLib/res/values-am/arrays.xml +++ b/packages/SettingsLib/res/values-am/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"የስርዓቱን ምርጫ (ነባሪ) ተጠቀም"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ኦዲዮ"</item> + <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ኦዲዮ"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"የስርዓቱን ምርጫ (ነባሪ) ተጠቀም"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ኦዲዮ"</item> + <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ኦዲዮ"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"የስርዓቱን ምርጫ (ነባሪ) ተጠቀም"</item> <item msgid="8003118270854840095">"44.1 ኪኸ"</item> diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml index 201877877916..9a51181105ad 100644 --- a/packages/SettingsLib/res/values-am/strings.xml +++ b/packages/SettingsLib/res/values-am/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"ኤችዲ ኦዲዮ፦ <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"ኤችዲ ኦዲዮ"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"አጋዥ መስሚያዎች"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE ኦዲዮ"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"ከአጋዥ መስሚያዎች ጋር ተገናኝቷል"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"ከLE_AUDIO ጋር ተገናኝቷል"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"ከLE ኦዲዮ ጋር ተገናኝቷል"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"ወደ ማህደረ መረጃ አውዲዮ ተያይዟል"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ወደ ስልክ አውዲዮ ተያይዟል"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"ወደ ፋይል ዝውውር አገልጋይ ተያይዟል"</string> @@ -183,21 +183,21 @@ <string name="running_process_item_user_label" msgid="3988506293099805796">"ተጠቃሚ፦ <xliff:g id="USER_NAME">%1$s</xliff:g>"</string> <string name="launch_defaults_some" msgid="3631650616557252926">"አንዳንድ ነባሪዎ ተዘጋጅተዋል"</string> <string name="launch_defaults_none" msgid="8049374306261262709">"ምንም ነባሪዎች አልተዘጋጁም"</string> - <string name="tts_settings" msgid="8130616705989351312">"ፅሁፍ-ወደ-ንግግር ቅንብሮች"</string> + <string name="tts_settings" msgid="8130616705989351312">"ጽሁፍ-ወደ-ንግግር ቅንብሮች"</string> <string name="tts_settings_title" msgid="7602210956640483039">"የፅሁፍ- ወደ- ንግግር ውፅዓት"</string> <string name="tts_default_rate_title" msgid="3964187817364304022">" የንግግር ደረጃ"</string> - <string name="tts_default_rate_summary" msgid="3781937042151716987">"የተነገረበትን ፅሁፍ አፍጥን"</string> + <string name="tts_default_rate_summary" msgid="3781937042151716987">"የተነገረበትን ጽሁፍ አፍጥን"</string> <string name="tts_default_pitch_title" msgid="6988592215554485479">"ቅላፄ"</string> <string name="tts_default_pitch_summary" msgid="9132719475281551884">"በሲንተሲስ በተሠራው ድምፅ ላይ ተፅዕኖ ያሳድራል"</string> <string name="tts_default_lang_title" msgid="4698933575028098940">"ቋንቋ"</string> <string name="tts_lang_use_system" msgid="6312945299804012406">"የስርዓት ቋንቋ ተጠቀም"</string> <string name="tts_lang_not_selected" msgid="7927823081096056147">"ቋንቋ አልተመረጠም"</string> - <string name="tts_default_lang_summary" msgid="9042620014800063470">"ለሚነገረው ፅሁፍ ቋንቋ-ተኮር ድምፅ አዘጋጅ"</string> + <string name="tts_default_lang_summary" msgid="9042620014800063470">"ለሚነገረው ጽሁፍ ቋንቋ-ተኮር ድምፅ አዘጋጅ"</string> <string name="tts_play_example_title" msgid="1599468547216481684">"ምሳሌውን አዳምጥ"</string> <string name="tts_play_example_summary" msgid="634044730710636383">"አጭር የንግግር ልምምድ ማሳያ አጫውት"</string> <string name="tts_install_data_title" msgid="1829942496472751703">"የድምፅ ውሂብ ጫን"</string> <string name="tts_install_data_summary" msgid="3608874324992243851">"ለንግግር ልምምድ የሚጠየቀውን የድምፅ ውሂብ ጫን"</string> - <string name="tts_engine_security_warning" msgid="3372432853837988146">"ይህ የንግግር ልምምድ አንቀሳቃሽ የሚነገረውን ፅሁፍ ሁሉ እንደ ይለፍ ቃል እና የዱቤ ካርድ ቁጥሮች፣ የግል ውሂብ ጨምሮ ለመሰብሰብ ይችል ይሆናል። ከ <xliff:g id="TTS_PLUGIN_ENGINE_NAME">%s</xliff:g> አንቀሳቃሽ ይመጣል። የዚህን የንግግር ልምምድ አንቀሳቃሽ አጠቃቀም ይንቃ?"</string> + <string name="tts_engine_security_warning" msgid="3372432853837988146">"ይህ የንግግር ልምምድ አንቀሳቃሽ የሚነገረውን ጽሁፍ ሁሉ እንደ ይለፍ ቃል እና የዱቤ ካርድ ቁጥሮች፣ የግል ውሂብ ጨምሮ ለመሰብሰብ ይችል ይሆናል። ከ <xliff:g id="TTS_PLUGIN_ENGINE_NAME">%s</xliff:g> አንቀሳቃሽ ይመጣል። የዚህን የንግግር ልምምድ አንቀሳቃሽ አጠቃቀም ይንቃ?"</string> <string name="tts_engine_network_required" msgid="8722087649733906851">"ይህ ቋንቋ የጽሑፍ-ወደ-ንግግር ውጽዓት እንዲኖረው የሚሰራ የአውታረ መረብ ግንኙነት ያስፈልገዋል።"</string> <string name="tts_default_sample_string" msgid="6388016028292967973">"ይህ የተሰራ ንግግር ምሳሌ ነው"</string> <string name="tts_status_title" msgid="8190784181389278640">"የነባሪ ቋንቋ ሁኔታ"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"የዝርዝር ሰነድ እሴቶች ምንም ይሁኑ ምን ለበርካታ መስኮቶች ሁሉንም እንቅስቃሴዎች መጠናቸው የሚቀየሩ እንዲሆኑ ያደርጋቸዋል።"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"የነጻ ቅርጽ መስኮቶችን ያንቁ"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"የሙከራ ነጻ መልክ መስኮቶች ድጋፍን አንቃ"</string> + <string name="desktop_mode" msgid="2389067840550544462">"የዴስክቶፕ ሁነታ"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"የዴስክቶፕ መጠባበቂያ ይለፍ ቃል"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"ዴስክቶፕ ሙሉ ምትኬዎች በአሁኑ ሰዓት አልተጠበቁም"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"የዴስክቶፕ ሙሉ ምትኬዎች የይለፍ ቃሉን ለመለወጥ ወይም ለማስወገድ ነካ ያድርጉ"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"እስኪሞላ ድረስ <xliff:g id="TIME">%1$s</xliff:g> ይቀራል"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - እስኪሞላ ድረስ <xliff:g id="TIME">%2$s</xliff:g> ይቀራል"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - ኃይል መሙላት ለጊዜው ተገድቧል"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"ያልታወቀ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ኃይል በመሙላት ላይ"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ኃይል በፍጥነት በመሙላት ላይ"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"ኃይል በዝግታ በመሙላት ላይ"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"በገመድ-አልባ ኃይል በመሙላት ላይ"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"የኃይል መሙያ መትከያ"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ባትሪ እየሞላ አይደለም"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"ተገናኝቷል፣ ኃይል በመሙላት ላይ አይደለም"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"ባትሪ ሞልቷል"</string> @@ -514,7 +517,7 @@ <string name="active_input_method_subtypes" msgid="4232680535471633046">"የገባሪ ግቤት ዘዴ"</string> <string name="use_system_language_to_select_input_method_subtypes" msgid="4865195835541387040">"የሥርዓት ቋንቋዎችን ይጠቀሙ"</string> <string name="failed_to_open_app_settings_toast" msgid="764897252657692092">"የ<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> ቅንብሮች መክፈት አልተሳካም"</string> - <string name="ime_security_warning" msgid="6547562217880551450">"ይህ ግቤት ስልት የሚትተይበውን ፅሁፍ ሁሉ፣ እንደይለፍ ቃል እና የብድር ካርድ ጨምሮ የግል ውሂብ ምናልባት መሰብሰብ ይችላል። ከትግበራው ይመጣል። <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> ይህን ግቤት ስልትይጠቀም?"</string> + <string name="ime_security_warning" msgid="6547562217880551450">"ይህ ግቤት ስልት የሚትተይበውን ጽሁፍ ሁሉ፣ እንደይለፍ ቃል እና የብድር ካርድ ጨምሮ የግል ውሂብ ምናልባት መሰብሰብ ይችላል። ከትግበራው ይመጣል። <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> ይህን ግቤት ስልትይጠቀም?"</string> <string name="direct_boot_unaware_dialog_message" msgid="7845398276735021548">"ማስታወሻ፦ እንደገና ከማስነሳት በኋላ ይህ መተግበሪያ ስልክዎን እስከሚከፍቱት ድረስ ሊጀምር አይችልም"</string> <string name="ims_reg_title" msgid="8197592958123671062">"የIMS ምዝገባ ቀን"</string> <string name="ims_reg_status_registered" msgid="884916398194885457">"የተመዘገበ"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"የአየር ጥራት"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"የCast መረጃ"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"የቤት ውስጥ ቁጥጥሮች"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"የመገለጫ ሥዕል ይምረጡ"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"ነባሪ የተጠቃሚ አዶ"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"አካላዊ ቁልፍ ሰሌዳ"</string> diff --git a/packages/SettingsLib/res/values-ar/arrays.xml b/packages/SettingsLib/res/values-ar/arrays.xml index 8f7d7d2ca399..0720cf58f523 100644 --- a/packages/SettingsLib/res/values-ar/arrays.xml +++ b/packages/SettingsLib/res/values-ar/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"استخدام اختيار النظام (تلقائي)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"صوت <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="2908219194098827570">"صوت <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"استخدام اختيار النظام (تلقائي)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"صوت <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="3517061573669307965">"صوت <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"استخدام اختيار النظام (تلقائي)"</item> <item msgid="8003118270854840095">"44.1 كيلو هرتز"</item> diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml index 65c8edc20aac..086956b7f40a 100644 --- a/packages/SettingsLib/res/values-ar/strings.xml +++ b/packages/SettingsLib/res/values-ar/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"صوت عالي الدقة: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"صوت عالي الدقة"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"سماعات الأذن الطبية"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE Audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"تمّ التوصيل بسماعات الأذن الطبية"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"متصل بـ LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"متصل بـ LE Audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"متصل بالإعدادات الصوتية للوسائط"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"متصل بالإعدادات الصوتية للهاتف"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"متصل بخادم نقل الملف"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"السماح بتغيير حجم جميع الأنشطة لتناسب تعدد النوافذ، بغض النظر عن قيم البيان"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"تفعيل النوافذ الحرة"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"إتاحة استخدام النوافذ الحرة التجريبية"</string> + <string name="desktop_mode" msgid="2389067840550544462">"وضع سطح المكتب"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"كلمة مرور احتياطية للكمبيوتر"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"النُسخ الاحتياطية الكاملة لسطح المكتب غير محمية في الوقت الحالي."</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"انقر لتغيير كلمة مرور النسخ الاحتياطية الكاملة لسطح المكتب أو إزالتها."</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"يتبقّى <xliff:g id="TIME">%1$s</xliff:g> حتى اكتمال شحن البطارية."</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - يتبقّى <xliff:g id="TIME">%2$s</xliff:g> حتى اكتمال شحن البطارية."</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - الشحن محدود مؤقتًا"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"غير معروف"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"جارٍ الشحن"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"جارٍ الشحن سريعًا"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"جارٍ الشحن ببطء"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"جارٍ الشحن لاسلكيًا"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"وحدة الإرساء للشحن"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"لا يتم الشحن"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"الجهاز متصل بالشاحن، ولا يتم الشحن."</string> <string name="battery_info_status_full" msgid="1339002294876531312">"مشحونة"</string> @@ -590,7 +593,7 @@ <string name="add_user_failed" msgid="4809887794313944872">"تعذّر إنشاء مستخدم جديد."</string> <string name="add_guest_failed" msgid="8074548434469843443">"تعذّر إنشاء جلسة ضيف جديدة."</string> <string name="user_nickname" msgid="262624187455825083">"اللقب"</string> - <string name="user_add_user" msgid="7876449291500212468">"إضافة حساب مستخدم"</string> + <string name="user_add_user" msgid="7876449291500212468">"إضافة مستخدم"</string> <string name="guest_new_guest" msgid="3482026122932643557">"إضافة ضيف"</string> <string name="guest_exit_guest" msgid="5908239569510734136">"إزالة جلسة الضيف"</string> <string name="guest_reset_guest" msgid="6110013010356013758">"إعادة ضبط جلسة الضيف"</string> @@ -600,7 +603,7 @@ <string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"إزالة"</string> <string name="guest_resetting" msgid="7822120170191509566">"جارٍ إعادة ضبط جلسة الضيف…"</string> <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"هل تريد إعادة ضبط جلسة الضيف؟"</string> - <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"سيؤدي إجراء إعادة الضبط إلى بدء جلسة ضيف جديدة وحذف جميع التطبيقات والبيانات من الجلسة الحالية."</string> + <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"ستؤدي إعادة الضبط إلى بدء جلسة ضيف جديدة وحذف جميع التطبيقات والبيانات من الجلسة الحالية."</string> <string name="guest_exit_dialog_title" msgid="1846494656849381804">"هل تريد الخروج من وضع الضيف؟"</string> <string name="guest_exit_dialog_message" msgid="1743218864242719783">"سيؤدي الخروج من وضع الضيف إلى حذف التطبيقات والبيانات من جلسة الضيف الحالية."</string> <string name="guest_exit_dialog_button" msgid="1736401897067442044">"خروج"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"جودة الهواء"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"معلومات البث"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"إدارة آلية للمنزل"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"اختيار صورة الملف الشخصي"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"رمز المستخدم التلقائي"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"لوحة مفاتيح خارجية"</string> diff --git a/packages/SettingsLib/res/values-as/arrays.xml b/packages/SettingsLib/res/values-as/arrays.xml index 4c879d0123d3..cbacce8becba 100644 --- a/packages/SettingsLib/res/values-as/arrays.xml +++ b/packages/SettingsLib/res/values-as/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"ছিষ্টেমৰ বাছনি ব্যৱহাৰ কৰক (ডিফ\'ল্ট)"</item> + <item msgid="4055460186095649420">"এছবিচি"</item> + <item msgid="720249083677397051">"এএচি"</item> + <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> অডিঅ\'"</item> + <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> অডিঅ’"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"ছিষ্টেমৰ বাছনি ব্যৱহাৰ কৰক (ডিফ\'ল্ট)"</item> + <item msgid="9024885861221697796">"এছবিচি"</item> + <item msgid="4688890470703790013">"এএচি"</item> + <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> অডিঅ’"</item> + <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> অডিঅ’"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"ছিষ্টেমৰ বাছনি ব্যৱহাৰ কৰক (ডিফ\'ল্ট)"</item> <item msgid="8003118270854840095">"৪৪.১ কিল\'হাৰ্টজ"</item> diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml index 402c615e232e..9e0f9107da61 100644 --- a/packages/SettingsLib/res/values-as/strings.xml +++ b/packages/SettingsLib/res/values-as/strings.xml @@ -121,13 +121,13 @@ <string name="bluetooth_profile_pbap_summary" msgid="6466456791354759132">"সম্পৰ্কসূচী আৰু কলৰ ইতিহাস শ্বেয়াৰ কৰাৰ বাবে ব্যৱহাৰ কৰক"</string> <string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"ইণ্টাৰনেট সংযোগ শ্বেয়াৰ"</string> <string name="bluetooth_profile_map" msgid="8907204701162107271">"পাঠ বাৰ্তা"</string> - <string name="bluetooth_profile_sap" msgid="8304170950447934386">"ছিম প্ৰৱেশ"</string> + <string name="bluetooth_profile_sap" msgid="8304170950447934386">"ছিমৰ এক্সেছ"</string> <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"এইচ্ছডি অডি\'অ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"এইচ্ছডি অডিঅ’"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"শ্ৰৱণ যন্ত্ৰ"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE অডিঅ’"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"শ্ৰৱণ যন্ত্ৰলৈ সংযোগ কৰা হৈছে"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"LE_AUDIOৰ সৈতে সংযোগ কৰক"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE অডিঅ’ৰ সৈতে সংযোগ কৰক"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"মিডিয়া অডিঅ’লৈ সংযোগ হৈছে"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ফ’ন অডিঅ\'ৰ লগত সংযোগ কৰা হ’ল"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"ফাইল ট্ৰান্সফাৰ ছাৰ্ভাৰৰ সৈতে সংযোজিত হৈ আছে"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"মেনিফেষ্টৰ মান যিয়েই নহওক, মাল্টি-ৱিণ্ডৰ বাবে আটাইবোৰ কাৰ্যকলাপৰ আকাৰ সলনি কৰিব পৰা কৰক।"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"ফ্ৰিফৰ্ম ৱিণ্ড\'জ সক্ষম কৰক"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"পৰীক্ষামূলক ফ্ৰী-ফৰ্ম ৱিণ্ড’বোৰৰ বাবে সহায়তা সক্ষম কৰক৷"</string> + <string name="desktop_mode" msgid="2389067840550544462">"ডেস্কটপ ম’ড"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"ডেস্কটপ বেকআপ পাছৱৰ্ড"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"ডেস্কটপৰ পূৰ্ণ বেকআপ এতিয়ালৈকে সংৰক্ষিত অৱস্থাত নাই"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"ডেস্কটপ সম্পূৰ্ণ বেকআপৰ বাবে পাছৱৰ্ডটো সলনি কৰিবলৈ বা আঁতৰাবলৈ টিপক"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"সম্পূৰ্ণ হ’বলৈ <xliff:g id="TIME">%1$s</xliff:g> বাকী আছে"</string> <string name="power_charging_duration" msgid="6127154952524919719">"সম্পূৰ্ণ হ’বলৈ <xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> বাকী আছে"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> • চাৰ্জ কৰাটো সাময়িকভাৱে সীমিত কৰা হৈছে"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"অজ্ঞাত"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"চাৰ্জ কৰি থকা হৈছে"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"দ্ৰুততাৰে চাৰ্জ হৈছে"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"লাহে লাহে চাৰ্জ হৈছে"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"বেতাঁৰৰ মাধ্যমেৰে চাৰ্জ হৈ আছে"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"চাৰ্জিং ড’ক"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"চ্চাৰ্জ কৰা নাই"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"সংযোগ হৈ আছে, চাৰ্জ হৈ থকা নাই"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"চাৰ্জ হ’ল"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"বায়ুৰ গুণগত মান"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"কাষ্টৰ তথ্য"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"গৃহ নিয়ন্ত্ৰণ"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"স্মাৰ্টস্পেচ"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"এখন প্ৰ’ফাইল চিত্ৰ বাছনি কৰক"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"ডিফ’ল্ট ব্যৱহাৰকাৰীৰ চিহ্ন"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"কায়িক কীব’ৰ্ড"</string> diff --git a/packages/SettingsLib/res/values-az/arrays.xml b/packages/SettingsLib/res/values-az/arrays.xml index 48974a7bd40f..d1f157af8194 100644 --- a/packages/SettingsLib/res/values-az/arrays.xml +++ b/packages/SettingsLib/res/values-az/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"Sistem Seçimini istifadə edin (Defolt)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item> + <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"Sistem Seçimini istifadə edin (Defolt)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item> + <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"Sistem Seçimini istifadə edin (Defolt)"</item> <item msgid="8003118270854840095">"44.1 kHz"</item> diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml index 89b91e69bac0..f6da021703f5 100644 --- a/packages/SettingsLib/res/values-az/strings.xml +++ b/packages/SettingsLib/res/values-az/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Eşitmə cihazları"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Eşitmə Aparatlarına qoşuldu"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"LE_AUDIO audiosuna qoşulub"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE audiosuna qoşulub"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Media audioya birləşdirilib"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Telefon audiosuna qoşulu"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Fayl transfer serverinə qoşulu"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Manifest dəyərindən asılı olmayaraq çoxpəncərəli rejimdə pəncərə ölçüsünün dəyişdirilməsinə icazə verilsin"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"İxtiyari formada pəncərə yaradılsın"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Eksperimental olaraq ixtiyari formada pəncərə yaradılsın"</string> + <string name="desktop_mode" msgid="2389067840550544462">"Masaüstü rejimi"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Masaüstü rezerv parolu"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Masaüstü tam rezervlər hazırda qorunmayıblar."</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Masaüstünün tam rezerv kopyalanması üçün parolu dəyişmək və ya silmək üçün basın"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Tam şarj edilənədək <xliff:g id="TIME">%1$s</xliff:g> qalıb"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - tam şarj edilənədək <xliff:g id="TIME">%2$s</xliff:g> qalıb"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - Şarj müvəqqəti məhdudlaşdırılıb"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Naməlum"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Enerji doldurma"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Sürətlə doldurulur"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Asta doldurulur"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Simsiz şarj edilir"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Şarj Doku"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Doldurulmur"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Qoşulub, şarj edilmir"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Şarj edilib"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Havanın keyfiyyəti"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Yayım məlumatı"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Əsas səhifə kontrolları"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"Profil şəkli seçin"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"Defolt istifadəçi ikonası"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"Fiziki klaviatura"</string> diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml index 13c3f412f518..a21a89a5e7f8 100644 --- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml +++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD zvuk: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD zvuk"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Slušni aparati"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Povezano sa slušnim aparatima"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Povezano sa LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Povezano sa LE audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Povezano sa zvukom medija"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Povezano sa zvukom telefona"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Povezano sa serverom za prenos datoteka"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Omogućava promenu veličine svih aktivnosti za režim sa više prozora, bez obzira na vrednosti manifesta."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Omogući prozore proizvoljnog formata"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Omogućava podršku za eksperimentalne prozore proizvoljnog formata."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Režim za računare"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Lozinka rezervne kopije za računar"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Rezervne kopije čitavog sistema trenutno nisu zaštićene"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Dodirnite da biste promenili ili uklonili lozinku za pravljenje rezervnih kopija čitavog sistema na računaru"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> do kraja punjenja"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do kraja punjenja"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> – Punjenje je privremeno ograničeno"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Nepoznato"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Puni se"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Brzo se puni"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Sporo se puni"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Bežično punjenje"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Stanica za punjenje"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ne puni se"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Povezano, ne puni se"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Napunjeno"</string> @@ -601,7 +604,7 @@ <string name="guest_resetting" msgid="7822120170191509566">"Sesija gosta se resetuje…"</string> <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Želite da resetujete sesiju gosta?"</string> <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Time ćete pokrenuti novu sesiju gosta i izbrisati sve aplikacije i podatke iz aktuelne sesije"</string> - <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Izaći ćete iz režima gosta?"</string> + <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Izlazite iz režima gosta?"</string> <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Time ćete izbrisati sve aplikacije i podatke iz aktuelne sesije gosta"</string> <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Izađi"</string> <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Sačuvaćete aktivnosti gosta?"</string> @@ -610,7 +613,7 @@ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Sačuvaj"</string> <string name="guest_exit_button" msgid="5774985819191803960">"Izađi iz režima gosta"</string> <string name="guest_reset_button" msgid="2515069346223503479">"Resetuj sesiju gosta"</string> - <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Izađi iz režima gosta"</string> + <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Zatvori režim gosta"</string> <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Sve aktivnosti će biti izbrisane pri izlazu"</string> <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Možete da sačuvate ili izbrišete aktivnosti pri izlazu"</string> <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Resetujete za brisanje aktivnosti sesije, ili sačuvajte ili izbrišite aktivnosti pri izlazu"</string> diff --git a/packages/SettingsLib/res/values-be/arrays.xml b/packages/SettingsLib/res/values-be/arrays.xml index d843629a43c1..f16e1c50f0fa 100644 --- a/packages/SettingsLib/res/values-be/arrays.xml +++ b/packages/SettingsLib/res/values-be/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"Выбар сістэмы (стандартны)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"Аўдыя <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="2908219194098827570">"Аўдыя <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"Выбар сістэмы (стандартны)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"Аўдыя <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="3517061573669307965">"Аўдыя <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"Выбар сістэмы (стандартны)"</item> <item msgid="8003118270854840095">"44,1 кГц"</item> diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml index 75d974ab2c19..316cefa99e01 100644 --- a/packages/SettingsLib/res/values-be/strings.xml +++ b/packages/SettingsLib/res/values-be/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Аўдыя ў HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Аўдыя ў HD"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Слыхавыя апараты"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"Le audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Падключана да слыхавых апаратаў"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Падключана да LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Падключана да LE audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Падключана да аўдыё медыа"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Падключана да аўдыё тэлефона"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Падключаны да серверу перадачы файлаў"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Зрабіць усе віды дзейнасці даступнымі для змены памеру ў рэжыме некалькіх вокнаў, незалежна ад значэнняў маніфеста."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Уключыць адвольную форму вокнаў"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Уключыць падтрымку для эксперыментальнай адвольнай формы акна."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Рэжым працоўнага стала"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Пароль для рэз. копіі ПК"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Поўнае рэзервовае капіраванне працоўнага стала зараз не абаронена"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Краніце, каб змяніць або выдаліць пароль для поўнага рэзервовага капіравання працоўнага стала"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Да поўнай зарадкі засталося <xliff:g id="TIME">%1$s</xliff:g>"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – да поўнай зарадкі засталося: <xliff:g id="TIME">%2$s</xliff:g>"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> – Зарадка часова абмежавана"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Невядома"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Зарадка"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Хуткая зарадка"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Павольная зарадка"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Бесправадная зарадка"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Зарадная док-станцыя"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не зараджаецца"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Падключана, не зараджаецца"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Зараджаны"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Якасць паветра"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Даныя пра трансляцыю"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Кіраванне домам"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"Выберыце відарыс профілю"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"Стандартны карыстальніцкі значок"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"Фізічная клавіятура"</string> diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml index c146ef529d64..f7452dff115b 100644 --- a/packages/SettingsLib/res/values-bg/strings.xml +++ b/packages/SettingsLib/res/values-bg/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Висококачествено аудио: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Висококачествено аудио"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Слухови апарати"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE Audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Установена е връзка със слухов апарат"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Свързано с(ъс) LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Свързано с LE Audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Установена е връзка с медийно аудио"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Връзка със звука на телефона"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Установена е връзка със сървър за трансфер на файлове"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Дава възможност за преоразмеряване на всички активности в режима за няколко прозореца независимо от стойностите в манифеста."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Активиране на прозорците в свободна форма"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Активиране на поддръжката за експерименталните прозорци в свободна форма."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Режим за компютри"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Парола за резервни копия"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Понастоящем пълните резервни копия за настолен компютър не са защитени"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Докоснете, за да промените или премахнете паролата за пълни резервни копия на настолния компютър"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Оставащо време до пълно зареждане: <xliff:g id="TIME">%1$s</xliff:g>"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – Оставащо време до пълно зареждане: <xliff:g id="TIME">%2$s</xliff:g>"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> – зареждането временно е ограничено"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Неизвестно"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Зарежда се"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Зарежда се бързо"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Зарежда се бавно"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Зарежда се безжично"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Докинг станция за зарежд."</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не се зарежда"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Свързано, не се зарежда"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Заредена"</string> diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml index 1d1472ea5454..0c0c7378333f 100644 --- a/packages/SettingsLib/res/values-bn/strings.xml +++ b/packages/SettingsLib/res/values-bn/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD অডিও: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD অডিও"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"হিয়ারিং এড"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE অডিও"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"হিয়ারিং এডের সাথে কানেক্ট করা হয়েছে"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"LE_AUDIO-এ কানেক্ট করা হয়েছে"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE অডিও কানেক্ট করা হয়েছে"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"মিডিয়া অডিওতে কানেক্ট রয়েছে"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ফোন অডিওতে কানেক্ট"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"ফাইল স্থানান্তর সার্ভারের সঙ্গে কানেক্ট"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"ম্যানিফেস্ট মানগুলির নির্বিশেষে মাল্টি-উইন্ডোর জন্য সমস্ত ক্রিয়াকলাপগুলির আকার পরিবর্তনযোগ্য করুন৷"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"ফ্রি-ফর্ম উইন্ডোগুলি সক্ষম করুন"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"পরীক্ষামূলক ফ্রি-ফর্ম উইন্ডোগুলির জন্য সহায়তা সক্ষম করুন৷"</string> + <string name="desktop_mode" msgid="2389067840550544462">"\'ডেস্কটপ\' মোড"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"ডেস্কটপ ব্যাকআপ পাসওয়ার্ড"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"ডেস্কটপ পূর্ণ ব্যাকআপ বর্তমানে সুরক্ষিত নয়"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"ডেস্কটপের সম্পূর্ণ ব্যাকআপের পাসওয়ার্ডটি পরিবর্তন করতে বা মুছে ফেলতে আলতো চাপুন"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g>-এ ব্যাটারি পুরো চার্জ হয়ে যাবে"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>-এ ব্যাটারি পুরো চার্জ হয়ে যাবে"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - চার্জ সাময়িকভাবে বন্ধ করা আছে"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"অজানা"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"চার্জ হচ্ছে"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"দ্রুত চার্জ হচ্ছে"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"ধীরে চার্জ হচ্ছে"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"কেবল ছাড়া চার্জ হচ্ছে"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"চার্জিং ডক"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"চার্জ হচ্ছে না"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"কানেক্ট করা থাকলেও চার্জ করা হচ্ছে না"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"চার্জ হয়েছে"</string> diff --git a/packages/SettingsLib/res/values-bs/arrays.xml b/packages/SettingsLib/res/values-bs/arrays.xml index 262a35feab65..926ad8464ccf 100644 --- a/packages/SettingsLib/res/values-bs/arrays.xml +++ b/packages/SettingsLib/res/values-bs/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"Korištenje odabira sistema (zadano)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item> + <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"Korištenje odabira sistema (zadano)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item> + <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"Korištenje odabira sistema (zadano)"</item> <item msgid="8003118270854840095">"44,1 kHz"</item> diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml index d85fa5657c4b..28c80e9bb268 100644 --- a/packages/SettingsLib/res/values-bs/strings.xml +++ b/packages/SettingsLib/res/values-bs/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Slušni aparati"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE zvuk"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Povezan na slušne aparate"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Povezano sa: LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Povezano s LE zvukom"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Povezano sa zvukom medija"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Povezano na zvuk telefona"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Povezano sa serverom za prijenos podataka"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Omogućava mijenjanje veličine svih aktivnosti za prikaz s više prozora, bez obzira na prikazane vrijednosti."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Omogući prozore nepravilnih oblika"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Omogućava podršku za eksperimentalne prozore nepravilnih oblika."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Način rada radne površine"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Lozinka sigurnosne kopije za računar"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Potpune sigurnosne kopije za računare trenutno nisu zaštićene"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Dodirnite da promijenite ili uklonite lozinku za potpune rezervne kopije s radne površine"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> do potpune napunjenosti"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do potpune napunjenosti"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> – Punjenje je privremeno ograničeno"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Nepoznato"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Punjenje"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Brzo punjenje"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Sporo punjenje"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Bežično punjenje"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Priključna stanica"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ne puni se"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Povezano, ne puni se"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Napunjeno"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Kvalitet zraka"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Podaci o emitiranju"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Kontrole doma"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"Odaberite sliku profila"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"Zadana ikona korisnika"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"Fizička tastatura"</string> diff --git a/packages/SettingsLib/res/values-ca/arrays.xml b/packages/SettingsLib/res/values-ca/arrays.xml index 8c34a1f0f72a..b6f15903ecbd 100644 --- a/packages/SettingsLib/res/values-ca/arrays.xml +++ b/packages/SettingsLib/res/values-ca/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"Utilitza la selecció del sistema (predeterminada)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"Àudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="2908219194098827570">"Àudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"Utilitza la selecció del sistema (predeterminada)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"Àudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="3517061573669307965">"Àudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"Utilitza la selecció del sistema (predeterminada)"</item> <item msgid="8003118270854840095">"44,1 kHz"</item> diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml index 9ddba82177d5..e17adb1546ba 100644 --- a/packages/SettingsLib/res/values-ca/strings.xml +++ b/packages/SettingsLib/res/values-ca/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Àudio HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Àudio HD"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Audiòfons"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE Audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"S\'ha connectat als audiòfons"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Connectat a LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Connectat a LE Audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Connectat a l\'àudio del mitjà"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Connectat a àudio del telèfon"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Connectat al servidor de transferència de fitxers"</string> @@ -408,6 +408,8 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Permet ajustar la mida de totes les activitats per al mode multifinestra, independentment dels valors definits"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Activa les finestres de forma lliure"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Activa la compatibilitat amb finestres de forma lliure experimentals"</string> + <!-- no translation found for desktop_mode (2389067840550544462) --> + <skip /> <string name="local_backup_password_title" msgid="4631017948933578709">"Contrasenya per a còpies d\'ordinador"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Les còpies de seguretat completes d\'ordinador no estan protegides"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Toca per canviar o suprimir la contrasenya per a les còpies de seguretat completes de l\'ordinador"</string> @@ -476,13 +478,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> per completar la càrrega"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> per completar la càrrega"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g>: càrrega limitada temporalment"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconegut"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"S\'està carregant"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Carregant ràpidament"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Carregant lentament"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Carregant sense fil"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Base de càrrega"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"No s\'està carregant"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Connectat; no s\'està carregant"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Carregada"</string> @@ -661,8 +665,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Qualitat de l\'aire"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Informació d\'emissió"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Domòtica"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"D\'una ullada"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"Tria una foto de perfil"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"Icona d\'usuari predeterminat"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"Teclat físic"</string> diff --git a/packages/SettingsLib/res/values-cs/arrays.xml b/packages/SettingsLib/res/values-cs/arrays.xml index 90bcaa4ba386..e1d033cd289b 100644 --- a/packages/SettingsLib/res/values-cs/arrays.xml +++ b/packages/SettingsLib/res/values-cs/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"Použít systémový výběr (výchozí)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"Zvuk <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="2908219194098827570">"Zvuk <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"Použít systémový výběr (výchozí)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"Zvuk <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="3517061573669307965">"Zvuk <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"Použít systémový výběr (výchozí)"</item> <item msgid="8003118270854840095">"44,1 kHz"</item> diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml index 7b959f49162b..eb3a05cc2d5e 100644 --- a/packages/SettingsLib/res/values-cs/strings.xml +++ b/packages/SettingsLib/res/values-cs/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD zvuk: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD zvuk"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Naslouchátka"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE Audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Připojeno k naslouchátkům"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Připojeno k LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Připojeno k LE Audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Připojeno ke zvukovému médiu"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Připojeno k náhlavní soupravě"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Připojeno k serveru pro přenos dat"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Umožní změnu velikosti všech aktivit na několik oken (bez ohledu na hodnoty manifestu)"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Aktivovat okna s volným tvarem"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Aktivuje podporu experimentálních oken s volným tvarem"</string> + <string name="desktop_mode" msgid="2389067840550544462">"Režim počítače"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Heslo pro zálohy v počítači"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Úplné zálohy v počítači nejsou v současné době chráněny"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Tuto možnost vyberte, chcete-li změnit nebo odebrat heslo pro úplné zálohy do počítače"</string> @@ -448,7 +449,7 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomálie (červená a zelená)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomálie (modrá a žlutá)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Korekce barev"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Korekce barev se může hodit, když chcete:<br/> <ol> <li>&nbsp;Zobrazit přesnější barvy.</li> <li>&nbsp;Odstranit barvy kvůli zlepšení soustředění.</li> </ol>"</string> + <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Korekce barev se může hodit, když chcete:<br/> <ol> <li>&nbsp;Vidět barvy přesněji.</li> <li>&nbsp;Odstranit barvy kvůli zlepšení soustředění.</li> </ol>"</string> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Přepsáno nastavením <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Zbývá asi <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> do úplného nabití"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do úplného nabití"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> – nabíjení je dočasně omezeno"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Neznámé"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Nabíjí se"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Rychlé nabíjení"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Pomalé nabíjení"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Bezdrátové nabíjení"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Nabíjecí dok"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nenabíjí se"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Připojeno, nenabíjí se"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Nabito"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Kvalita vzduchu"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info o odesílání"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Ovládání domácnosti"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"Vyberte profilový obrázek"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"Výchozí uživatelská ikona"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"Fyzická klávesnice"</string> diff --git a/packages/SettingsLib/res/values-da/arrays.xml b/packages/SettingsLib/res/values-da/arrays.xml index 155104ae81dc..48a33f61a427 100644 --- a/packages/SettingsLib/res/values-da/arrays.xml +++ b/packages/SettingsLib/res/values-da/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"Brug systemvalg (standard)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-lyd"</item> + <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-lyd"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"Brug systemvalg (standard)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-lyd"</item> + <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-lyd"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"Brug systemvalg (standard)"</item> <item msgid="8003118270854840095">"44,1 kHz"</item> diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml index 951d417639f6..84fca3db8720 100644 --- a/packages/SettingsLib/res/values-da/strings.xml +++ b/packages/SettingsLib/res/values-da/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-lyd: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD-lyd"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Høreapparater"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE Audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Forbundet til høreapparater"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Forbundet med LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Forbundet med LE Audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Forbundet til medielyd"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Forbundet til telefonlyd"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Forbundet til filoverførselsserver"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Tillad, at alle aktiviteter kan tilpasses flere vinduer uafhængigt af manifestværdier"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Aktivér vinduer i frit format"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Aktivér understøttelse af eksperimentelle vinduer i frit format"</string> + <string name="desktop_mode" msgid="2389067840550544462">"Computertilstand"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Kode til lokal backup"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Lokale komplette backups er i øjeblikket ikke beskyttet"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Tryk for at skifte eller fjerne adgangskoden til fuld lokal backup"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Fuldt opladet om <xliff:g id="TIME">%1$s</xliff:g>"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – fuldt opladet om <xliff:g id="TIME">%2$s</xliff:g>"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> – Opladningen er midlertidigt begrænset"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Ukendt"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Oplader"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Oplader hurtigt"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Oplader langsomt"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Trådløs opladning"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Dockingstation"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Oplader ikke"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Tilsluttet, oplader ikke"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Opladet"</string> @@ -610,7 +613,7 @@ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Gem"</string> <string name="guest_exit_button" msgid="5774985819191803960">"Afslut gæstetilstand"</string> <string name="guest_reset_button" msgid="2515069346223503479">"Nulstil gæstesession"</string> - <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Afslut gæstesession"</string> + <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Afslut gæst"</string> <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Al aktivitet slettes ved afslutning"</string> <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Du kan gemme eller slette din aktivitet ved afslutning"</string> <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Nulstil for at slette sessionsaktiviteten nu, eller gem eller slet aktivitet ved afslutning"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Luftkvalitet"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Cast-oplysninger"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Styring af hjem"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Overblik"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"Vælg et profilbillede"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"Ikon for standardbruger"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"Fysisk tastatur"</string> diff --git a/packages/SettingsLib/res/values-de/arrays.xml b/packages/SettingsLib/res/values-de/arrays.xml index 31126a803f8f..ca999db843b5 100644 --- a/packages/SettingsLib/res/values-de/arrays.xml +++ b/packages/SettingsLib/res/values-de/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"Systemauswahl verwenden (Standard)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-Audio"</item> + <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-Audio"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"Systemauswahl verwenden (Standard)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-Audio"</item> + <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-Audio"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"Systemauswahl verwenden (Standard)"</item> <item msgid="8003118270854840095">"44,1 kHz"</item> diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml index 3bd4d46b6cef..b75e4df9c762 100644 --- a/packages/SettingsLib/res/values-de/strings.xml +++ b/packages/SettingsLib/res/values-de/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-Audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD-Audio"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Hörhilfen"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE Audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Mit Hörhilfen verbunden"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Verbinden mit LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Mit LE Audio verbunden"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Verbunden mit Medien-Audio"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Verbunden mit Telefon-Audio"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Mit Dateiübertragungsserver verbunden"</string> @@ -408,6 +408,8 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Die Größe aller Aktivitäten darf, ungeachtet der Manifestwerte, für die Mehrfensterdarstellung angepasst werden"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Freiform-Fenster zulassen"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Unterstützung für experimentelle Freiform-Fenster aktivieren"</string> + <!-- no translation found for desktop_mode (2389067840550544462) --> + <skip /> <string name="local_backup_password_title" msgid="4631017948933578709">"Passwort für Desktop-Sicherung"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Vollständige Desktop-Sicherungen sind momentan nicht passwortgeschützt"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Zum Ändern oder Entfernen des Passworts für vollständige Desktop-Sicherungen tippen"</string> @@ -476,13 +478,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Voll in <xliff:g id="TIME">%1$s</xliff:g>"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – voll in <xliff:g id="TIME">%2$s</xliff:g>"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> – Aufladen vorübergehend eingeschränkt"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Unbekannt"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Wird aufgeladen"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Schnelles Aufladen"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Langsames Aufladen"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Kabelloses Laden"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Wird am Dock geladen"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Wird nicht geladen"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Verbunden, wird nicht geladen"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Aufgeladen"</string> @@ -661,8 +665,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Luftqualität"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Streaming-Info"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Smart-Home-Steuerung"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"Profilbild auswählen"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"Standardmäßiges Nutzersymbol"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"Physische Tastatur"</string> diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml index 2979e78ca5c1..cbc2724a59c6 100644 --- a/packages/SettingsLib/res/values-el/strings.xml +++ b/packages/SettingsLib/res/values-el/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Ήχος HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Ήχος HD"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Βοηθήματα ακοής"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE Audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Έγινε σύνδεση σε βοηθήματα ακοής"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Συνδέθηκε σε LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Συνδέθηκε σε LE Audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Συνδέθηκε σε ήχο πολυμέσων"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Συνδεδεμένο στον ήχο τηλεφώνου"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Συνδεδεμένο σε διακομιστή μεταφοράς αρχείων"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Να έχουν όλες οι δραστηριότητες δυνατότητα αλλαγής μεγέθους για την προβολή πολλαπλών παραθύρων, ανεξάρτητα από τις τιμές του μανιφέστου."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Ενεργοποίηση παραθύρων ελεύθερης μορφής"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Ενεργοποίηση υποστήριξης για πειραματικά παράθυρα ελεύθερης μορφής."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Λειτ. επιφάνειας εργασίας"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Εφ/κός κωδικός desktop"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Τα πλήρη αντίγραφα ασφαλείας επιφάνειας εργασίας δεν προστατεύονται αυτήν τη στιγμή"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Πατήστε για αλλαγή ή κατάργηση του κωδικού πρόσβασης για τα πλήρη αντίγραφα ασφαλείας επιφάνειας εργασίας"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Απομένουν <xliff:g id="TIME">%1$s</xliff:g> για πλήρη φόρτιση"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - Απομένουν <xliff:g id="TIME">%2$s</xliff:g> για πλήρη φόρτιση"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - Προσωρινός περιορισμός φόρτισης"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Άγνωστο"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Φόρτιση"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Ταχεία φόρτιση"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Αργή φόρτιση"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Ασύρματη φόρτιση"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Βάση φόρτισης"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Δεν φορτίζει"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Συνδεδεμένη, δεν φορτίζει"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Φορτισμένη"</string> @@ -586,9 +589,9 @@ <string name="user_set_lock_button" msgid="1427128184982594856">"Ορισμός κλειδώματος"</string> <string name="user_switch_to_user" msgid="6975428297154968543">"Εναλλαγή σε <xliff:g id="USER_NAME">%s</xliff:g>"</string> <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Δημιουργία νέου χρήστη…"</string> - <string name="creating_new_guest_dialog_message" msgid="1114905602181350690">"Δημιουργία νέου προσκεκλημένου…"</string> + <string name="creating_new_guest_dialog_message" msgid="1114905602181350690">"Δημιουργία νέου επισκέπτη…"</string> <string name="add_user_failed" msgid="4809887794313944872">"Η δημιουργία νέου χρήστη απέτυχε"</string> - <string name="add_guest_failed" msgid="8074548434469843443">"Αποτυχία δημιουργίας νέου προσκεκλημένου"</string> + <string name="add_guest_failed" msgid="8074548434469843443">"Αποτυχία δημιουργίας νέου επισκέπτη"</string> <string name="user_nickname" msgid="262624187455825083">"Ψευδώνυμο"</string> <string name="user_add_user" msgid="7876449291500212468">"Προσθήκη χρήστη"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Προσθήκη επισκέπτη"</string> diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml index 426a6d215380..c787941cab1f 100644 --- a/packages/SettingsLib/res/values-en-rAU/strings.xml +++ b/packages/SettingsLib/res/values-en-rAU/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Hearing Aids"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Connected to Hearing Aids"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Connected to LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Connected to LE audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Connected to media audio"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Connected to phone audio"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Connected to file-transfer server"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Make all activities resizeable for multi-window, regardless of manifest values."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Enable freeform windows"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Enable support for experimental freeform windows."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Desktop mode"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Desktop backup password"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Desktop full backups aren\'t currently protected"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Tap to change or remove the password for desktop full backups"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> left until full"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> left until full"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging temporarily limited"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Charging rapidly"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Charging slowly"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Charging wirelessly"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Charging dock"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Connected, not charging"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Charged"</string> diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml index dc3691b713f4..dc794082a4fb 100644 --- a/packages/SettingsLib/res/values-en-rCA/strings.xml +++ b/packages/SettingsLib/res/values-en-rCA/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Hearing Aids"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Connected to Hearing Aids"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Connected to LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Connected to LE audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Connected to media audio"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Connected to phone audio"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Connected to file-transfer server"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Make all activities resizeable for multi-window, regardless of manifest values."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Enable freeform windows"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Enable support for experimental freeform windows."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Desktop mode"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Desktop backup password"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Desktop full backups aren\'t currently protected"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Tap to change or remove the password for desktop full backups"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> left until full"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> left until full"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging temporarily limited"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Charging rapidly"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Charging slowly"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Charging wirelessly"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Charging dock"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Connected, not charging"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Charged"</string> diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml index 426a6d215380..c787941cab1f 100644 --- a/packages/SettingsLib/res/values-en-rGB/strings.xml +++ b/packages/SettingsLib/res/values-en-rGB/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Hearing Aids"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Connected to Hearing Aids"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Connected to LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Connected to LE audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Connected to media audio"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Connected to phone audio"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Connected to file-transfer server"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Make all activities resizeable for multi-window, regardless of manifest values."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Enable freeform windows"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Enable support for experimental freeform windows."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Desktop mode"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Desktop backup password"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Desktop full backups aren\'t currently protected"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Tap to change or remove the password for desktop full backups"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> left until full"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> left until full"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging temporarily limited"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Charging rapidly"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Charging slowly"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Charging wirelessly"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Charging dock"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Connected, not charging"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Charged"</string> diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml index 426a6d215380..c787941cab1f 100644 --- a/packages/SettingsLib/res/values-en-rIN/strings.xml +++ b/packages/SettingsLib/res/values-en-rIN/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Hearing Aids"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Connected to Hearing Aids"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Connected to LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Connected to LE audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Connected to media audio"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Connected to phone audio"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Connected to file-transfer server"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Make all activities resizeable for multi-window, regardless of manifest values."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Enable freeform windows"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Enable support for experimental freeform windows."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Desktop mode"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Desktop backup password"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Desktop full backups aren\'t currently protected"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Tap to change or remove the password for desktop full backups"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> left until full"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> left until full"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging temporarily limited"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Charging rapidly"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Charging slowly"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Charging wirelessly"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Charging dock"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Connected, not charging"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Charged"</string> diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml index 8360f0152bfe..175ac5d7b980 100644 --- a/packages/SettingsLib/res/values-en-rXC/strings.xml +++ b/packages/SettingsLib/res/values-en-rXC/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Hearing Aids"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Connected to Hearing Aids"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Connected to LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Connected to LE audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Connected to media audio"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Connected to phone audio"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Connected to file transfer server"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Make all activities resizable for multi-window, regardless of manifest values."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Enable freeform windows"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Enable support for experimental freeform windows."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Desktop mode"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Desktop backup password"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Desktop full backups aren’t currently protected"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Tap to change or remove the password for desktop full backups"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> left until full"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> left until full"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging temporarily limited"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Charging rapidly"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Charging slowly"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Charging wirelessly"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Charging Dock"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Connected, not charging"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Charged"</string> diff --git a/packages/SettingsLib/res/values-es-rUS/arrays.xml b/packages/SettingsLib/res/values-es-rUS/arrays.xml index 9b1aa3a274fc..381380800e97 100644 --- a/packages/SettingsLib/res/values-es-rUS/arrays.xml +++ b/packages/SettingsLib/res/values-es-rUS/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"Usar selección del sistema (predeterminado)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="2908219194098827570">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"Usar selección del sistema (predeterminado)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="3517061573669307965">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"Usar selección del sistema (predeterminado)"</item> <item msgid="8003118270854840095">"44.1 kHz"</item> @@ -212,7 +228,7 @@ <item msgid="7051983425968643928">"720 píxeles (seguro)"</item> <item msgid="7765795608738980305">"1080 píxeles"</item> <item msgid="8084293856795803592">"1080 píxeles (seguro)"</item> - <item msgid="938784192903353277">"4 K"</item> + <item msgid="938784192903353277">"4K"</item> <item msgid="8612549335720461635">"4 K (seguro)"</item> <item msgid="7322156123728520872">"4 K (mejorado)"</item> <item msgid="7735692090314849188">"4 K (mejorado, seguro)"</item> diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml index d0f2c641982f..cf154664753f 100644 --- a/packages/SettingsLib/res/values-es-rUS/strings.xml +++ b/packages/SettingsLib/res/values-es-rUS/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Audio en HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Audio en HD"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Audífonos"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"Audio de bajo consumo"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Conectado a audífonos"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Conectado a LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Conectado a audio de bajo consumo"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Conectado al audio multimedia"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Conectado al audio del dispositivo"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Conectado al servidor de transferencia de archivo"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Permitir que todas las actividades puedan cambiar de tamaño para el modo multiventana, sin importar los valores del manifiesto."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Habilitar ventanas de forma libre"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Permitir la compatibilidad con ventanas de forma libre experimentales"</string> + <string name="desktop_mode" msgid="2389067840550544462">"Modo de escritorio"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Contraseñas"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Tus copias de seguridad de escritorio no están protegidas por contraseña."</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Presiona para cambiar o quitar la contraseña de las copias de seguridad completas de tu escritorio."</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> para completar"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> para completar"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - Carga limitada temporalmente"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconocido"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Cargando rápidamente"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Carga lenta"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Carga inalámbrica"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Base de carga"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"No se está cargando."</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Conectado; no se está cargando"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Cargada"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Calidad del aire"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info de reparto"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Control de la casa"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"Elige una foto de perfil"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"Ícono de usuario predeterminado"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"Teclado físico"</string> diff --git a/packages/SettingsLib/res/values-es/arrays.xml b/packages/SettingsLib/res/values-es/arrays.xml index 0677864d1f46..49244078105e 100644 --- a/packages/SettingsLib/res/values-es/arrays.xml +++ b/packages/SettingsLib/res/values-es/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"Usar preferencia del sistema (predeterminado)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="2908219194098827570">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"Usar preferencia del sistema (predeterminado)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="3517061573669307965">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"Usar preferencia del sistema (predeterminado)"</item> <item msgid="8003118270854840095">"44,1 kHz"</item> diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml index e75c13c4aecd..bb2f59b622fb 100644 --- a/packages/SettingsLib/res/values-es/strings.xml +++ b/packages/SettingsLib/res/values-es/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Audio HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Audio HD"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Audífonos"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"Le Audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Conectado a audífonos"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Conectado a LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Conectado a LE Audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Conectado al audio del medio"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Conectado al audio del teléfono"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Conectado con el servidor de transferencia de archivos"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Permite que todas las actividades puedan cambiar de tamaño en multiventana independientemente de los valores de manifiesto"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Habilitar ventanas de forma libre"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Permite la compatibilidad con ventanas de forma libre experimentales"</string> + <string name="desktop_mode" msgid="2389067840550544462">"Modo Escritorio"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Contraseña para copias de ordenador"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Las copias de seguridad completas de ordenador no están protegidas"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Toca para cambiar o quitar la contraseña de las copias de seguridad completas del escritorio"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> hasta la carga completa"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> hasta la carga completa"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - Carga limitada temporalmente"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconocido"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Carga rápida"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Carga lenta"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Carga inalámbrica"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Base de carga"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"No se está cargando"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Conectado pero sin cargar"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Cargada"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Calidad del aire"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info. de emisión"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Domótica"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"De un vistazo"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"Elige una imagen de perfil"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"Icono de usuario predeterminado"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"Teclado físico"</string> diff --git a/packages/SettingsLib/res/values-et/arrays.xml b/packages/SettingsLib/res/values-et/arrays.xml index d986ecf47260..0402ac2f5719 100644 --- a/packages/SettingsLib/res/values-et/arrays.xml +++ b/packages/SettingsLib/res/values-et/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"Süsteemi valiku kasutamine (vaikeseade)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"Heli: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="2908219194098827570">"Heli: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"Süsteemi valiku kasutamine (vaikeseade)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"Heli: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="3517061573669307965">"Heli: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"Süsteemi valiku kasutamine (vaikeseade)"</item> <item msgid="8003118270854840095">"44,1 kHz"</item> diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml index 022063cfe20d..bcd6ad1a3ea6 100644 --- a/packages/SettingsLib/res/values-et/strings.xml +++ b/packages/SettingsLib/res/values-et/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-heli: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD-heli"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Kuuldeaparaadid"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE Audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Kuuldeaparaatidega ühendatud"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Ühendatud üksusega LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Ühendatud üksusega LE Audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Ühendatud meediumiheliga"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Ühendatud telefoniheliga"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Ühendatud failiedastuse serveriga"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Muudetakse kõigi tegevuste suurused mitme aknaga vaates muudetavaks (manifesti väärtustest olenemata)."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Luba vabas vormis aknad"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Lubatakse katseliste vabavormis akende tugi."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Lauaarvuti režiim"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Arvutivarunduse parool"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Täielikud arvutivarundused pole praegu kaitstud"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Puudutage täielike arvutivarunduste parooli muutmiseks või eemaldamiseks"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Täislaadimiseks kulub <xliff:g id="TIME">%1$s</xliff:g>"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – täislaadimiseks kulub <xliff:g id="TIME">%2$s</xliff:g>"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> – laadimine on ajutiselt piiratud"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Tundmatu"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Laadimine"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Kiirlaadimine"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Aeglaselt laadimine"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Juhtmevaba laadimine"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Laadimisdokk"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ei lae"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Ühendatud, ei laeta"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Laetud"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Õhukvaliteet"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Osatäitjate teave"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Kodu juhtimine"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Ülevaade"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"Valige profiilipilt"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"Vaikekasutajaikoon"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"Füüsiline klaviatuur"</string> diff --git a/packages/SettingsLib/res/values-eu/arrays.xml b/packages/SettingsLib/res/values-eu/arrays.xml index d166e1b97a38..9c12e95cf5c8 100644 --- a/packages/SettingsLib/res/values-eu/arrays.xml +++ b/packages/SettingsLib/res/values-eu/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"Erabili sistema-hautapena (lehenetsia)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audioa"</item> + <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audioa"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"Erabili sistema-hautapena (lehenetsia)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audioa"</item> + <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audioa"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"Erabili sistema-hautapena (lehenetsia)"</item> <item msgid="8003118270854840095">"44,1 kHz"</item> diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml index ba8b1ad1fe3d..b1ac23618444 100644 --- a/packages/SettingsLib/res/values-eu/strings.xml +++ b/packages/SettingsLib/res/values-eu/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Kalitate handiko audioa: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Kalitate handiko audioa"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Audifonoak"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Audifonoetara konektatuta"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Konektatu LE_AUDIO-ra"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE audio-ra konektatuta"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Euskarriaren audiora konektatuta"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Telefonoaren audiora konektatuta"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Fitxategi-transferentziako zerbitzarira konektatuta"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Eman aukera jarduera guztien tamaina doitzeko, hainbat leihotan erabili ahal izan daitezen, ezarritako balioak kontuan izan gabe"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Gaitu estilo libreko leihoak"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Onartu estilo libreko leiho esperimentalak"</string> + <string name="desktop_mode" msgid="2389067840550544462">"Ordenagailuetarako modua"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Babeskopien pasahitz lokala"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Une honetan, ordenagailuko babeskopia osoak ez daude babestuta"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Ordenagailuko eduki guztiaren babeskopia egiteko erabiltzen den pasahitza aldatzeko edo kentzeko, sakatu hau"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> guztiz kargatu arte"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> guztiz kargatu arte"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - Kargatzeko aukera mugatuta dago aldi baterako"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Ezezaguna"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Kargatzen"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Bizkor kargatzen"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Mantso kargatzen"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Hari gabe kargatzen"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Oinarrian kargatzen"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ez da kargatzen ari"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Konektatuta dago, baina ez da kargatzen ari"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Kargatuta"</string> @@ -660,10 +663,8 @@ <string name="dream_complication_title_weather" msgid="598609151677172783">"Eguraldia"</string> <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Airearen kalitatea"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Igorpenari buruzko informazioa"</string> - <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) --> - <skip /> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Etxeko gailuak kontrolatzeko aukerak"</string> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"Aukeratu profileko argazki bat"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"Erabiltzaile lehenetsiaren ikonoa"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"Teklatu fisikoa"</string> diff --git a/packages/SettingsLib/res/values-fa/arrays.xml b/packages/SettingsLib/res/values-fa/arrays.xml index b7761dd6ebcb..41410cbc3288 100644 --- a/packages/SettingsLib/res/values-fa/arrays.xml +++ b/packages/SettingsLib/res/values-fa/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"استفاده از انتخاب سیستم (پیشفرض)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"صوت <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="2908219194098827570">"صوت <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"استفاده از انتخاب سیستم (پیشفرض)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"صوت <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="3517061573669307965">"صوت <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"استفاده از انتخاب سیستم (پیشفرض)"</item> <item msgid="8003118270854840095">"۴۴٫۱ کیلوهرتز"</item> diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml index 911426cecd7d..ffd80ec67a0a 100644 --- a/packages/SettingsLib/res/values-fa/strings.xml +++ b/packages/SettingsLib/res/values-fa/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"صدای HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"صدای HD"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"سمعک"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"صدای کممصرف"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"به سمعک متصل شد"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"متصل به LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"به «صدای کممصرف» وصل است"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"به رسانه صوتی متصل شد"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"به تلفن صوتی متصل شد"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"به سرور انتقال فایل متصل شد"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"بدون توجه به مقادیر مانیفست، اندازه همه فعالیتها برای حالت چند پنجرهای میتواند تغییر کند."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"فعال کردن پنجرههای آزاد"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"فعال کردن پشتیبانی برای پنجرههای آزاد آزمایشی."</string> + <string name="desktop_mode" msgid="2389067840550544462">"حالت رایانه"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"گذرواژه پشتیبانگیری محلی"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"پشتیبانگیری کامل رایانه درحال حاضر محافظت نمیشود"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"برای تغییر یا حذف گذرواژه برای نسخههای پشتیبان کامل رایانهای ضربه بزنید"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> تا شارژ کامل باقی مانده است"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> تا شارژ کامل باقی مانده است"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - شارژ موقتاً محدود شده است"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"ناشناس"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"در حال شارژ شدن"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"درحال شارژ شدن سریع"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"درحال شارژ شدن آهسته"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"درحال شارژ بیسیم"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"پایه شارژ"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"شارژ نمیشود"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"متصل، شارژ نمیشود"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"شارژ کامل شد"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"کیفیت هوا"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"اطلاعات پخش محتوا"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"کنترل لوازم خانگی"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"انتخاب عکس نمایه"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"نماد کاربر پیشفرض"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"صفحهکلید فیزیکی"</string> diff --git a/packages/SettingsLib/res/values-fi/arrays.xml b/packages/SettingsLib/res/values-fi/arrays.xml index 296989299cfe..842fb8fed9a7 100644 --- a/packages/SettingsLib/res/values-fi/arrays.xml +++ b/packages/SettingsLib/res/values-fi/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"Käytä järjestelmän valintaa (oletus)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ‑ääni"</item> + <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ‑ääni"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"Käytä järjestelmän valintaa (oletus)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ‑ääni"</item> + <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ‑ääni"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"Käytä järjestelmän valintaa (oletus)"</item> <item msgid="8003118270854840095">"44,1 kHz"</item> diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml index 670bcae60bc6..3d12c02c0c81 100644 --- a/packages/SettingsLib/res/values-fi/strings.xml +++ b/packages/SettingsLib/res/values-fi/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-ääni: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD-ääni"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Kuulolaitteet"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE Audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Yhdistetty kuulolaitteisiin"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"LE_AUDIO yhdistetty"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE Audio yhdistetty"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Yhdistetty median ääneen"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Yhdistetty puhelimen ääneen"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Yhdistetty tiedostonsiirtopalvelimeen"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Pakota kaikki toiminnot hyväksymään koon muuttaminen usean ikkunan tilassa luettelon arvoista riippumatta"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Ota käyttöön vapaamuotoiset ikkunat"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Ota kokeellisten vapaamuotoisten ikkunoiden tuki käyttöön"</string> + <string name="desktop_mode" msgid="2389067840550544462">"Työpöytätila"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Varmuuskop. salasana"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Tietokoneen kaikkien tietojen varmuuskopiointia ei ole tällä hetkellä suojattu"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Vaihda tai poista tietokoneen kaikkien tietojen varmuuskopioinnin salasana koskettamalla."</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> kunnes täynnä"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> kunnes täynnä"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> – Lataamista rajoitettu väliaikaisesti"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Tuntematon"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Ladataan"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Nopea lataus"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Hidas lataus"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Langaton lataus"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Latausteline"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ei laturissa"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Yhdistetty, ei ladata"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Ladattu"</string> @@ -575,7 +578,7 @@ <string name="user_setup_dialog_title" msgid="8037342066381939995">"Lisätäänkö käyttäjä nyt?"</string> <string name="user_setup_dialog_message" msgid="269931619868102841">"Varmista, että käyttäjä voi ottaa laitteen nyt ja määrittää oman tilansa."</string> <string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"Määritetäänkö profiilin asetukset nyt?"</string> - <string name="user_setup_button_setup_now" msgid="1708269547187760639">"Määritä nyt"</string> + <string name="user_setup_button_setup_now" msgid="1708269547187760639">"Ota käyttöön nyt"</string> <string name="user_setup_button_setup_later" msgid="8712980133555493516">"Ei nyt"</string> <string name="user_add_user_type_title" msgid="551279664052914497">"Lisää"</string> <string name="user_new_user_name" msgid="60979820612818840">"Uusi käyttäjä"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Ilmanlaatu"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Striimaustiedot"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Kodin ohjaus"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Pikanäkymä"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"Valitse profiilikuva"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"Oletuskäyttäjäkuvake"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"Fyysinen näppäimistö"</string> diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml index 5d6aaeb0a513..7dfc86ed801d 100644 --- a/packages/SettingsLib/res/values-fr-rCA/strings.xml +++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Audio HD : <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Audio HD"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Prothèses auditives"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE Audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Connecté aux prothèses auditives"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Connecté à LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Connecté par LE Audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Connecté aux paramètres audio du média"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Connecté à l\'audio du téléphone"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Connexion au serveur de transfert de fichiers"</string> @@ -408,6 +408,8 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Permet de redimensionner toutes les activités pour le mode multi-fenêtre, indépendamment des valeurs du fichier manifeste."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Activer les fenêtres de forme libre"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Activer la compatibilité avec les fenêtres de forme libre expérimentales."</string> + <!-- no translation found for desktop_mode (2389067840550544462) --> + <skip /> <string name="local_backup_password_title" msgid="4631017948933578709">"Mot de passe sauvegarde PC"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Les sauvegardes complètes sur PC ne sont pas protégées actuellement"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Touchez pour modifier ou supprimer le mot de passe utilisé pour les sauvegardes complètes sur ordinateur."</string> @@ -476,13 +478,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> jusqu\'à la recharge complète"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> (<xliff:g id="TIME">%2$s</xliff:g> jusqu\'à la recharge complète)"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> : recharge temporairement limitée"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Inconnu"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Charge en cours…"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Recharge rapide"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Recharge lente"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"En recharge sans fil"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Station de recharge"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"N\'est pas en charge"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Connecté, pas en charge"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Chargée"</string> diff --git a/packages/SettingsLib/res/values-fr/arrays.xml b/packages/SettingsLib/res/values-fr/arrays.xml index 80ac7e4e4752..92546da3f56d 100644 --- a/packages/SettingsLib/res/values-fr/arrays.xml +++ b/packages/SettingsLib/res/values-fr/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"Utiliser la sélection du système (par défaut)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="2908219194098827570">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"Utiliser la sélection du système (par défaut)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="3517061573669307965">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"Utiliser la sélection du système (par défaut)"</item> <item msgid="8003118270854840095">"44,1 kHz"</item> diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml index dad8668c9782..52d15f1af116 100644 --- a/packages/SettingsLib/res/values-fr/strings.xml +++ b/packages/SettingsLib/res/values-fr/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Audio HD : <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Audio HD"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Appareils auditifs"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE Audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Connexion établie avec les appareils auditifs"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Connecté à LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Connecté à LE Audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Connecté aux paramètres audio du média"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Connecté aux paramètres audio du téléphone"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Connexion au serveur de transfert de fichiers"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Rendre toutes les activités redimensionnables pour le mode multifenêtre, indépendamment des valeurs du fichier manifeste"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Activer les fenêtres de forme libre"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Activer la compatibilité avec les fenêtres de forme libre expérimentales"</string> + <string name="desktop_mode" msgid="2389067840550544462">"Mode ordinateur"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Mot de passe de sauvegarde ordi"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Les sauvegardes complètes sur ordi ne sont actuellement pas protégées"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Appuyez pour modifier ou supprimer le mot de passe des sauvegardes complètes sur ordi."</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Chargée à 100 %% dans <xliff:g id="TIME">%1$s</xliff:g>"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - chargée à 100 %% dans <xliff:g id="TIME">%2$s</xliff:g>"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - Recharge momentanément limitée"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Inconnu"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Batterie en charge"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Charge rapide"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Charge lente"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"En charge sans fil"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Station de charge"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Pas en charge"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Connectée, pas en charge"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Chargée"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Qualité de l\'air"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Infos distribution"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Domotique"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"Choisissez une photo de profil"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"Icône de l\'utilisateur par défaut"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"Clavier physique"</string> diff --git a/packages/SettingsLib/res/values-gl/arrays.xml b/packages/SettingsLib/res/values-gl/arrays.xml index b6cf48e54f72..f66312084e4b 100644 --- a/packages/SettingsLib/res/values-gl/arrays.xml +++ b/packages/SettingsLib/res/values-gl/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"Usar selección do sistema (predeterminado)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="2908219194098827570">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"Usa a selección do sistema (predeterminado)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="3517061573669307965">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"Usar selección do sistema (predeterminado)"</item> <item msgid="8003118270854840095">"44,1 kHz"</item> diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml index 3fd7f439fae6..ef73b59078c8 100644 --- a/packages/SettingsLib/res/values-gl/strings.xml +++ b/packages/SettingsLib/res/values-gl/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Audio en HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Audio en HD"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Audiófonos"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"Audio de baixo consumo"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Conectado a audiófonos"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Conexión establecida con LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Estableceuse conexión co audio de baixo consumo"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Conectado ao audio multimedia"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Conectado ao audio do teléfono"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Conectado ao servidor de transferencia de ficheiros"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Permite axustar o tamaño de todas as actividades para o modo multiventá, independentemente dos valores do manifesto"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Activar ventás de forma libre"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Activa a compatibilidade con ventás de forma libre experimentais"</string> + <string name="desktop_mode" msgid="2389067840550544462">"Modo de escritorio"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Contrasinal para copias"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"As copias de seguranza de ordenador completas non están protexidas"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Toca para cambiar ou quitar o contrasinal para as copias de seguranza completas de ordenador"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> para completar a carga"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> (<xliff:g id="TIME">%2$s</xliff:g> para completar a carga)"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - Carga limitada temporalmente"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Descoñecido"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Cargando rapidamente"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Cargando lentamente"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Cargando sen fíos"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Base de carga"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Non se está cargando"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Conectado, sen cargar"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Cargada"</string> @@ -660,10 +663,8 @@ <string name="dream_complication_title_weather" msgid="598609151677172783">"O tempo"</string> <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Calidade do aire"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Datos da emisión"</string> - <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) --> - <skip /> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Controis domóticos"</string> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Espazo intelixente"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"Escolle unha imaxe do perfil"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"Icona do usuario predeterminado"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"Teclado físico"</string> diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml index eb84599798ec..02c99277b77e 100644 --- a/packages/SettingsLib/res/values-gu/strings.xml +++ b/packages/SettingsLib/res/values-gu/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ઑડિયો: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ઑડિયો"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"શ્રવણ યંત્રો"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE ઑડિયો"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"શ્રવણ યંત્રો સાથે કનેક્ટ કરેલું છે"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"LE_AUDIO સાથે કનેક્ટેડ છે"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE ઑડિયોથી કનેક્ટેડ"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"મીડિયા ઑડિઓ સાથે કનેક્ટ કર્યુ"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ફોન ઑડિઓ સાથે કનેક્ટ થયાં"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"ફાઇલ સ્થાનાંતરણ સેવાથી કનેક્ટ થયાં"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"મૅનિફેસ્ટ મૂલ્યોને ધ્યાનમાં લીધા સિવાય, તમામ પ્રવૃત્તિઓને મલ્ટી-વિન્ડો માટે ફરીથી કદ બદલી શકે તેવી બનાવો."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"ફ્રીફોર્મ વિન્ડો ચાલુ કરો"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"પ્રાયોગિક ફ્રીફોર્મ વિન્ડો માટે સપોર્ટને ચાલુ કરો."</string> + <string name="desktop_mode" msgid="2389067840550544462">"ડેસ્કટૉપ મોડ"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"ડેસ્કટૉપ બૅકઅપ પાસવર્ડ"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"ડેસ્કટૉપ સંપૂર્ણ બૅકઅપ હાલમાં સુરક્ષિત નથી"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"ડેસ્કટૉપ સંપૂર્ણ બેકઅપ્સ માટેનો પાસવર્ડ બદલવા અથવા દૂર કરવા માટે ટૅચ કરો"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"પૂર્ણ ચાર્જ થવામાં <xliff:g id="TIME">%1$s</xliff:g> બાકી છે"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - પૂર્ણ ચાર્જ થવામાં <xliff:g id="TIME">%2$s</xliff:g> બાકી છે"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - ચાર્જ કરવાનું થોડા સમય માટે મર્યાદિત કરવામાં આવ્યું છે"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"અજાણ્યું"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ચાર્જ થઈ રહ્યું છે"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ઝડપથી ચાર્જ થાય છે"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"ધીમેથી ચાર્જ થાય છે"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"વાયરલેસથી ચાર્જિંગ"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"ડૉકથી ચાર્જ થઈ રહ્યું છે"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ચાર્જ થઈ રહ્યું નથી"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"કનેક્ટ કરેલું છે, પણ ચાર્જ થઈ રહ્યું નથી"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"ચાર્જ થયું"</string> diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml index e52975627aac..6dbb2b47dd88 100644 --- a/packages/SettingsLib/res/values-hi/strings.xml +++ b/packages/SettingsLib/res/values-hi/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"एचडी ऑडियो: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"एचडी ऑडियो"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"कान की मशीन"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE Audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"सुनने में मदद करने वाले डिवाइस से कनेक्ट है"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"LE_AUDIO से कनेक्ट किया गया"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE Audio से कनेक्ट किया गया"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"मीडिया ऑडियो से कनेक्ट किया गया"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"फ़ोन ऑडियो से कनेक्ट किया गया"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"फ़ाइल स्थानांतरण सर्वर से कनेक्ट किया गया"</string> @@ -408,6 +408,8 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"सभी गतिविधियों को मल्टी-विंडो (एक से ज़्यादा ऐप्लिकेशन, एक साथ) के लिए साइज़ बदलने लायक बनाएं, चाहे उनकी मेनिफ़ेस्ट वैल्यू कुछ भी हो."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"फ़्रीफ़ॉर्म विंडो (एक साथ कई विंडो दिखाना) चालू करें"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"जांच के लिए बनी फ़्रीफ़ॉर्म विंडो के लिए सहायता चालू करें."</string> + <!-- no translation found for desktop_mode (2389067840550544462) --> + <skip /> <string name="local_backup_password_title" msgid="4631017948933578709">"डेस्कटॉप बैक अप पासवर्ड"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"डेस्कटॉप के पूरे बैक अप फ़िलहाल सुरक्षित नहीं हैं"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"डेस्कटॉप के पूरे बैक अप का पासवर्ड बदलने या हटाने के लिए टैप करें"</string> @@ -476,13 +478,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> में बैटरी पूरी चार्ज हो जाएगी"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> में बैटरी पूरी चार्ज हो जाएगी"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्जिंग कुछ समय के लिए रोकी गई"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"अज्ञात"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"चार्ज हो रही है"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"तेज़ चार्ज हो रही है"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"धीरे चार्ज हो रही है"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"वायरलेस चार्जिंग"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"चार्जिंग डॉक"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"चार्ज नहीं हो रही है"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"कनेक्ट किया गया, चार्ज नहीं हो रहा है"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"बैटरी चार्ज हो गई"</string> @@ -602,7 +606,7 @@ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"क्या मेहमान मोड के मौजूदा सेशन को रीसेट करना है?"</string> <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"ऐसा करने पर, मेहमान के तौर पर ब्राउज़ करने का एक नया सेशन शुरू हो जाएगा. साथ ही, पिछले सेशन में मौजूद डेटा और इस्तेमाल किए जा रहे ऐप्लिकेशन को मिटा दिया जाएगा"</string> <string name="guest_exit_dialog_title" msgid="1846494656849381804">"मेहमान मोड से बाहर निकलना है?"</string> - <string name="guest_exit_dialog_message" msgid="1743218864242719783">"इससे, मेहमान मोड के मौजूदा सेशन का डेटा और इसमें इस्तेमाल हो रहे ऐप मिट जाएंगे"</string> + <string name="guest_exit_dialog_message" msgid="1743218864242719783">"इससे, मेहमान मोड के मौजूदा सेशन का डेटा और इसमें इस्तेमाल हो रहे ऐप्लिकेशन मिट जाएंगे"</string> <string name="guest_exit_dialog_button" msgid="1736401897067442044">"बाहर निकलें"</string> <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"मेहमान मोड की गतिविधि को सेव करना है?"</string> <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"मौजूदा सेशन की गतिविधि को सेव किया जा सकता है या सभी ऐप और डेटा को मिटाया जा सकता है"</string> diff --git a/packages/SettingsLib/res/values-hr/arrays.xml b/packages/SettingsLib/res/values-hr/arrays.xml index 0e66858f2d3a..559383ad875f 100644 --- a/packages/SettingsLib/res/values-hr/arrays.xml +++ b/packages/SettingsLib/res/values-hr/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"Upotreba odabira sustava (zadano)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item> + <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"Upotreba odabira sustava (zadano)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item> + <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"Upotreba odabira sustava (zadano)"</item> <item msgid="8003118270854840095">"44,1 kHz"</item> diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml index 598f65f60583..e3f8ba8b8051 100644 --- a/packages/SettingsLib/res/values-hr/strings.xml +++ b/packages/SettingsLib/res/values-hr/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Slušni aparati"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE_AUDIO"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Povezano sa Slušnim aparatima"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Povezano s profilom LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Povezano s profilom LE_AUDIO"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Povezano s medijskim zvukom"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Povezano sa telefonskim zvukom"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Povezano s poslužiteljem za prijenos datoteka"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Omogući mijenjanje veličine svih aktivnosti za više prozora, neovisno o vrijednostima manifesta."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Omogući prozore slobodnog oblika"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Omogući podršku za eksperimentalne prozore slobodnog oblika."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Stolni način rada"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Zaporka sigurnosne kopije"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Potpune sigurnosne kopije na stolnom računalu trenutačno nisu zaštićene"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Dodirnite da biste promijenili ili uklonili zaporku za potpune sigurnosne kopije na računalu"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> do napunjenosti"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do napunjenosti"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> – punjenje je privremeno ograničeno"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Nepoznato"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Punjenje"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Brzo punjenje"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Sporo punjenje"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Bežično punjenje"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Stanica za punjenje"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ne puni se"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Povezano, ne puni se"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Napunjeno"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Kvaliteta zraka"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Inform. o emitiranju"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Upravlj. kuć. uređ."</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"Odabir profilne slike"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"Ikona zadanog korisnika"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"Fizička tipkovnica"</string> diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml index 8eae4d0cdde8..8ff60319e572 100644 --- a/packages/SettingsLib/res/values-hu/strings.xml +++ b/packages/SettingsLib/res/values-hu/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Hallókészülékek"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"Alacsony energiaszintű hangátvitel"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Hallókészülékhez csatlakoztatva"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Csatlakoztatva ehhez: LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Csatlakoztatva az alacsony energiaszintű hangátvitelhez"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Csatlakoztatva az eszköz hangjához"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Csatlakoztatva a telefon hangjához"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Csatlakozva a fájlküldő szerverhez"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Legyen az összes tevékenység átméretezhető a többablakos megjelenítés érdekében a jegyzékértékektől függetlenül."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Szabad formájú ablakok engedélyezése"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Kísérleti, szabad formájú ablakok támogatásának engedélyezése."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Asztali üzemmód"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Asztali mentés jelszava"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Az asztali teljes biztonsági mentések jelenleg nem védettek."</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Koppintson ide az asztali teljes mentések jelszavának módosításához vagy eltávolításához"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> a teljes töltöttségig"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> a teljes töltöttségig"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> – Töltés ideiglenesen korlátozva"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Ismeretlen"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Töltés"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Gyorstöltés"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Lassú töltés"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Vezeték nélküli töltés"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Töltődokk"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nem tölt"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Csatlakoztatva, nem töltődik"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Feltöltve"</string> diff --git a/packages/SettingsLib/res/values-hy/arrays.xml b/packages/SettingsLib/res/values-hy/arrays.xml index 50d7c7f59f65..009875d2ba5c 100644 --- a/packages/SettingsLib/res/values-hy/arrays.xml +++ b/packages/SettingsLib/res/values-hy/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"Օգտագործել համակարգի կարգավորումը (կանխադրված)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> աուդիո"</item> + <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> աուդիո"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"Օգտագործել համակարգի կարգավորումը (կանխադրված)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> աուդիո"</item> + <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> աուդիո"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"Օգտագործել համակարգի կարգավորումը (կանխադրված)"</item> <item msgid="8003118270854840095">"44,1 կՀց"</item> diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml index 2a5fbfb2a156..8a4586689bd6 100644 --- a/packages/SettingsLib/res/values-hy/strings.xml +++ b/packages/SettingsLib/res/values-hy/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD աուդիո՝ <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD աուդիո"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Լսողական ապարատ"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Լսողական ապարատը միացված է"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Միացած է LE_AUDIO-ին"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Միացած է LE audio-ին"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Միացված է մեդիա աուդիոյին"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Միացված է հեռախոսի ձայնային տվյալներին"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Միացված է ֆայլերի փոխանցման սերվերին"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Բոլոր ակտիվությունների չափերը բազմապատուհան ռեժիմի համար դարձնել փոփոխելի՝ մանիֆեստի արժեքներից անկախ:"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Ակտիվացնել կամայական ձևի պատուհանները"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Միացնել ազատ ձևի փորձնական պատուհանների աջակցումը:"</string> + <string name="desktop_mode" msgid="2389067840550544462">"Համակարգչի ռեժիմ"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Աշխատասեղանի պահուստավորման գաղտնաբառ"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Աշխատասեղանի ամբողջական պահուստավորումները այժմ պաշտպանված չեն"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Հպեք՝ աշխատասեղանի ամբողջական պահուստավորման գաղտնաբառը փոխելու կամ հեռացնելու համար"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> մինչև լրիվ լիցքավորումը"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> մինչև լրիվ լիցքավորումը"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> – Լիցքավորումը ժամանակավորապես սահմանափակված է"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Անհայտ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Լիցքավորում"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Արագ լիցքավորում"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Դանդաղ լիցքավորում"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Անլար լիցքավորում"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Լիցքավորում դոկ-կայանի միջոցով"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Չի լիցքավորվում"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Միացված է, չի լիցքավորվում"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Լիցքավորված է"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Օդի որակը"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Հեռարձակման տվյալներ"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Տան կարգավորումներ"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"Պրոֆիլի նկար ընտրեք"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"Օգտատիրոջ կանխադրված պատկերակ"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"Ֆիզիկական ստեղնաշար"</string> diff --git a/packages/SettingsLib/res/values-in/arrays.xml b/packages/SettingsLib/res/values-in/arrays.xml index 5b0ad98aa2d5..95274177e888 100644 --- a/packages/SettingsLib/res/values-in/arrays.xml +++ b/packages/SettingsLib/res/values-in/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"Gunakan Pilihan Sistem (Default)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="2908219194098827570">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"Gunakan Pilihan Sistem (Default)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="3517061573669307965">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"Gunakan Pilihan Sistem (Default)"</item> <item msgid="8003118270854840095">"44,1 kHz"</item> diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml index 08ec2b1e7805..6dc569711e23 100644 --- a/packages/SettingsLib/res/values-in/strings.xml +++ b/packages/SettingsLib/res/values-in/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Audio HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Audio HD"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Alat Bantu Dengar"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE Audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Terhubung ke Alat Bantu Dengar"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Terhubung ke LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Terhubung ke LE Audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Terhubung ke media audio"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Terhubung ke audio ponsel"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Sambungkan ke server transfer file"</string> @@ -232,7 +232,7 @@ <string name="vpn_settings_not_available" msgid="2894137119965668920">"Setelan VPN tidak tersedia untuk pengguna ini"</string> <string name="tethering_settings_not_available" msgid="266821736434699780">"Setelan Penambatan tidak tersedia untuk pengguna ini"</string> <string name="apn_settings_not_available" msgid="1147111671403342300">"Setelan Nama Titik Akses tidak tersedia untuk pengguna ini"</string> - <string name="enable_adb" msgid="8072776357237289039">"Debugging USB"</string> + <string name="enable_adb" msgid="8072776357237289039">"Proses debug USB"</string> <string name="enable_adb_summary" msgid="3711526030096574316">"Mode debug ketika USB terhubung"</string> <string name="clear_adb_keys" msgid="3010148733140369917">"Cabut otorisasi debug USB"</string> <string name="enable_adb_wireless" msgid="6973226350963971018">"Proses debug nirkabel"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Membuat semua aktivitas dapat diubah ukurannya untuk banyak jendela, terlepas dari nilai manifes."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Aktifkan jendela berformat bebas"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Mengaktifkan dukungan untuk jendela eksperimental berformat bebas."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Mode desktop"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Sandi cadangan desktop"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Saat ini cadangan desktop penuh tidak dilindungi"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Ketuk guna mengubah atau menghapus sandi untuk cadangan lengkap desktop"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> lagi sampai penuh"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> lagi sampai penuh"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - Pengisian daya dibatasi sementara"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Tidak diketahui"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Mengisi daya"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Mengisi daya cepat"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Mengisi daya lambat"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Mengisi daya nirkabel"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Mengisi Daya di Dok"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Tidak mengisi daya"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Terhubung, tidak mengisi daya"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Terisi"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Kualitas Udara"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info Transmisi"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Kontrol Rumah"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"Pilih foto profil"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"Ikon pengguna default"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"Keyboard fisik"</string> diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml index dc2036f0ac88..4531fb1ecefb 100644 --- a/packages/SettingsLib/res/values-is/strings.xml +++ b/packages/SettingsLib/res/values-is/strings.xml @@ -117,7 +117,7 @@ <string name="bluetooth_profile_opp" msgid="6692618568149493430">"Skráaflutningur"</string> <string name="bluetooth_profile_hid" msgid="2969922922664315866">"Inntakstæki"</string> <string name="bluetooth_profile_pan" msgid="1006235139308318188">"Internetaðgangur"</string> - <string name="bluetooth_profile_pbap" msgid="4262303387989406171">"Tengiliðir, SMS-skilaboð og símtalaferill"</string> + <string name="bluetooth_profile_pbap" msgid="4262303387989406171">"Deiling tengiliða og símtalaferils"</string> <string name="bluetooth_profile_pbap_summary" msgid="6466456791354759132">"Nota til að deila tengiliðum og símtalaferli"</string> <string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"Deiling nettengingar"</string> <string name="bluetooth_profile_map" msgid="8907204701162107271">"Textaskilaboð"</string> @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-hljóð: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD-hljóð"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Heyrnartæki"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE-hljóð"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Tengt við heyrnartæki"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Tengt við LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Tengt við LE-hljóð"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Tengt við hljóðspilun efnis"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Tengt við hljóð símans"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Tengt við skráaflutningsþjón"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Gera stærð allrar virkni breytanlega svo að hún henti fyrir marga glugga, óháð gildum í upplýsingaskrá."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Virkja glugga með frjálsu sniði"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Virkja stuðning við glugga með frjálsu sniði á tilraunastigi."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Skjáborðsstilling"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Aðgangsorð tölvuafritunar"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Heildarafritun á tölvu er ekki varin sem stendur."</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Ýttu til að breyta eða fjarlægja aðgangsorðið fyrir heildarafritun á tölvu"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> fram að fullri hleðslu"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> fram að fullri hleðslu"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> – Hleðsla takmörkuð tímabundið"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Óþekkt"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Í hleðslu"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Hröð hleðsla"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Hæg hleðsla"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Hleður þráðlaust"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Hleður í dokku"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ekki í hleðslu"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Tengt, ekki í hleðslu"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Fullhlaðin"</string> diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml index 201fb48bc25b..87f5870d6a61 100644 --- a/packages/SettingsLib/res/values-it/strings.xml +++ b/packages/SettingsLib/res/values-it/strings.xml @@ -117,7 +117,7 @@ <string name="bluetooth_profile_opp" msgid="6692618568149493430">"Trasferimento file"</string> <string name="bluetooth_profile_hid" msgid="2969922922664315866">"Dispositivo di input"</string> <string name="bluetooth_profile_pan" msgid="1006235139308318188">"Accesso a Internet"</string> - <string name="bluetooth_profile_pbap" msgid="4262303387989406171">"Condivis. contatti e cronologia chiamate"</string> + <string name="bluetooth_profile_pbap" msgid="4262303387989406171">"Condivisione contatti e cronologia chiamate"</string> <string name="bluetooth_profile_pbap_summary" msgid="6466456791354759132">"Usa per condivisione di contatti e cronologia chiamate"</string> <string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"Condivisione connessione Internet"</string> <string name="bluetooth_profile_map" msgid="8907204701162107271">"SMS"</string> @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Audio HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Audio HD"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Apparecchi acustici"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Connessione con gli apparecchi acustici stabilita"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Connesso a LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Connesso a LE Audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Collegato ad audio media"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Collegato ad audio telefono"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Collegato al server di trasferimento file"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Rendi il formato di tutte le attività modificabile per la modalità multi-finestra, indipendentemente dai valori manifest"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Attiva finestre a forma libera"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Attiva il supporto delle finestre a forma libera sperimentali"</string> + <string name="desktop_mode" msgid="2389067840550544462">"Modalità desktop"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Password di backup desktop"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"I backup desktop completi non sono attualmente protetti"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Tocca per modificare o rimuovere la password per i backup desktop completi"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> alla ricarica completa"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> alla ricarica completa"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - Ricarica momentaneamente limitata"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Sconosciuta"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"In carica"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Ricarica veloce"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Ricarica lenta"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"In carica, wireless"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"In carica nel dock"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Non in carica"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Dispositivo connesso, non in carica"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Carica"</string> diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml index e6f1bcbbe768..5f567c6b4fb9 100644 --- a/packages/SettingsLib/res/values-iw/strings.xml +++ b/packages/SettingsLib/res/values-iw/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"אודיו באיכות HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"אודיו באיכות HD"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"מכשירי שמיעה"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE Audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"מחובר אל מכשירי שמיעה"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"מחובר אל LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"מחובר אל LE audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"מחובר לאודיו של מדיה"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"מחובר לאודיו של הטלפון"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"מחובר לשרת העברת קבצים"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"מאפשר יכולת קביעת גודל של כל הפעילויות לריבוי חלונות, ללא קשר לערך המניפסט."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"הפעלת האפשרות לשנות את הגודל והמיקום של החלונות"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"הפעלת תמיכה בתכונה הניסיונית של שינוי הגודל והמיקום של החלונות."</string> + <string name="desktop_mode" msgid="2389067840550544462">"ממשק המחשב"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"סיסמת גיבוי שולחן העבודה"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"גיבויים מלאים בשולחן העבודה אינם מוגנים כעת"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"יש להקיש כדי לשנות או להסיר את הסיסמה לגיבויים מלאים בשולחן העבודה"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"הזמן הנותר לטעינה מלאה: <xliff:g id="TIME">%1$s</xliff:g>"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – הזמן הנותר לטעינה מלאה: <xliff:g id="TIME">%2$s</xliff:g>"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> – הטעינה מוגבלת זמנית"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"לא ידוע"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"בטעינה"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"הסוללה נטענת מהר"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"הסוללה נטענת לאט"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"בטעינה אלחוטית"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"אביזר עגינה לטעינה"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"לא בטעינה"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"מחובר, לא בטעינה"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"הסוללה טעונה"</string> diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml index c42679bff7f3..49dfb92ef8d2 100644 --- a/packages/SettingsLib/res/values-ja/strings.xml +++ b/packages/SettingsLib/res/values-ja/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD オーディオ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD オーディオ"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"補聴器"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE Audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"補聴器に接続"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"LE_AUDIO に接続"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE Audio に接続"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"メディアの音声に接続"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"携帯電話の音声に接続"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"ファイル転送サーバーに接続"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"マニフェストの値に関係なく、マルチウィンドウですべてのアクティビティのサイズを変更できるようにします。"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"フリーフォーム ウィンドウを有効にする"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"試験運用機能のフリーフォーム ウィンドウのサポートを有効にします。"</string> + <string name="desktop_mode" msgid="2389067840550544462">"デスクトップ モード"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"PC バックアップ パスワード"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"デスクトップのフルバックアップは現在保護されていません"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"デスクトップのフルバックアップ用のパスワードを変更または削除する場合にタップします"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"完了まであと <xliff:g id="TIME">%1$s</xliff:g>"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - 完了まであと <xliff:g id="TIME">%2$s</xliff:g>"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - 充電は一時的に制限されています"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"不明"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"急速充電中"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"低速充電中"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"ワイヤレス充電中"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"充電ホルダー"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"充電していません"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"接続済み、充電していません"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"充電が完了しました"</string> diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml index cd206e6275cd..93dfb44e079d 100644 --- a/packages/SettingsLib/res/values-ka/strings.xml +++ b/packages/SettingsLib/res/values-ka/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD აუდიო: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD აუდიო"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"სმენის მოწყობილობები"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE აუდიო"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"დაკავშირებულია სმენის მოწყობილობებთან"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"დაკავშირებულია შემდეგთან: LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"დაკავშირებულია LE აუდიოსთან"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"დაკავშირებულია აუდიო მულტიმედიურ სისტემასთან"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"დაკავშირებულია ტელეფონის აუდიო მოწყობილობასთან"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"დაკავშირებულია ფაილების გადაცემის სერვერთან"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"მანიფესტის მნიშვნელობების მიუხედავად, მრავალი ფანჯრის რეჟიმისთვის ყველა აქტივობის ზომაცვლადად გადაქცევა."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"თავისუფალი ფორმის მქონე ფანჯრების ჩართვა"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"თავისუფალი ფორმის მქონე ფანჯრების მხარდაჭერის ექსპერიმენტული ფუნქციის ჩართვა."</string> + <string name="desktop_mode" msgid="2389067840550544462">"დესკტოპის რეჟიმი"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"დესკტოპის სარეზერვო ასლის პაროლი"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"დესკტოპის სრული სარეზერვო ასლები ამჟამად დაცული არ არის"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"შეეხეთ დესკტოპის სრული სარეზერვო ასლების პაროლის შესაცვლელად ან წასაშლელად"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"სრულ დატენვამდე დარჩენილია <xliff:g id="TIME">%1$s</xliff:g>"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> — სრულ დატენვამდე დარჩენილია <xliff:g id="TIME">%2$s</xliff:g>"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> — დატენვა დროებით შეზღუდულია"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"უცნობი"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"იტენება"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"სწრაფად იტენება"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"ნელა იტენება"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"უსადენოდ დატენა"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"დამტენი სამაგრი"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"არ იტენება"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"დაკავშირებულია, არ იტენება"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"დატენილია"</string> diff --git a/packages/SettingsLib/res/values-kk/arrays.xml b/packages/SettingsLib/res/values-kk/arrays.xml index fc998e73a16c..9971f8651c12 100644 --- a/packages/SettingsLib/res/values-kk/arrays.xml +++ b/packages/SettingsLib/res/values-kk/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"Жүйенің таңдағанын алу (әдепкі)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудиокодегі"</item> + <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудиокодегі"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"L34C"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"Жүйенің таңдағанын алу (әдепкі)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудиокодегі"</item> + <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудиокодегі"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"L34C"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"Жүйенің таңдағанын алу (әдепкі)"</item> <item msgid="8003118270854840095">"44,1 кГц"</item> diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml index d0b8c0dfa6ea..4733c28d23bc 100644 --- a/packages/SettingsLib/res/values-kk/strings.xml +++ b/packages/SettingsLib/res/values-kk/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD форматты аудио: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD форматты аудио"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Есту аппараттары"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE Audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Есту аппараттарына жалғанған"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"LE_AUDIO құрылғысына жалғанды."</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE Audio-ға жалғанды."</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Медиа аудиосына жалғанған"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Телефон аудиосына қосылған"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Файл жіберу серверіне жалғанған"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Манифест мәндеріне қарамастан, бірнеше терезе режимінде барлық әрекеттердің өлшемін өзгертуге рұқсат беру"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Еркін пішінді терезелерге рұқсат беру"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Еркін пішінді терезелерді құру эксперименттік функиясын қосу"</string> + <string name="desktop_mode" msgid="2389067840550544462">"Компьютер режимі"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Компьютердегі сақтық көшірме құпия сөзі"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Компьютердегі толық сақтық көшірмелер қазір қорғалмаған."</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Үстелдік компьютердің толық сақтық көшірмелерінің кілтсөзін өзгерту немесе жою үшін түртіңіз"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Толық зарядталғанға дейін <xliff:g id="TIME">%1$s</xliff:g> қалды."</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – толық зарядталғанға дейін <xliff:g id="TIME">%2$s</xliff:g> қалды."</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> – Зарядтау уақытша шектелген"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Белгісіз"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Зарядталуда"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Жылдам зарядталуда"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Баяу зарядталуда"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Сымсыз зарядталуда"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Қондыру станциясы зарядталуда"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Зарядталу орындалып жатқан жоқ"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Жалғанған, зарядталып жатқан жоқ"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Зарядталды"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Ауа сапасы"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Трансляция ақпараты"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Үйді басқару элементтері"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"Профиль суретін таңдау"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"Әдепкі пайдаланушы белгішесі"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"Пернетақта"</string> diff --git a/packages/SettingsLib/res/values-km/arrays.xml b/packages/SettingsLib/res/values-km/arrays.xml index a005f4dc2bd0..2269df193c23 100644 --- a/packages/SettingsLib/res/values-km/arrays.xml +++ b/packages/SettingsLib/res/values-km/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"ប្រើការជ្រើសរើសប្រព័ន្ធ (លំនាំដើម)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g>សំឡេង <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g>សំឡេង <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"ប្រើការជ្រើសរើសប្រព័ន្ធ (លំនាំដើម)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g>សំឡេង <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g>សំឡេង <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"ប្រើការជ្រើសរើសប្រព័ន្ធ (លំនាំដើម)"</item> <item msgid="8003118270854840095">"44.1 kHz"</item> diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml index fa759a61cf06..771598535e45 100644 --- a/packages/SettingsLib/res/values-km/strings.xml +++ b/packages/SettingsLib/res/values-km/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"សំឡេងកម្រិត HD៖ <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"សំឡេងកម្រិត HD"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"ឧបករណ៍ជំនួយការស្ដាប់"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"បានភ្ជាប់ទៅឧបករណ៍ជំនួយការស្ដាប់"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"បានភ្ជាប់ទៅ LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"បានភ្ជាប់ទៅ LE audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"បានភ្ជាប់ទៅអូឌីយ៉ូមេឌៀ"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"តភ្ជាប់ទៅអូឌីយ៉ូទូរស័ព្ទ"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"បានតភ្ជាប់ទៅម៉ាស៊ីនមេផ្ទេរឯកសារ"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"ធ្វើឲ្យសកម្មភាពទាំងអស់អាចប្តូរទំហំបានសម្រាប់ពហុវិនដូ ដោយមិនគិតពីតម្លៃមេនីហ្វេសថ៍។"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"បើកដំណើរការផ្ទាំងវិនដូទម្រង់សេរី"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"បើកឱ្យអាចប្រើផ្ទាំងវិនដូទម្រង់សេរីពិសោធន៍។"</string> + <string name="desktop_mode" msgid="2389067840550544462">"មុខងារកុំព្យូទ័រ"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"ពាក្យសម្ងាត់បម្រុងទុកលើកុំព្យូទ័រ"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"បច្ចុប្បន្ន ការបម្រុងទុកពេញលេញនៅលើកុំព្យូទ័រមិនត្រូវបានការពារទេ"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"ប៉ះដើម្បីប្ដូរ ឬយកពាក្យសម្ងាត់ចេញសម្រាប់ការបម្រុងទុកពេញលេញលើកុំព្យូទ័រ"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> ទៀតទើបពេញ"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - នៅសល់ <xliff:g id="TIME">%2$s</xliff:g> ទៀតទើបពេញ"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - បានដាក់កំហិតលើការសាកថ្មជាបណ្ដោះអាសន្ន"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"មិនស្គាល់"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"កំពុងសាកថ្ម"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"កំពុងសាកថ្មយ៉ាងឆាប់រហ័ស"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"កំពុងសាកថ្មយឺត"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"កំពុងសាកថ្មឥតខ្សែ"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"ឧបករណ៍ភ្ជាប់សាកថ្ម"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"មិនកំពុងសាកថ្ម"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"បានភ្ជាប់ មិនកំពុងសាកថ្ម"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"បានសាកថ្មពេញ"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"គុណភាពខ្យល់"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"ព័ត៌មានអំពីការបញ្ជូន"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ការគ្រប់គ្រងផ្ទះ"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"ជ្រើសរើសរូបភាពកម្រងព័ត៌មាន"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"រូបអ្នកប្រើប្រាស់លំនាំដើម"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"ក្ដារចុចរូបវន្ត"</string> diff --git a/packages/SettingsLib/res/values-kn/arrays.xml b/packages/SettingsLib/res/values-kn/arrays.xml index b6014ce72c65..975f60f3c5d5 100644 --- a/packages/SettingsLib/res/values-kn/arrays.xml +++ b/packages/SettingsLib/res/values-kn/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"ಸಿಸ್ಟಂ ಆಯ್ಕೆಯನ್ನು ಬಳಸಿ (ಡಿಫಾಲ್ಟ್)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ಆಡಿಯೋ"</item> + <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ಆಡಿಯೋ"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"ಸಿಸ್ಟಂ ಆಯ್ಕೆಯನ್ನು ಬಳಸಿ (ಡಿಫಾಲ್ಟ್)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ಆಡಿಯೋ"</item> + <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ಆಡಿಯೋ"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"ಸಿಸ್ಟಂ ಆಯ್ಕೆಯನ್ನು ಬಳಸಿ (ಡಿಫಾಲ್ಟ್)"</item> <item msgid="8003118270854840095">"44.1 kHz"</item> diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml index 5b0d684cd9f4..66e2da872045 100644 --- a/packages/SettingsLib/res/values-kn/strings.xml +++ b/packages/SettingsLib/res/values-kn/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ಆಡಿಯೋ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ಆಡಿಯೋ"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"ಶ್ರವಣ ಸಾಧನಗಳು"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE ಆಡಿಯೋ"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"ಶ್ರವಣ ಸಾಧನಗಳಿಗೆ ಸಂಪರ್ಕಿಸಲಾಗಿದೆ"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"LE_AUDIO ಗೆ ಕನೆಕ್ಟ್ ಆಗಿದೆ"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE ಆಡಿಯೋಗೆ ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"ಮಾಧ್ಯಮ ಆಡಿಯೋಗೆ ಸಂಪರ್ಕಗೊಂಡಿದೆ"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ಫೋನ್ ಆಡಿಯೋಗೆ ಸಂಪರ್ಕಗೊಂಡಿದೆ"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"ಫೈಲ್ ವರ್ಗಾವಣೆ ಸರ್ವರ್ಗೆ ಸಂಪರ್ಕಗೊಂಡಿದೆ"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"ಮ್ಯಾನಿಫೆಸ್ಟ್ ಮೌಲ್ಯಗಳನ್ನು ಪರಿಗಣಿಸದೇ, ಬಹು-ವಿಂಡೊಗೆ ಎಲ್ಲಾ ಚಟುವಟಿಕೆಗಳನ್ನು ಮರುಗಾತ್ರಗೊಳಿಸುವಂತೆ ಮಾಡಿ."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"ಮುಕ್ತಸ್ವರೂಪದ ವಿಂಡೊಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"ಪ್ರಾಯೋಗಿಕ ಫ್ರೀಫಾರ್ಮ್ ವಿಂಡೊಗಳಿಗೆ ಬೆಂಬಲವನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ."</string> + <string name="desktop_mode" msgid="2389067840550544462">"ಡೆಸ್ಕ್ಟಾಪ್ ಮೋಡ್"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"ಡೆಸ್ಕ್ಟಾಪ್ ಬ್ಯಾಕಪ್ ಪಾಸ್ವರ್ಡ್"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"ಡೆಸ್ಕ್ಟಾಪ್ನ ಪೂರ್ಣ ಬ್ಯಾಕಪ್ಗಳನ್ನು ಪ್ರಸ್ತುತ ರಕ್ಷಿಸಲಾಗಿಲ್ಲ"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"ಡೆಸ್ಕ್ಟಾಪ್ನ ಪೂರ್ಣ ಬ್ಯಾಕಪ್ಗಳಿಗೆ ಪಾಸ್ವರ್ಡ್ ಬದಲಾಯಿಸಲು ಅಥವಾ ತೆಗೆದುಹಾಕಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> - ಸಮಯದಲ್ಲಿ ಪೂರ್ತಿ ಚಾರ್ಜ್ ಆಗುತ್ತದೆ"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ಸಮಯದಲ್ಲಿ ಪೂರ್ತಿ ಚಾರ್ಜ್ ಆಗುತ್ತದೆ"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - ಚಾರ್ಜಿಂಗ್ ಅನ್ನು ತಾತ್ಕಾಲಿಕವಾಗಿ ಸೀಮಿತಗೊಳಿಸಲಾಗಿದೆ"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"ಅಪರಿಚಿತ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ವೇಗದ ಚಾರ್ಜಿಂಗ್"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"ನಿಧಾನ ಗತಿಯ ಚಾರ್ಜಿಂಗ್"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"ವೈರ್ಲೆಸ್ ಚಾರ್ಜಿಂಗ್"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"ಚಾರ್ಜಿಂಗ್ ಡಾಕ್"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ಚಾರ್ಜ್ ಆಗುತ್ತಿಲ್ಲ"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"ಕನೆಕ್ಟ್ ಆಗಿದೆ, ಚಾರ್ಜ್ ಆಗುತ್ತಿಲ್ಲ"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"ಚಾರ್ಜ್ ಆಗಿದೆ"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"ವಾಯು ಗುಣಮಟ್ಟ"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"ಬಿತ್ತರಿಸಿದ ಮಾಹಿತಿ"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ಹೋಮ್ ನಿಯಂತ್ರಣಗಳು"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"ಪ್ರೊಫೈಲ್ ಚಿತ್ರವನ್ನು ಆಯ್ಕೆ ಮಾಡಿ"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"ಡೀಫಾಲ್ಟ್ ಬಳಕೆದಾರರ ಐಕಾನ್"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"ಭೌತಿಕ ಕೀಬೋರ್ಡ್"</string> diff --git a/packages/SettingsLib/res/values-ko/arrays.xml b/packages/SettingsLib/res/values-ko/arrays.xml index 7138113c8b74..16b840bd9f5f 100644 --- a/packages/SettingsLib/res/values-ko/arrays.xml +++ b/packages/SettingsLib/res/values-ko/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"시스템 설정 사용(기본)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> 오디오"</item> + <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> 오디오"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"시스템 설정 사용(기본)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> 오디오"</item> + <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> 오디오"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"시스템 설정 사용(기본)"</item> <item msgid="8003118270854840095">"44.1kHz"</item> diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml index ae9c19f9f9a5..111dd34c5d91 100644 --- a/packages/SettingsLib/res/values-ko/strings.xml +++ b/packages/SettingsLib/res/values-ko/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD 오디오: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD 오디오"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"보청기"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE 오디오"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"보청기에 연결됨"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"LE_AUDIO에 연결됨"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE 오디오에 연결됨"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"미디어 오디오에 연결됨"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"휴대전화 오디오에 연결됨"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"파일 전송 서버에 연결됨"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"모든 활동을 매니페스트 값에 관계없이 멀티 윈도우용으로 크기 조정 가능하도록 설정"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"자유 형식 창 사용"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"자유 형식 창 지원 사용"</string> + <string name="desktop_mode" msgid="2389067840550544462">"데스크톱 모드"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"데스크톱 백업 비밀번호"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"데스크톱 전체 백업에 비밀번호가 설정되어 있지 않음"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"데스크톱 전체 백업에 대한 비밀번호를 변경하거나 삭제하려면 탭하세요."</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> 후 충전 완료"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> 후 충전 완료"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - 충전이 일시적으로 제한됨"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"알 수 없음"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"충전 중"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"고속 충전 중"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"저속 충전 중"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"무선 충전 중"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"충전 도크"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"충전 안함"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"연결됨, 충전 중 아님"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"충전됨"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"대기 상태"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"전송 정보"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"홈 컨트롤"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"프로필 사진 선택하기"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"기본 사용자 아이콘"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"물리적 키보드"</string> diff --git a/packages/SettingsLib/res/values-ky/arrays.xml b/packages/SettingsLib/res/values-ky/arrays.xml index 40271f71afdb..700aae13822e 100644 --- a/packages/SettingsLib/res/values-ky/arrays.xml +++ b/packages/SettingsLib/res/values-ky/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"карта13"</item> <item msgid="8147982633566548515">"карта14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"Система тандаганды колдонуу (демейки)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item> + <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудио"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"Система тандаганды колдонуу (демейки)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item> + <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудио"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"Система тандаганды колдонуу (демейки)"</item> <item msgid="8003118270854840095">"44,1 кГц"</item> diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml index e85e6afa3509..ea9bd8629356 100644 --- a/packages/SettingsLib/res/values-ky/strings.xml +++ b/packages/SettingsLib/res/values-ky/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD форматындагы аудио: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD форматындагы аудио"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Угуу аппараттары"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE аудио"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Угуу аппараттарына туташып турат"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"LE_AUDIO менен туташты"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE аудио менен туташты"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Медиа аудиого туташты"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Телефон аудиосуна туташты"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Файл өткөрүү серверине туташты"</string> @@ -396,7 +396,7 @@ <string name="overlay_display_devices_title" msgid="5411894622334469607">"Көмөкчү экрандардын эмуляциясы"</string> <string name="debug_applications_category" msgid="5394089406638954196">"Колдонмолор"</string> <string name="immediately_destroy_activities" msgid="1826287490705167403">"Аракеттер сакталбасын"</string> - <string name="immediately_destroy_activities_summary" msgid="6289590341144557614">"Колдонуучу чыгып кетери менен бардык аракеттер өчүрүлөт"</string> + <string name="immediately_destroy_activities_summary" msgid="6289590341144557614">"Колдонуучу чыгып кетери менен бардык аракеттер өчүп калат"</string> <string name="app_process_limit_title" msgid="8361367869453043007">"Фондогу процесстер чеги"</string> <string name="show_all_anrs" msgid="9160563836616468726">"Фондук режимдеги ANR"</string> <string name="show_all_anrs_summary" msgid="8562788834431971392">"Фондогу колдонмо жооп бербей жатат деп билдирип турат"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Бир нече терезе режиминде өлчөмдү өзгөртүүгө уруксат берет (манифесттин маанилерине карабастан)"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Эркин формадагы терезелерди түзүүнү иштетүү"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Эркин формадагы терезелерди түзүү боюнча сынамык функциясы иштетилет."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Компьютер режими"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Камдык көчүрмөнүн сырсөзү"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Толук камдык көчүрмөлөр учурда корголгон эмес"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Иш тактасынын камдалган сырсөзүн өзгөртүү же алып салуу үчүн таптап коюңуз"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> кийин толук кубатталат"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> кийин толук кубатталат"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - Кубаттоо убактылуу чектелген"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Белгисиз"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Кубатталууда"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Ыкчам кубатталууда"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Жай кубатталууда"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Зымсыз кубатталууда"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Кубаттоо док бекети"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Кубат алган жок"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Туташты, кубатталган жок"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Кубатталды"</string> @@ -602,7 +605,7 @@ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Конок сеансын баштапкы абалга келтиресизби?"</string> <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Бул аракет жаңы конок сеансын баштап, учурдагы сеанстагы бардык колдонмолорду жана алардагы нерселерди жок кылат"</string> <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Конок режиминен чыгасызбы?"</string> - <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Бул учурдагы конок сеансындагы колдонмолорду жана алардагы нерселерди жок кылат"</string> + <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Учурдагы конок сеансындагы бардык колдонмолор менен алардагы нерселер өчүп калат"</string> <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Чыгуу"</string> <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Коноктун аракеттерин сактайсызбы?"</string> <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Учурдагы сеанстагы аракеттерди сактап же бардык колдонмолорду жана алардагы нерселерди жок кылсаңыз болот"</string> @@ -611,7 +614,7 @@ <string name="guest_exit_button" msgid="5774985819191803960">"Конок режиминен чыгуу"</string> <string name="guest_reset_button" msgid="2515069346223503479">"Конок сеансын кайра коюу"</string> <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Конок режиминен чыгуу"</string> - <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Чыксаңыз, бардык аракеттер өчүрүлөт"</string> + <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Чыксаңыз, бардык аракеттер өчүп калат"</string> <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Чыгуудан мурун аракеттериңизди сактап же жок кылсаңыз болот"</string> <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Сеанстагы аракеттерди азыр өчүрсөңүз болот же чыгып баратып өчүрүп же сактап коюңуз"</string> <string name="user_image_take_photo" msgid="467512954561638530">"Сүрөткө тартуу"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Абанын сапаты"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Тышкы экранга чыгаруу маалыматы"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Үйдү көзөмөлдөө"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"Профилдин сүрөтүн тандоо"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"Демейки колдонуучунун сүрөтчөсү"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"Аппараттык баскычтоп"</string> diff --git a/packages/SettingsLib/res/values-lo/arrays.xml b/packages/SettingsLib/res/values-lo/arrays.xml index 792ca39a8bf4..f116e6f4f311 100644 --- a/packages/SettingsLib/res/values-lo/arrays.xml +++ b/packages/SettingsLib/res/values-lo/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"ໃຊ້ການເລືອກຂອງລະບົບ (ຄ່າເລີ່ມຕົ້ນ)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"ສຽງ <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="2908219194098827570">"ສຽງ <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"ໃຊ້ການເລືອກຂອງລະບົບ (ຄ່າເລີ່ມຕົ້ນ)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"ສຽງ <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="3517061573669307965">"ສຽງ <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"ໃຊ້ການເລືອກຂອງລະບົບ (ຄ່າເລີ່ມຕົ້ນ)"</item> <item msgid="8003118270854840095">"44.1 kHz"</item> diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml index ace42767eb27..10b814d4b98a 100644 --- a/packages/SettingsLib/res/values-lo/strings.xml +++ b/packages/SettingsLib/res/values-lo/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"ສຽງ HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"ສຽງ HD"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"ອຸປະກອນຊ່ວຍຟັງ"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"ສຽງ LE"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"ເຊື່ອມຕໍ່ຫາອຸປະກອນຊ່ວຍຟັງແລ້ວ"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"ເຊື່ອມຕໍ່ຫາ LE_AUDIO ແລ້ວ"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"ເຊື່ອມຕໍ່ຫາສຽງ LE ແລ້ວ"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"ເຊື່ອມຕໍ່ກັບສື່ດ້ານສຽງແລ້ວ"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ເຊື່ອມຕໍ່ກັບສຽງໂທລະສັບແລ້ວ"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"ເຊື່ອມຕໍ່ກັບເຊີບເວີໂອນຍ້າຍໄຟລ໌ແລ້ວ"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"ເຮັດໃຫ້ທຸກການເຄື່ອນໄຫວສາມາດປັບຂະໜາດໄດ້ສຳລັບຫຼາຍໜ້າຈໍ, ໂດຍບໍ່ຄຳນຶງເຖິງຄ່າ manifest."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"ເປີດໃຊ້ໜ້າຈໍຮູບແບບອິດສະຫຼະ"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"ເປີດໃຊ້ການຮອງຮັບໜ້າຈໍຮູບແບບອິດສະຫຼະແບບທົດລອງ."</string> + <string name="desktop_mode" msgid="2389067840550544462">"ໂໝດເດັສທັອບ"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"ລະຫັດຜ່ານການສຳຮອງຂໍ້ມູນເດັສທັອບ"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"ການສຳຮອງຂໍ້ມູນເຕັມຮູບແບບໃນເດັສທັອບຍັງບໍ່ໄດ້ຮັບການປ້ອງກັນໃນເວລານີ້"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"ແຕະເພື່ອປ່ຽນ ຫຼື ລຶບລະຫັດຂອງການສຳຮອງຂໍ້ມູນເຕັມຮູບແບບໃນເດັສທັອບ"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"ຍັງເຫຼືອອີກ <xliff:g id="TIME">%1$s</xliff:g> ຈຶ່ງຈະສາກເຕັມ"</string> <string name="power_charging_duration" msgid="6127154952524919719">"ຍັງເຫຼືອອີກ <xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ຈຶ່ງຈະສາກເຕັມ"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - ຈຳກັດການສາກໄຟຊົ່ວຄາວ"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"ບໍ່ຮູ້ຈັກ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ກຳລັງສາກໄຟ"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ກຳລັງສາກໄຟດ່ວນ"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"ກຳລັງສາກໄຟຊ້າໆ"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"ກຳລັງສາກໄຟໄຮ້ສາຍ"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"ກຳລັງສາກໄຟຜ່ານດັອກ"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ບໍ່ໄດ້ສາກໄຟ"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"ເຊື່ອມຕໍ່ແລ້ວ, ບໍ່ໄດ້ສາກໄຟ"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"ສາກເຕັມແລ້ວ"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"ຄຸນນະພາບອາກາດ"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"ຂໍ້ມູນການສົ່ງສັນຍານ"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ການຄວບຄຸມເຮືອນ"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"ເລືອກຮູບໂປຣໄຟລ໌"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"ໄອຄອນຜູ້ໃຊ້ເລີ່ມຕົ້ນ"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"ແປ້ນພິມພາຍນອກ"</string> diff --git a/packages/SettingsLib/res/values-lt/arrays.xml b/packages/SettingsLib/res/values-lt/arrays.xml index 946f69c3030b..c0aafdcd4da3 100644 --- a/packages/SettingsLib/res/values-lt/arrays.xml +++ b/packages/SettingsLib/res/values-lt/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"Naudoti sistemos pasirink. (numatytasis)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> garsas"</item> + <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> garsas"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"Naudoti sistemos pasirink. (numatytasis)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> garsas"</item> + <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> garsas"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"Naudoti sistemos pasirink. (numatytasis)"</item> <item msgid="8003118270854840095">"44,1 kHz"</item> diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml index b309db6c9537..ce53dfebc15d 100644 --- a/packages/SettingsLib/res/values-lt/strings.xml +++ b/packages/SettingsLib/res/values-lt/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD garsas: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD garsas"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Klausos aparatai"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE Audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Prisijungta prie klausos aparatų"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Prisijungta prie LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Prisijungta prie „LE Audio“"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Prijungta prie medijos garso įrašo"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Prijungta prie telefono garso"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Prijungta prie failų perkėlimo serverio"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Nustatyti, kad visus veiksmus būtų galima atlikti kelių dydžių languose, nepaisant aprašo verčių."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Įgalinti laisvos formos langus"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Įgalinti eksperimentinių laisvos formos langų palaikymą."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Stalinio komp. režimas"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Viet. atsrg. kop. slapt."</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Šiuo metu visos vietinės atsarginės kopijos neapsaugotos"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Jei norite pakeisti ar pašalinti visų stalinio kompiuterio atsarginių kopijų slaptažodį, palieskite"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Liko <xliff:g id="TIME">%1$s</xliff:g>, kol bus visiškai įkrauta"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – liko <xliff:g id="TIME">%2$s</xliff:g>, kol bus visiškai įkrauta"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> – Įkrovimas laikinai apribotas"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Nežinomas"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Kraunasi..."</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Greitai įkraunama"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Lėtai įkraunama"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Kraunama be laidų"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Įkrovimo dokas"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nekraunama"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Prijungta, neįkraunama"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Įkrauta"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Oro kokybė"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Perdav. informacija"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Namų sist. valdikl."</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"Pasirinkite profilio nuotrauką"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"Numatytojo naudotojo piktograma"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"Fizinė klaviatūra"</string> diff --git a/packages/SettingsLib/res/values-lv/arrays.xml b/packages/SettingsLib/res/values-lv/arrays.xml index f4ae45234bf5..0f9ee52c3fac 100644 --- a/packages/SettingsLib/res/values-lv/arrays.xml +++ b/packages/SettingsLib/res/values-lv/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"Sistēmas atlases izmantošana (nokl.)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item> + <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"Sistēmas atlases izmantošana (nokl.)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item> + <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"Sistēmas atlases izmantošana (nokl.)"</item> <item msgid="8003118270854840095">"44,1 kHz"</item> diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml index 6bbc051fad77..df917917799a 100644 --- a/packages/SettingsLib/res/values-lv/strings.xml +++ b/packages/SettingsLib/res/values-lv/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Dzirdes aparāti"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO profils"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE Audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Izveidots savienojums ar dzirdes aparātiem"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Izveidots savienojums ar LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Izveidots savienojums ar LE Audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Savienots ar multivides audio"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Savienots ar tālruņa audio"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Savienots ar failu pārsūtīšanas serveri"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Pielāgot visas darbības vairāku logu režīmam neatkarīgi no vērtībām manifestā."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Iespējot brīvās formas logus"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Iespējot eksperimentālo brīvās formas logu atbalstu."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Darbvirsmas režīms"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Datora dublējuma parole"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Darbvirsmas pilnie dublējumi pašlaik nav aizsargāti."</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Pieskarieties, lai mainītu vai noņemtu paroli pilniem datora dublējumiem."</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> — <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> līdz pilnai uzlādei"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> — <xliff:g id="TIME">%2$s</xliff:g> līdz pilnai uzlādei"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> — uzlāde īslaicīgi ierobežota"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Nezināms"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Uzlāde"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Notiek ātrā uzlāde"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Notiek lēnā uzlāde"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Bezvadu uzlāde"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Uzlādes doks"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nenotiek uzlāde"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Ierīce pievienota, uzlāde nenotiek"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Uzlādēts"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Gaisa kvalitāte"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Apraides informācija"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Mājas kontrolierīces"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"Profila attēla izvēle"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"Noklusējuma lietotāja ikona"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"Fiziskā tastatūra"</string> diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml index eb9cbfdd6a7e..7f8283501b96 100644 --- a/packages/SettingsLib/res/values-mk/strings.xml +++ b/packages/SettingsLib/res/values-mk/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-аудио: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD-аудио"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Слушни помагала"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE-аудио"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Поврзано со слушни помагала"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Поврзано на LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Поврзано на LE-аудио"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Поврзан со аудио на медиуми"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Поврзан со аудио на телефон"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Поврзан со сервер за пренос на датотеки"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Направете сите активности да бидат со променлива големина за повеќе прозорци, без разлика на вредностите на манифестот."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Овозможи прозорци со слободна форма"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Овозможи поддршка за експериментални прозорци со слободна форма."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Режим за компјутер"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Лозинка за бекап на компјутер"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Целосниот бекап на компјутерот во моментов не е заштитен"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Допрете за да се промени или отстрани лозинката за целосен бекап на компјутерот"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> до полна батерија"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> до полна батерија"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - Полнењето е привремено ограничено"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Непознато"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Се полни"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Брзо полнење"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Бавно полнење"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Се полни безжично"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Се полни на док"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не се полни"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Поврзано, не се полни"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Полна"</string> diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml index 3d99eda91210..43040ef6281a 100644 --- a/packages/SettingsLib/res/values-ml/strings.xml +++ b/packages/SettingsLib/res/values-ml/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ഓഡിയോ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ഓഡിയോ"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"ശ്രവണ സഹായികൾ"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE ഓഡിയോ"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"ശ്രവണ സഹായികളിലേക്ക് കണക്റ്റ് ചെയ്തു"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"LE_AUDIO-യിലേക്ക് കണക്റ്റ് ചെയ്തു"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE ഓഡിയോയിലേക്ക് കണക്റ്റ് ചെയ്തു"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"മീഡിയ ഓഡിയോയിലേക്ക് കണക്റ്റുചെയ്തു"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ഫോൺ ഓഡിയോയിൽ കണക്റ്റുചെയ്തു"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"ഫയൽ കൈമാറ്റ സെർവറിലേക്ക് കണക്റ്റുചെയ്തു"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"മാനിഫെസ്റ്റ് മൂല്യങ്ങൾ പരിഗണിക്കാതെ, എല്ലാ ആക്ടിവിറ്റികളെയും മൾട്ടി-വിൻഡോയ്ക്കായി വലുപ്പം മാറ്റുക."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"ഫ്രീഫോം വിൻഡോകൾ പ്രവർത്തനക്ഷമമാക്കുക"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"പരീക്ഷണാത്മക ഫ്രീഫോം വിൻഡോകൾക്കുള്ള പിന്തുണ പ്രവർത്തനക്ഷമമാക്കുക."</string> + <string name="desktop_mode" msgid="2389067840550544462">"ഡെസ്ക്ടോപ്പ് മോഡ്"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"ഡെസ്ക്ടോപ്പ് ബാക്കപ്പ് പാസ്വേഡ്"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"ഡെസ്ക്ടോപ്പ് പൂർണ്ണ ബാക്കപ്പുകൾ നിലവിൽ പരിരക്ഷിച്ചിട്ടില്ല"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"ഡെസ്ക്ടോപ്പ് പൂർണ്ണ ബാക്കപ്പുകൾക്കായി പാസ്വേഡുകൾ മാറ്റാനോ നീക്കംചെയ്യാനോ ടാപ്പുചെയ്യുക"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"പൂർണ്ണമാകാൻ <xliff:g id="TIME">%1$s</xliff:g> ശേഷിക്കുന്നു"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - പൂർണ്ണമാകാൻ <xliff:g id="TIME">%2$s</xliff:g> ശേഷിക്കുന്നു"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - ചാർജിംഗ് താൽക്കാലികമായി പരിമിതപ്പെടുത്തിയിരിക്കുന്നു"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"അജ്ഞാതം"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ചാർജ് ചെയ്യുന്നു"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"അതിവേഗ ചാർജിംഗ്"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"പതുക്കെയുള്ള ചാർജിംഗ്"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"വയർലെസായി ചാർജുചെയ്യുന്നു"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"ചാർജിംഗ് ഡോക്ക്"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ചാർജ്ജുചെയ്യുന്നില്ല"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"കണക്റ്റ് ചെയ്തിരിക്കുന്നു, ചാർജ് ചെയ്യുന്നില്ല"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"ചാർജായി"</string> diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml index c2f241c33b85..ad8b6fdcf4c0 100644 --- a/packages/SettingsLib/res/values-mn/strings.xml +++ b/packages/SettingsLib/res/values-mn/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD аудио: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD аудио"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Сонсголын төхөөрөмж"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_АУДИО"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE аудио"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Сонсголын төхөөрөмжтэй холбосон"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"LE_АУДИОНД холбогдлоо"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE аудионд холбогдсон"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Медиа аудиод холбогдсон"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Утасны аудид холбогдсон"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Файл дамжуулах серверт холбогдсон"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Тодорхойлогч файлын утгыг үл хамааран, бүх үйл ажиллагааны хэмжээг олон цонхонд өөрчилж болохуйц болгоно уу."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Чөлөөт хэлбэрийн цонхыг идэвхжүүлэх"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Туршилтын чөлөөт хэлбэрийн цонхны дэмжлэгийг идэвхжүүлнэ үү."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Дэлгэцийн горим"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Компьютерын нөөцлөлтийн нууц үг"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Компьютерын бүрэн нөөцлөлт одоогоор хамгаалалтгүй байна"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Компьютерийн бүтэн нөөцлөлтийн нууц үгийг өөрчлөх, устгах бол дарна уу"</string> @@ -447,8 +448,8 @@ <string name="daltonizer_mode_deuteranomaly" msgid="3507284319584683963">"Дьютераномаль (улаан-ногоон)"</string> <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномаль (улаан-ногоон)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомаль (цэнхэр-шар)"</string> - <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Өнгө тохируулах"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Өнгөний засвар нь таныг дараахыг хийхийг хүсэх үед хэрэгтэй байж болно:<br/> <ol> <li>&nbsp;Өнгөнүүдийг илүү нарийвчилж харах</li> <li>&nbsp;Төвлөрөхийн тулд өнгөнүүдийг хасах</li> </ol>"</string> + <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Өнгө тохируулга"</string> + <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Өнгө тохируулга нь таныг дараахыг хийхийг хүсэх үед хэрэгтэй байж болно:<br/> <ol> <li>&nbsp;Өнгөнүүдийг илүү нарийвчилж харах</li> <li>&nbsp;Төвлөрөхийн тулд өнгөнүүдийг хасах</li> </ol>"</string> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Давхарласан <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ойролцоогоор <xliff:g id="TIME_REMAINING">%1$s</xliff:g> үлдсэн"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Дүүрэх хүртэл <xliff:g id="TIME">%1$s</xliff:g> үлдсэн"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - дүүрэх хүртэл <xliff:g id="TIME">%2$s</xliff:g> үлдсэн"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - Цэнэглэхийг түр зуур хязгаарласан"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Тодорхойгүй"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Цэнэглэж байна"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Хурдан цэнэглэж байна"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Удаан цэнэглэж байна"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Утасгүй цэнэглэж байна"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Цэнэглэх холбогч"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Цэнэглэхгүй байна"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Холбогдсон, цэнэглээгүй байна"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Цэнэглэсэн"</string> diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml index 13ea9c91996f..be5222a12f83 100644 --- a/packages/SettingsLib/res/values-mr/strings.xml +++ b/packages/SettingsLib/res/values-mr/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ऑडिओ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ऑडिओ"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"श्रवणयंत्रे"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE ऑडिओ"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"श्रवण यंत्रांशी कनेक्ट केले आहे"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"LE_AUDIO शी कनेक्ट केले आहे"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE ऑडिओशी कनेक्ट केले आहे"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"मीडिया ऑडिओवर कनेक्ट केले"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"फोन ऑडिओ वर कनेक्ट केले"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"फाइल स्थानांतर सर्व्हरवर कनेक्ट केले"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"मॅनिफेस्ट मूल्ये काहीही असू देत, एकाहून अधिक विंडोसाठी सर्व अॅक्टिव्हिटीचा आकार बदलण्यायोग्य करा."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"freeform विंडो सुरू करा"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"प्रायोगिक मुक्तस्वरूपाच्या विंडोसाठी सपोर्ट सुरू करा."</string> + <string name="desktop_mode" msgid="2389067840550544462">"डेस्कटॉप मोड"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"डेस्कटॉप बॅकअप पासवर्ड"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"डेस्कटॉप पूर्ण बॅक अप सध्या संरक्षित नाहीत"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"डेस्कटॉपच्या पूर्ण बॅकअपसाठी असलेला पासवर्ड बदलण्यासाठी किंवा काढण्यासाठी टॅप करा"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"पूर्ण चार्ज होण्यासाठी <xliff:g id="TIME">%1$s</xliff:g> शिल्लक आहेत"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - पूर्ण चार्ज होण्यासाठी <xliff:g id="TIME">%2$s</xliff:g> शिल्लक आहे"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> • चार्जिंग तात्पुरते मर्यादित आहे"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"अज्ञात"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"चार्ज होत आहे"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"वेगाने चार्ज होत आहे"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"हळू चार्ज होत आहे"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"वायरलेसने चार्ज होत आहे"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"चार्जिंग डॉक"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"चार्ज होत नाही"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"कनेक्ट केले, चार्ज होत नाही"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"चार्ज झाली"</string> diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml index 9ff2170181d8..cadc5cf3548a 100644 --- a/packages/SettingsLib/res/values-ms/strings.xml +++ b/packages/SettingsLib/res/values-ms/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Audio HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Audio HD"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Alat Bantu Dengar"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE Audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Disambungkan pada Alat Bantu Dengar"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Disambungkan kepada LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Disambungkan kepada LE Audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Disambungkan ke audio media"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Disambungkan ke audio telefon"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Bersambung ke pelayan pemindahan fail"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Bolehkan semua saiz aktiviti diubah untuk berbilang tetingkap, tanpa mengambil kira nilai manifes."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Dayakan tetingkap bentuk bebas"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Dayakan sokongan untuk tetingkap bentuk bebas percubaan."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Mod desktop"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Kata laluan sandaran komputer meja"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Sandaran penuh komputer meja tidak dilindungi pada masa ini"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Ketik untuk menukar atau mengalih keluar kata laluan untuk sandaran penuh desktop"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> lagi sebelum penuh"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> lagi sebelum penuh"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - Pengecasan terhad sementara"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Tidak diketahui"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Mengecas"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Mengecas dgn cepat"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Mengecas perlahan"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Mengecas tanpa wayar"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Dok Pengecasan"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Tidak mengecas"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Bersambung, tidak mengecas"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Sudah dicas"</string> diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml index 0492dac1b191..0c49387acc63 100644 --- a/packages/SettingsLib/res/values-my/strings.xml +++ b/packages/SettingsLib/res/values-my/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD အသံ- <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD အသံ"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"နားကြားကိရိယာ"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE အသံ"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"နားကြားကိရိယာနှင့် ချိတ်ဆက်ပြီးပါပြီ"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"LE_AUDIO နှင့် ချိတ်ဆက်ထားသည်"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE အသံနှင့် ချိတ်ဆက်ထားသည်"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"မီဒီယာအသံအား ချိတ်ဆက်ရန်"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ဖုန်းအသံအား ချိတ်ဆက်ရန်"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"ဖိုင်လွှဲပြောင်းမည့်ဆာဗာနှင့် ချိတ်ဆက်ထားပြီး"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"သတ်မှတ်တန်ဖိုး မည်သို့ပင်ရှိစေ ဝင်းဒိုးများ၏ လုပ်ဆောင်မှုအားလုံးကို အရွယ်အစားပြင်သည်။"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"ရွှေ့နိုင်ပြင်နိုင်သော ဝင်းဒိုးများ ဖွင့်ရန်"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"ပုံစံမျိုးစုံဝင်းဒိုးများ စမ်းသပ်မှုအတွက် အထောက်အပံ့ကို ဖွင့်ပါ"</string> + <string name="desktop_mode" msgid="2389067840550544462">"ဒက်စ်တော့မုဒ်"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"ဒက်စ်တော့ အရန်စကားဝှက်"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"ဒက်စ်တော့ အရန်သိမ်းဆည်းခြင်းအားလုံးကို လောလောဆယ် ကာကွယ်မထားပါ"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"ဒက်စ်တော့ အပြည့်အဝ အရန်သိမ်းခြင်းအတွက် စကားဝှက်ကို ပြောင်းရန် သို့မဟုတ် ဖယ်ရှားရန် တို့ပါ။"</string> @@ -448,7 +449,7 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (အနီ-အစိမ်း)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (အပြာ-အဝါ)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"အရောင်ပြင်ဆင်မှု"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"အရောင် အမှန်ပြင်ခြင်းသည် အောက်ပါတို့အတွက် အသုံးဝင်နိုင်သည်-<br/> <ol> <li>&nbsp;အရောင်များကို ပိုမိုမှန်ကန်စွာ ကြည့်ရှုခြင်း&lt</li> <li>&nbsp;အာရုံစိုက်နိုင်ရန် အရောင်များ ဖယ်ရှားခြင်း</li> </ol>"</string> + <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"အရောင် အမှန်ပြင်ခြင်းသည် အောက်ပါတို့အတွက် အသုံးဝင်နိုင်သည်-<br/> <ol> <li>&nbsp;အရောင်များကို ပိုမိုမှန်ကန်စွာ ကြည့်ရှုခြင်း</li> <li>&nbsp;အာရုံစိုက်နိုင်ရန် အရောင်များ ဖယ်ရှားခြင်း</li> </ol>"</string> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> မှ ကျော်၍ လုပ်ထားသည်။"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ခန့် ကျန်သည်"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"အားပြည့်ရန် <xliff:g id="TIME">%1$s</xliff:g> လိုသည်"</string> <string name="power_charging_duration" msgid="6127154952524919719">"အားပြည့်ရန် <xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> လိုသည်"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - အားသွင်းခြင်းကို လောလောဆယ် ကန့်သတ်ထားသည်"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"မသိ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"အားသွင်းနေပါသည်"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"အမြန် အားသွင်းနေသည်"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"နှေးကွေးစွာ အားသွင်း"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"ကြိုးမဲ့ အားသွင်းနေသည်"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"အားသွင်းအထိုင်"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"အားသွင်းမနေပါ"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"ချိတ်ဆက်ထားသည်၊ အားသွင်းမနေပါ"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"အားသွင်းပြီးပါပြီ"</string> @@ -591,7 +594,7 @@ <string name="add_guest_failed" msgid="8074548434469843443">"ဧည့်သည်သစ် ပြုလုပ်၍မရပါ"</string> <string name="user_nickname" msgid="262624187455825083">"နာမည်ပြောင်"</string> <string name="user_add_user" msgid="7876449291500212468">"အသုံးပြုသူ ထည့်ရန်"</string> - <string name="guest_new_guest" msgid="3482026122932643557">"ဧည့်သည့် ထည့်ရန်"</string> + <string name="guest_new_guest" msgid="3482026122932643557">"ဧည့်သည် ထည့်ရန်"</string> <string name="guest_exit_guest" msgid="5908239569510734136">"ဧည့်သည်ကို ဖယ်ထုတ်ရန်"</string> <string name="guest_reset_guest" msgid="6110013010356013758">"ဧည့်သည်ကို ပြင်ဆင်သတ်မှတ်ရန်"</string> <string name="guest_reset_guest_dialog_title" msgid="8047270010895437534">"ဧည့်သည်ကို ပြင်ဆင်သတ်မှတ်မလား။"</string> diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml index 7f67bd236efb..6ca0cc3e5d60 100644 --- a/packages/SettingsLib/res/values-nb/strings.xml +++ b/packages/SettingsLib/res/values-nb/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-lyd: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD-lyd"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Høreapparater"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE-lyd"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Koblet til høreapparater"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Koblet til LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Koblet til LE-lyd"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Koblet til medielyd"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Koblet til telefonlyd"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Koblet til tjener for filoverføring"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Gjør at alle aktiviteter kan endre størrelse for flervindusmodus, uavhengig av manifestverdier."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Slå på vinduer i fritt format"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Slå på støtte for vinduer i eksperimentelt fritt format."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Skrivebordmodus"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Passord for sikkerhetskopiering på datamaskin"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Fullstendig sikkerhetskopiering på datamaskin er ikke beskyttet"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Trykk for å endre eller fjerne passordet for fullstendige sikkerhetskopier på datamaskinen"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Fulladet om <xliff:g id="TIME">%1$s</xliff:g>"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – Fulladet om <xliff:g id="TIME">%2$s</xliff:g>"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> – Lading er midlertidig begrenset"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Ukjent"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Lader"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Lader raskt"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Lader sakte"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Lader trådløst"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Ladedokk"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Lader ikke"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Tilkoblet, lader ikke"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Ladet"</string> diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml index 522b5ee5dfe9..88bdba4e5e4d 100644 --- a/packages/SettingsLib/res/values-ne/strings.xml +++ b/packages/SettingsLib/res/values-ne/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD अडियो: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD अडियो"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"श्रवण यन्त्रहरू"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE अडियो"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"श्रवण यन्त्रहरूमा जडान गरियो"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"LE_AUDIO मा कनेक्ट गरिएको छ"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE अडियोमा कनेक्ट गरिएको छ"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"मिडिया अडियोसँग जडित"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"फोन अडियोमा जडान गरियो"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"फाइल ट्रान्सफर सर्भरमा जडान गरियो"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"तोकिएको नियमको ख्याल नगरी एपलाई एकभन्दा बढी विन्डोमा रिसाइज गर्न सकिने बनाइयोस्।"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"फ्रिफर्म विन्डोहरू अन गरियोस्"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"प्रयोगात्मक फ्रिफर्म विन्डोहरू चल्ने बनाइयोस्"</string> + <string name="desktop_mode" msgid="2389067840550544462">"डेस्कटप मोड"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"डेस्कटप ब्याकअप पासवर्ड"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"हाल डेस्कटपका सबै ब्याकअप पासवर्ड सुरक्षित छैनन्"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"डेस्कटप पूर्ण ब्याकअपको लागि पासवर्ड बदल्न वा हटाउन ट्याप गर्नुहोस्"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"पूरा चार्ज हुन <xliff:g id="TIME">%1$s</xliff:g> लाग्ने छ"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - पूरा चार्ज हुन <xliff:g id="TIME">%2$s</xliff:g> लाग्ने छ"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्जिङ केही समयका लागि सीमित पारिएको छ"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"अज्ञात"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"चार्ज हुँदै छ"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"द्रुत गतिमा चार्ज गरिँदै छ"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"ढिलो चार्ज हुँदै छ"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"वायरलेस तरिकाले चार्ज गरिँदै छ"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"डक चार्ज हुँदै छ"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"चार्ज भइरहेको छैन"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"कनेक्ट गरिएको छ, चार्ज भइरहेको छैन"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"चार्ज भयो"</string> diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml index 9a90a7f994f3..118363005f1d 100644 --- a/packages/SettingsLib/res/values-nl/strings.xml +++ b/packages/SettingsLib/res/values-nl/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD-audio"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Hoortoestellen"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"Le Audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Verbonden met hoortoestellen"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Verbonden met LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Verbonden met LE Audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Verbonden met audio van medium"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Verbonden met audio van telefoon"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Verbonden met server voor bestandsoverdracht"</string> @@ -184,7 +184,7 @@ <string name="launch_defaults_some" msgid="3631650616557252926">"Enkele standaardwaarden ingesteld"</string> <string name="launch_defaults_none" msgid="8049374306261262709">"Geen standaardwaarden ingesteld"</string> <string name="tts_settings" msgid="8130616705989351312">"Instellingen tekst-naar-spraak"</string> - <string name="tts_settings_title" msgid="7602210956640483039">"Spraakuitvoer"</string> + <string name="tts_settings_title" msgid="7602210956640483039">"Tekst-naar-spraakuitvoer"</string> <string name="tts_default_rate_title" msgid="3964187817364304022">"Spreeksnelheid"</string> <string name="tts_default_rate_summary" msgid="3781937042151716987">"Snelheid waarmee de tekst wordt gesproken"</string> <string name="tts_default_pitch_title" msgid="6988592215554485479">"Toonhoogte"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Maak het formaat van alle activiteiten aanpasbaar, ongeacht de manifestwaarden"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Vensters met vrije vorm aanzetten"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Zet ondersteuning voor vensters met experimentele vrije vorm aan"</string> + <string name="desktop_mode" msgid="2389067840550544462">"Desktopmodus"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Wachtwoord desktopback-up"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Volledige back-ups naar desktops zijn momenteel niet beveiligd"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Tik om het wachtwoord voor volledige back-ups naar desktops te wijzigen of te verwijderen"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Vol over <xliff:g id="TIME">%1$s</xliff:g>"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - vol over <xliff:g id="TIME">%2$s</xliff:g>"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - Opladen tijdelijk beperkt"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Onbekend"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Opladen"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Snel opladen"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Langzaam opladen"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Draadloos opladen"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Oplaaddock"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Wordt niet opgeladen"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Verbonden, wordt niet opgeladen"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Opgeladen"</string> @@ -600,7 +603,7 @@ <string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Verwijderen"</string> <string name="guest_resetting" msgid="7822120170191509566">"Gast resetten…"</string> <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Gastsessie resetten?"</string> - <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Hierdoor wordt een nieuwe gastsessie gestart en worden alle apps en gegevens van de huidige sessie verwijderd"</string> + <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Hiermee start een nieuwe gastsessie en worden alle apps en gegevens van de huidige sessie verwijderd"</string> <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Gastmodus sluiten?"</string> <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Hierdoor worden apps en gegevens van de huidige gastsessie verwijderd"</string> <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Sluiten"</string> diff --git a/packages/SettingsLib/res/values-or/arrays.xml b/packages/SettingsLib/res/values-or/arrays.xml index d42aeaff755d..a6c40b07941c 100644 --- a/packages/SettingsLib/res/values-or/arrays.xml +++ b/packages/SettingsLib/res/values-or/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"ସିଷ୍ଟମ୍ ଚୟନ ବ୍ୟବହାର କରନ୍ତୁ (ଡିଫଲ୍ଟ)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ଅଡିଓ"</item> + <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ଅଡିଓ"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"ସିଷ୍ଟମ୍ର ଚୟନ (ଡିଫଲ୍ଟ୍) ବ୍ୟବହାର କରନ୍ତୁ"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ଅଡିଓ"</item> + <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ଅଡିଓ"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"ସିଷ୍ଟମ୍ର ଚୟନ (ଡିଫଲ୍ଟ୍) ବ୍ୟବହାର କରନ୍ତୁ"</item> <item msgid="8003118270854840095">"44.1 kHz"</item> diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml index 65813a7977d5..a949dc933a60 100644 --- a/packages/SettingsLib/res/values-or/strings.xml +++ b/packages/SettingsLib/res/values-or/strings.xml @@ -116,18 +116,18 @@ <string name="bluetooth_profile_headset" msgid="5395952236133499331">"ଫୋନ୍ କଲ୍ଗୁଡ଼ିକ"</string> <string name="bluetooth_profile_opp" msgid="6692618568149493430">"ଫାଇଲ୍ ଟ୍ରାନ୍ସଫର୍"</string> <string name="bluetooth_profile_hid" msgid="2969922922664315866">"ଇନ୍ପୁଟ୍ ଡିଭାଇସ୍"</string> - <string name="bluetooth_profile_pan" msgid="1006235139308318188">"ଇଣ୍ଟର୍ନେଟ୍ ଆକ୍ସେସ୍"</string> + <string name="bluetooth_profile_pan" msgid="1006235139308318188">"ଇଣ୍ଟରନେଟ ଆକ୍ସେସ"</string> <string name="bluetooth_profile_pbap" msgid="4262303387989406171">"କଣ୍ଟାକ୍ଟ ଏବଂ କଲ ଇତିହାସ ସେୟାରିଂ"</string> <string name="bluetooth_profile_pbap_summary" msgid="6466456791354759132">"କଣ୍ଟାକ୍ଟ ଏବଂ କଲ ଇତିହାସ ସେୟାରିଂ ପାଇଁ ବ୍ୟବହାର କରନ୍ତୁ"</string> <string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"ଇଣ୍ଟର୍ନେଟ୍ ସଂଯୋଗ ଶେୟାରିଙ୍ଗ"</string> <string name="bluetooth_profile_map" msgid="8907204701162107271">"ଟେକ୍ସଟ୍ ମେସେଜ୍"</string> - <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM ଆକ୍ସେସ୍"</string> + <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM ଆକ୍ସେସ"</string> <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ଅଡିଓ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ଅଡିଓ"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"ଶ୍ରବଣ ଯନ୍ତ୍ର"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE ଅଡିଓ"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"ଶ୍ରବଣ ଯନ୍ତ୍ରକୁ ସଂଯୋଗ ହୋଇଛି"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"LE_AUDIO ସହ ସଂଯୋଗ କରାଯାଇଛି"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE ଅଡିଓ ସହ କନେକ୍ଟ କରାଯାଇଛି"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"ମିଡିଆ ଅଡିଓ ସହ ସଂଯୁକ୍ତ"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ଫୋନ୍ ଅଡିଓ ସହିତ ସଂଯୁକ୍ତ"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"ଫାଇଲ୍ ଟ୍ରାନ୍ସଫର୍ ସର୍ଭର୍ ସହ ସଂଯୁକ୍ତ"</string> @@ -156,7 +156,7 @@ <string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ଦ୍ୱାରା ପେୟାରିଙ୍ଗ ପାଇଁ ପ୍ରତ୍ୟାଖ୍ୟାନ କରିଦିଆଗଲା।"</string> <string name="bluetooth_talkback_computer" msgid="3736623135703893773">"କମ୍ପ୍ୟୁଟର୍"</string> <string name="bluetooth_talkback_headset" msgid="3406852564400882682">"ହେଡ୍ସେଟ୍"</string> - <string name="bluetooth_talkback_phone" msgid="868393783858123880">"ଫୋନ୍"</string> + <string name="bluetooth_talkback_phone" msgid="868393783858123880">"ଫୋନ"</string> <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"ଇମେଜିଙ୍ଗ"</string> <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"ହେଡ୍ଫୋନ୍"</string> <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"ଇନ୍ପୁଟ୍ ଉପକରଣ"</string> @@ -246,7 +246,7 @@ <string name="adb_pair_method_code_summary" msgid="6370414511333685185">"ଛଅ ଡିଜିଟ୍ କୋଡ୍ ବ୍ୟବହାର କରି ନୂଆ ଡିଭାଇସଗୁଡ଼ିକୁ ପେୟାର୍ କରନ୍ତୁ"</string> <string name="adb_paired_devices_title" msgid="5268997341526217362">"ପେୟାର୍ ହୋଇଥିବା ଡିଭାଇସଗୁଡ଼ିକ"</string> <string name="adb_wireless_device_connected_summary" msgid="3039660790249148713">"ବର୍ତ୍ତମାନ ସଂଯୁକ୍ତ ଅଛି"</string> - <string name="adb_wireless_device_details_title" msgid="7129369670526565786">"ଡିଭାଇସ୍ ବିବରଣୀ"</string> + <string name="adb_wireless_device_details_title" msgid="7129369670526565786">"ଡିଭାଇସର ବିବରଣୀ"</string> <string name="adb_device_forget" msgid="193072400783068417">"ଭୁଲିଯାଆନ୍ତୁ"</string> <string name="adb_device_fingerprint_title_format" msgid="291504822917843701">"ଡିଭାଇସ୍ ଫିଙ୍ଗରପ୍ରିଣ୍ଟ: <xliff:g id="FINGERPRINT_PARAM">%1$s</xliff:g>"</string> <string name="adb_wireless_connection_failed_title" msgid="664211177427438438">"ସଂଯୋଗ ବିଫଳ ହେଲା"</string> @@ -394,7 +394,7 @@ <string name="transition_animation_scale_title" msgid="1278477690695439337">"ଟ୍ରାଞ୍ଜିସନ୍ ଆନିମେସନ୍ ସ୍କେଲ୍"</string> <string name="animator_duration_scale_title" msgid="7082913931326085176">"ଆନିମେଟର୍ ଅବଧି ସ୍କେଲ୍"</string> <string name="overlay_display_devices_title" msgid="5411894622334469607">"ସେକେଣ୍ଡାରୀ ଡିସ୍ପ୍ଲେ ସିମୁଲେଟ୍ କରନ୍ତୁ"</string> - <string name="debug_applications_category" msgid="5394089406638954196">"ଆପ୍ଗୁଡ଼ିକ"</string> + <string name="debug_applications_category" msgid="5394089406638954196">"ଆପ୍ସ"</string> <string name="immediately_destroy_activities" msgid="1826287490705167403">"କାର୍ଯ୍ୟକଳାପଗୁଡ଼ିକୁ ରଖନ୍ତୁ ନାହିଁ"</string> <string name="immediately_destroy_activities_summary" msgid="6289590341144557614">"ୟୁଜର୍ ଏହାକୁ ଛାଡ଼ିବା କ୍ଷଣି ସମସ୍ତ କାର୍ଯ୍ୟକଳାପ ନଷ୍ଟ କରିଦିଅନ୍ତୁ"</string> <string name="app_process_limit_title" msgid="8361367869453043007">"ବ୍ୟାକ୍ଗ୍ରାଉଣ୍ଡ ପ୍ରୋସେସ୍ ସୀମା"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"ୱିଣ୍ଡୋ ହିସାବରେ କାର୍ଯ୍ୟକଳାପଗୁଡ଼ିକୁ ବଦଳାନ୍ତୁ, ସେଗୁଡ଼ିକର ମାନିଫେଷ୍ଟ ଭାଲ୍ୟୁ ଯାହା ହୋଇଥାଉ ନା କାହିଁକି"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"ଫ୍ରୀଫର୍ମ ୱିଣ୍ଡୋ ସକ୍ଷମ କରନ୍ତୁ"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"ପରୀକ୍ଷାମୂଳକ ଫ୍ରୀଫର୍ମ ୱିଣ୍ଡୋସ୍ ପାଇଁ ସପୋର୍ଟ ସକ୍ଷମ କରନ୍ତୁ।"</string> + <string name="desktop_mode" msgid="2389067840550544462">"ଡେସ୍କଟପ ମୋଡ"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"ଡେସ୍କଟପ୍ ବ୍ୟାକଅପ୍ ପାସ୍ୱର୍ଡ"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"ଡେସ୍କଟପ୍ର ସମ୍ପୂର୍ଣ୍ଣ ବ୍ୟାକଅପ୍ଗୁଡ଼ିକ ବର୍ତ୍ତମାନ ସୁରକ୍ଷିତ ନୁହେଁ"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"ଡେସ୍କଟପ୍ର ସମ୍ପୂର୍ଣ୍ଣ ବ୍ୟାକ୍ଅପ୍ ପାଇଁ ପାସ୍ୱର୍ଡ ବଦଳାଇବା କିମ୍ୱା କାଢ଼ିଦେବା ନିମନ୍ତେ ଟାପ୍ କରନ୍ତୁ"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"ପୂର୍ଣ୍ଣ ହେବାକୁ ଆଉ <xliff:g id="TIME">%1$s</xliff:g> ବାକି ଅଛି"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - ପୂର୍ଣ୍ଣ ହେବାକୁ ଆଉ <xliff:g id="TIME">%2$s</xliff:g> ବାକି ଅଛି"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - ଚାର୍ଜିଂ ଅସ୍ଥାୟୀ ଭାବେ ସୀମିତ କରାଯାଇଛି"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"ଅଜ୍ଞାତ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ଚାର୍ଜ ହେଉଛି"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ଶୀଘ୍ର ଚାର୍ଜ ହେଉଛି"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"ଧୀରେ ଚାର୍ଜ ହେଉଛି"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"ୱେୟରଲେସ ଭାବେ ଚାର୍ଜିଂ"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"ଡକ ଚାର୍ଜ ହେଉଛି"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ଚାର୍ଜ ହେଉନାହିଁ"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"ସଂଯୋଗ କରାଯାଇଛି, ଚାର୍ଜ ହେଉନାହିଁ"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"ଚାର୍ଜ ହୋଇଯାଇଛି"</string> @@ -551,7 +554,7 @@ <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"ସଂଯୋଗ କରିବାରେ ସମସ୍ୟା ହେଉଛି। ଡିଭାଇସ୍ ବନ୍ଦ କରି ପୁଣି ଚାଲୁ କରନ୍ତୁ"</string> <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"ତାରଯୁକ୍ତ ଅଡିଓ ଡିଭାଇସ୍"</string> <string name="help_label" msgid="3528360748637781274">"ସାହାଯ୍ୟ ଓ ମତାମତ"</string> - <string name="storage_category" msgid="2287342585424631813">"ଷ୍ଟୋରେଜ୍"</string> + <string name="storage_category" msgid="2287342585424631813">"ଷ୍ଟୋରେଜ"</string> <string name="shared_data_title" msgid="1017034836800864953">"ସେୟାର୍ କରାଯାଇଥିବା ଡାଟା"</string> <string name="shared_data_summary" msgid="5516326713822885652">"ସେୟାର୍ କରାଯାଇଥିବା ଡାଟା ଦେଖନ୍ତୁ ଏବଂ ଏହାକୁ ପରିବର୍ତ୍ତନ କରନ୍ତୁ"</string> <string name="shared_data_no_blobs_text" msgid="3108114670341737434">"ଏହି ଉପଯୋଗକର୍ତ୍ତାଙ୍କ ପାଇଁ କୌଣସି ସେୟାର୍ କରାଯାଇଥିବା ଡାଟା ନାହିଁ।"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"ବାୟୁର ଗୁଣବତ୍ତା"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"କାଷ୍ଟ ସୂଚନା"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ହୋମ କଣ୍ଟ୍ରୋଲ"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"ସ୍ମାର୍ଟସ୍ପେସ"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"ଏକ ପ୍ରୋଫାଇଲ ଛବି ବାଛନ୍ତୁ"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"ଡିଫଲ୍ଟ ଉପଯୋଗକର୍ତ୍ତା ଆଇକନ"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"ଫିଜିକାଲ କୀବୋର୍ଡ"</string> diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml index e111f8a82578..4d16cecca95c 100644 --- a/packages/SettingsLib/res/values-pa/strings.xml +++ b/packages/SettingsLib/res/values-pa/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ਆਡੀਓ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ਆਡੀਓ"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"ਸੁਣਨ ਦੇ ਸਾਧਨ"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE ਆਡੀਓ"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"ਸੁਣਨ ਦੇ ਸਾਧਨਾਂ ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"LE_AUDIO ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE ਆਡੀਓ ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"ਮੀਡੀਆ ਆਡੀਓ ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ਫ਼ੋਨ ਔਡੀਓ ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"ਫਾਈਲ ਟ੍ਰਾਂਸਫ਼ਰ ਸਰਵਰ ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ"</string> @@ -337,7 +337,7 @@ <string name="dev_settings_warning_message" msgid="37741686486073668">"ਇਹ ਸੈਟਿੰਗਾਂ ਕੇਵਲ ਵਿਕਾਸਕਾਰ ਦੀ ਵਰਤੋਂ ਲਈ ਹਨ। ਇਹ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਅਤੇ ਇਸਤੇ ਮੌਜੂਦ ਐਪਲੀਕੇਸ਼ਨ ਨੂੰ ਬ੍ਰੇਕ ਕਰਨ ਜਾਂ ਦੁਰਵਿਵਹਾਰ ਕਰਨ ਦਾ ਕਾਰਨ ਬਣ ਸਕਦੇ ਹਨ।"</string> <string name="verify_apps_over_usb_title" msgid="6031809675604442636">"USB \'ਤੇ ਐਪਾਂ ਦੀ ਪੁਸ਼ਟੀ ਕਰੋ"</string> <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"ADB/ADT ਰਾਹੀਂ ਸਥਾਪਤ ਕੀਤੀਆਂ ਐਪਾਂ ਦੀ ਹਾਨੀਕਾਰਕ ਵਿਵਹਾਰ ਲਈ ਜਾਂਚ ਕਰੋ।"</string> - <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"ਅਨਾਮ ਬਲੂਟੁੱਥ ਡੀਵਾਈਸਾਂ ਦਿਖਾਈਆਂ ਜਾਣਗੀਆਂ (ਸਿਰਫ਼ MAC ਪਤੇ)"</string> + <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"ਅਨਾਮ ਬਲੂਟੁੱਥ ਡੀਵਾਈਸ ਦਿਖਾਏ ਜਾਣਗੇ (ਸਿਰਫ਼ MAC ਪਤੇ)"</string> <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"ਰਿਮੋਟ ਡੀਵਾਈਸਾਂ ਨਾਲ ਅਵਾਜ਼ੀ ਸਮੱਸਿਆਵਾਂ ਜਿਵੇਂ ਕਿ ਨਾ ਪਸੰਦ ਕੀਤੀ ਜਾਣ ਵਾਲੀ ਉੱਚੀ ਅਵਾਜ਼ ਜਾਂ ਕੰਟਰੋਲ ਦੀ ਕਮੀ ਵਰਗੀ ਹਾਲਤ ਵਿੱਚ ਬਲੂਟੁੱਥ ਪੂਰਨ ਅਵਾਜ਼ ਵਿਸ਼ੇਸ਼ਤਾ ਨੂੰ ਬੰਦ ਕਰਦਾ ਹੈ।"</string> <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"ਬਲੂਟੁੱਥ Gabeldorsche ਵਿਸ਼ੇਸ਼ਤਾ ਸਟੈਕ ਨੂੰ ਚਾਲੂ ਕਰਦਾ ਹੈ।"</string> <string name="enhanced_connectivity_summary" msgid="1576414159820676330">"ਵਿਸਤ੍ਰਿਤ ਕਨੈਕਟੀਵਿਟੀ ਵਿਸ਼ੇਸ਼ਤਾ ਨੂੰ ਚਾਲੂ ਕਰਦਾ ਹੈ।"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"ਮੈਨੀਫ਼ੈਸਟ ਮੁੱਲਾਂ ਦੀ ਪਰਵਾਹ ਕੀਤੇ ਬਿਨਾਂ, ਮਲਟੀ-ਵਿੰਡੋ ਲਈ ਸਾਰੀਆਂ ਸਰਗਰਮੀਆਂ ਨੂੰ ਆਕਾਰ ਬਦਲਣਯੋਗ ਬਣਾਓ।"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"ਫ੍ਰੀਫਾਰਮ ਵਿੰਡੋਜ਼ ਨੂੰ ਚਾਲੂ ਕਰੋ"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"ਪ੍ਰਯੋਗਮਈ ਫ੍ਰੀਫਾਰਮ ਵਿੰਡੋਜ਼ ਲਈ ਸਮਰਥਨ ਨੂੰ ਚਾਲੂ ਕਰੋ।"</string> + <string name="desktop_mode" msgid="2389067840550544462">"ਡੈਸਕਟਾਪ ਮੋਡ"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"ਡੈਸਕਟਾਪ ਬੈਕਅੱਪ ਪਾਸਵਰਡ"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"ਡੈਸਕਟਾਪ ਦੇ ਪੂਰੇ ਬੈਕਅੱਪ ਇਸ ਵੇਲੇ ਸੁਰੱਖਿਅਤ ਨਹੀਂ ਹਨ"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"ਡੈਸਕਟਾਪ ਦੇ ਮੁਕੰਮਲ ਬੈਕਅੱਪਾਂ ਲਈ ਪਾਸਵਰਡ ਨੂੰ ਬਦਲਣ ਜਾਂ ਹਟਾਉਣ ਲਈ ਟੈਪ ਕਰੋ"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"ਬੈਟਰੀ ਪੂਰੀ ਚਾਰਜ ਹੋਣ ਵਿੱਚ <xliff:g id="TIME">%1$s</xliff:g> ਬਾਕੀ"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - ਬੈਟਰੀ ਪੂਰੀ ਚਾਰਜ ਹੋਣ ਵਿੱਚ <xliff:g id="TIME">%2$s</xliff:g> ਬਾਕੀ"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - ਚਾਰਜਿੰਗ ਕੁਝ ਸਮੇਂ ਲਈ ਰੋਕੀ ਗਈ"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"ਅਗਿਆਤ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ਚਾਰਜ ਹੋ ਰਹੀ ਹੈ"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ਤੇਜ਼ ਚਾਰਜ ਹੋ ਰਹੀ ਹੈ"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"ਹੌਲੀ ਚਾਰਜ ਹੋ ਰਹੀ ਹੈ"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"ਬਿਨਾਂ ਤਾਰ ਤੋਂ ਚਾਰਜ ਹੋ ਰਹੀ ਹੈ"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"ਡੌਕ ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ਚਾਰਜ ਨਹੀਂ ਹੋ ਰਿਹਾ"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"ਕਨੈਕਟ ਹੈ, ਚਾਰਜ ਨਹੀਂ ਹੋ ਰਹੀ"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"ਚਾਰਜ ਹੋ ਗਈ"</string> diff --git a/packages/SettingsLib/res/values-pl/arrays.xml b/packages/SettingsLib/res/values-pl/arrays.xml index f0453d1e7df1..71ecd4642742 100644 --- a/packages/SettingsLib/res/values-pl/arrays.xml +++ b/packages/SettingsLib/res/values-pl/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"Używaj wyboru systemu (domyślnie)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="2908219194098827570">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"Używaj wyboru systemu (domyślnie)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="3517061573669307965">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"Używaj wyboru systemu (domyślnie)"</item> <item msgid="8003118270854840095">"44,1 kHz"</item> diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml index 64d7af0d9f03..cd8b6fb56e0b 100644 --- a/packages/SettingsLib/res/values-pl/strings.xml +++ b/packages/SettingsLib/res/values-pl/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Dźwięk HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Dźwięk HD"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Aparaty słuchowe"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE Audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Połączono z aparatami słuchowymi"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Połączono z LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Połączono z LE Audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Połączono z funkcją audio multimediów"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Połączono z funkcją audio telefonu"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Połączono z serwerem transferu plików"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Zezwalaj na zmianę rozmiaru wszystkich okien aktywności w trybie wielu okien niezależnie od ustawień w pliku manifestu"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Włącz dowolny rozmiar okien"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Włącz obsługę eksperymentalnej funkcji dowolnego rozmiaru okien"</string> + <string name="desktop_mode" msgid="2389067840550544462">"Tryb pulpitu"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Hasło kopii zapasowej"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Pełne kopie zapasowe na komputerze nie są obecnie chronione"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Dotknij, by zmienić lub usunąć hasło pełnych kopii zapasowych na komputerze."</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> do pełnego naładowania"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do pełnego naładowania"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - Ładowanie tymczasowo ograniczone"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Nieznane"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Ładowanie"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Szybkie ładowanie"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Wolne ładowanie"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Ładowanie bezprzewodowe"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Ładowanie na stacji dokującej"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nie podłączony"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Podłączono, brak ładowania"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Naładowana"</string> @@ -603,7 +606,7 @@ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Zostanie uruchomiona nowa sesja gościa. Wszystkie aplikacje i dane z obecnej sesji zostaną usunięte."</string> <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Zamknąć tryb gościa?"</string> <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Wszystkie aplikacje i dane z obecnej sesji gościa zostaną usunięte."</string> - <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Wyjdź"</string> + <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Zamknij"</string> <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Zapisać aktywność gościa?"</string> <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Możesz zapisać aktywność z obecnej sesji lub usunąć wszystkie aplikacje i dane"</string> <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Usuń"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Jakość powietrza"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Obsada"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Sterowanie domem"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"Wybierz zdjęcie profilowe"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"Ikona domyślnego użytkownika"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"Klawiatura fizyczna"</string> diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml index 8c1e83d046db..4e1b1dda6099 100644 --- a/packages/SettingsLib/res/values-pt-rBR/strings.xml +++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Áudio HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Áudio HD"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Aparelhos auditivos"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"Áudio de baixa energia"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Conectado a aparelhos auditivos"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Conectado a LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Conectado ao perfil Áudio de baixa energia"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Conectado ao áudio da mídia"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Conectado ao áudio do smartphone"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Conectado ao servidor de transferência de arquivo"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Tornar todas as atividades redimensionáveis para várias janelas, independentemente dos valores do manifesto."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Ativar janelas de forma livre"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Ativar a compatibilidade com janelas experimentais de forma livre."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Modo área de trabalho"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Senha de backup local"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Os backups completos não estão protegidos no momento"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Toque para alterar ou remover a senha de backups completos do desktop"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> até a conclusão"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> até a conclusão"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> (carregamento temporariamente limitado)"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconhecido"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Carregando"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Carregando rápido"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Carregando devagar"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Carregando sem fio"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Base de carregamento"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Não está carregando"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Conectado sem carregar"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Carregada"</string> diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml index 63ce593d7c77..9215ce38a21f 100644 --- a/packages/SettingsLib/res/values-pt-rPT/strings.xml +++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Áudio HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Áudio HD"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Aparelhos auditivos"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE Audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Ligado a aparelhos auditivos"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Ligado a LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Ligado a LE Audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Ligado ao áudio de multimédia"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Ligado ao áudio do telefone"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Ligado ao servidor de transferência de ficheiros"</string> @@ -190,7 +190,7 @@ <string name="tts_default_pitch_title" msgid="6988592215554485479">"Tonalidade"</string> <string name="tts_default_pitch_summary" msgid="9132719475281551884">"Afeta o tom da voz sintetizada"</string> <string name="tts_default_lang_title" msgid="4698933575028098940">"Idioma"</string> - <string name="tts_lang_use_system" msgid="6312945299804012406">"Utilizar idioma do sistema"</string> + <string name="tts_lang_use_system" msgid="6312945299804012406">"Usar idioma do sistema"</string> <string name="tts_lang_not_selected" msgid="7927823081096056147">"Idioma não selecionado"</string> <string name="tts_default_lang_summary" msgid="9042620014800063470">"Define a voz do idioma específico para o texto lido"</string> <string name="tts_play_example_title" msgid="1599468547216481684">"Ouvir um exemplo"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Tornar todas as atividades redimensionáveis para várias janelas, independentemente dos valores do manifesto."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Ativar janelas de forma livre"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Ativar a compatibilidade com janelas de forma livre experimentais."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Ambiente de trabalho"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Palavra-passe cópia do computador"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"As cópias de segurança completas no ambiente de trabalho não estão atualmente protegidas"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Tocar para alterar ou remover a palavra-passe para cópias de segurança completas no ambiente de trabalho"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> até à carga máxima"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> até à carga máxima"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> – Carregamento limitado temporariamente"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconhecido"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"A carregar"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Carregamento rápido"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Carregamento lento"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"A carregar sem fios"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Est. ancor. carreg."</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Não está a carregar"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Ligado, não está a carregar"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Carregada"</string> diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml index 8c1e83d046db..4e1b1dda6099 100644 --- a/packages/SettingsLib/res/values-pt/strings.xml +++ b/packages/SettingsLib/res/values-pt/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Áudio HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Áudio HD"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Aparelhos auditivos"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"Áudio de baixa energia"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Conectado a aparelhos auditivos"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Conectado a LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Conectado ao perfil Áudio de baixa energia"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Conectado ao áudio da mídia"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Conectado ao áudio do smartphone"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Conectado ao servidor de transferência de arquivo"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Tornar todas as atividades redimensionáveis para várias janelas, independentemente dos valores do manifesto."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Ativar janelas de forma livre"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Ativar a compatibilidade com janelas experimentais de forma livre."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Modo área de trabalho"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Senha de backup local"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Os backups completos não estão protegidos no momento"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Toque para alterar ou remover a senha de backups completos do desktop"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> até a conclusão"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> até a conclusão"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> (carregamento temporariamente limitado)"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconhecido"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Carregando"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Carregando rápido"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Carregando devagar"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Carregando sem fio"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Base de carregamento"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Não está carregando"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Conectado sem carregar"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Carregada"</string> diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml index 4702b6a72179..3c8119993a0d 100644 --- a/packages/SettingsLib/res/values-ro/strings.xml +++ b/packages/SettingsLib/res/values-ro/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Audio HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Audio HD"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Aparate auditive"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Conectat la aparatul auditiv"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Conectat la LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Conectat la LE audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Conectat la profilul pentru conținut media audio"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Conectat la componenta audio a telefonului"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Conectat la serverul de transfer de fișiere"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Permiteți redimensionarea tuturor activităților pentru modul cu ferestre multiple, indiferent de valorile manifestului."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Activați ferestrele cu formă liberă"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Activați compatibilitatea pentru ferestrele experimentale cu formă liberă."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Modul desktop"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Parolă backup computer"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"În prezent, backupurile complete pe computer nu sunt protejate"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Atingeți ca să modificați sau să eliminați parola pentru backupurile complete pe desktop"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> până la finalizare"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> până la finalizare"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> – Încărcare limitată temporar"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Necunoscut"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Se încarcă"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Se încarcă rapid"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Se încarcă lent"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Se încarcă wireless"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Suport de încărcare"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nu se încarcă"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Conectat, nu se încarcă"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Încărcată"</string> diff --git a/packages/SettingsLib/res/values-ru/arrays.xml b/packages/SettingsLib/res/values-ru/arrays.xml index b1211a5c376c..4b6e69288af2 100644 --- a/packages/SettingsLib/res/values-ru/arrays.xml +++ b/packages/SettingsLib/res/values-ru/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"Выбор системы (по умолчанию)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"Аудиокодек: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="2908219194098827570">"Аудиокодек: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"Выбор системы (по умолчанию)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"Аудиокодек: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="3517061573669307965">"Аудиокодек: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"Выбор системы (по умолчанию)"</item> <item msgid="8003118270854840095">"44,1 кГц"</item> diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml index 2b6250b887f9..c27b55eb695c 100644 --- a/packages/SettingsLib/res/values-ru/strings.xml +++ b/packages/SettingsLib/res/values-ru/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD Audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD Audio"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Слуховые аппараты"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE Audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Слуховой аппарат подключен"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Подключено к LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Подключено к LE Audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Подключено к мультимедийному аудиоустройству"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Подключено к аудиоустройству телефона"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Установлено подключение к серверу передачи файлов"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Разрешить изменение размера окон в многооконном режиме (независимо от значений в манифесте)"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Разрешить создание окон произвольной формы"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Включить экспериментальную функцию создания окон произвольной формы"</string> + <string name="desktop_mode" msgid="2389067840550544462">"Режим компьютера"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Пароль для резервного копирования"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Полные локальные резервные копии в настоящее время не защищены"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Нажмите, чтобы изменить или удалить пароль для резервного копирования"</string> @@ -448,7 +449,7 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномалия (красный/зеленый)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомалия (синий/желтый)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Коррекция цвета"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Коррекция цвета поможет вам:<br/> <ol> <li>&nbsp;Добиться нужной цветопередачи.</li> <li>&nbsp;Включить черно-белый режим, чтобы меньше отвлекаться.</li> </ol>"</string> + <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Используйте коррекцию цвета, чтобы:<br/> <ol> <li> Добиться нужной цветопередачи.</li> <li> Убрать цвета, которые мешают сосредоточиться.</li> </ol>"</string> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Новая настройка: <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"Уровень заряда – <xliff:g id="PERCENTAGE">%1$s</xliff:g>. <xliff:g id="TIME_STRING">%2$s</xliff:g>."</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Заряда хватит примерно на <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> до полной зарядки"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до полной зарядки"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> – зарядка временно ограничена"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Неизвестно"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Идет зарядка"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Быстрая зарядка"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Медленная зарядка"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Беспроводная зарядка"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Док-станция: зарядка"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не заряжается"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Подключено, не заряжается"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Батарея заряжена"</string> @@ -586,7 +589,7 @@ <string name="user_set_lock_button" msgid="1427128184982594856">"Включить блокировку"</string> <string name="user_switch_to_user" msgid="6975428297154968543">"Сменить пользователя на <xliff:g id="USER_NAME">%s</xliff:g>"</string> <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Создаем нового пользователя…"</string> - <string name="creating_new_guest_dialog_message" msgid="1114905602181350690">"Создание гостя…"</string> + <string name="creating_new_guest_dialog_message" msgid="1114905602181350690">"Создание профиля…"</string> <string name="add_user_failed" msgid="4809887794313944872">"Не удалось создать пользователя"</string> <string name="add_guest_failed" msgid="8074548434469843443">"Не удалось создать гостя."</string> <string name="user_nickname" msgid="262624187455825083">"Псевдоним"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Качество воздуха"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Данные о трансляции"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Автоматизация дома"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"SmartSpace"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"Выберите фото профиля"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"Значок пользователя по умолчанию"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"Физическая клавиатура"</string> diff --git a/packages/SettingsLib/res/values-si/arrays.xml b/packages/SettingsLib/res/values-si/arrays.xml index 8386c1aa5559..eaacfb835de5 100644 --- a/packages/SettingsLib/res/values-si/arrays.xml +++ b/packages/SettingsLib/res/values-si/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"පද්ධති තේරීම භාවිත කරන්න (පෙරනිමි)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ශ්රව්යය"</item> + <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ශ්රව්යය"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"පද්ධති තේරීම භාවිත කරන්න (පෙරනිමි)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ශ්රව්යය"</item> + <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ශ්රව්යය"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"පද්ධති තේරීම භාවිත කරන්න (පෙරනිමි)"</item> <item msgid="8003118270854840095">"44.1 kHz"</item> diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml index 9318c8d7392c..dfc7bf91c0c4 100644 --- a/packages/SettingsLib/res/values-si/strings.xml +++ b/packages/SettingsLib/res/values-si/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ශ්රව්යය: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ශ්රව්යය"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"ශ්රවණාධාරක"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE ශ්රව්ය"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"ශ්රවණාධාරක වෙත සම්බන්ධ කළා"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"LE_AUDIO වෙත සම්බන්ධ විය"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE ශ්රව්ය වෙත සම්බන්ධ විය"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"මාධ්ය ශ්රව්යට සම්බන්ධ විය"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"දුරකතනයේ ශ්රව්යට සම්බන්ධ විය"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"ගොනු හුවමාරු සේවාදායකය සමග සම්බන්ධ විය"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"මැනිෆෙස්ට් අගයන් නොසලකා, සියලු ක්රියාකාරකම් බහු-කවුළුව සඳහා ප්රතිප්රමාණ කළ හැකි බවට පත් කරන්න."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"අනියම් හැඩැති කවුළු සබල කරන්න"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"පරීක්ෂණාත්මක අනියම් හැඩැති කවුළු සඳහා සහාය සබල කරන්න."</string> + <string name="desktop_mode" msgid="2389067840550544462">"ඩෙස්ක්ටොප් ප්රකාරය"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"ඩෙස්ක්ටොප් උපස්ථ මුරපදය"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"ඩෙස්ක්ටොප් සම්පූර්ණ උපස්ථ දැනට ආරක්ෂා කර නොමැත"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"ඩෙස්ක්ටොප් සම්පූර්ණ උපස්ථ සඳහා මුරපදය වෙනස් කිරීමට හෝ ඉවත් කිරීමට තට්ටු කරන්න"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"සම්පූර්ණ වීමට <xliff:g id="TIME">%1$s</xliff:g>ක් ඉතිරියි"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - සම්පූර්ණ වීමට <xliff:g id="TIME">%2$s</xliff:g>ක් ඉතිරියි"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - ආරෝපණය කිරීම තාවකාලිකව සීමා කර ඇත"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"නොදනී"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ආරෝපණය වෙමින්"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ශීඝ්ර ආරෝපණය"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"සෙමින් ආරෝපණය"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"නොරැහැන්ව ආරෝපණය වේ"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"ආරෝපණ ඩොකය"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ආරෝපණය නොවේ"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"සම්බන්ධයි, ආරෝපණය නොවේ"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"අරෝපිතයි"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"වායු ගුණත්වය"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"විකාශ තතු"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"නිවෙස් පාලන"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"ස්මාර්ට් අවකාශය"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"පැතිකඩ පින්තූරයක් තේරීම"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"පෙරනිමි පරිශීලක නිරූපකය"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"භෞතික යතුරු පුවරුව"</string> diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml index 6dee1e38df4d..8f435fb9145f 100644 --- a/packages/SettingsLib/res/values-sk/strings.xml +++ b/packages/SettingsLib/res/values-sk/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD zvuk: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD zvuk"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Načúvadlá"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Pripojené k načúvadlám"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Pripojené k profilu LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Pripojené k systému LE audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Pripojené ku zvukovému médiu"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Pripojené ku zvuku telefónu"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Pripojené na server pre prenos údajov"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Umožniť zmeniť veľkosť všetkých aktivít na niekoľko okien (bez ohľadu na hodnoty manifestu)"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Povoliť okná s voľným tvarom"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Povoliť podporu pre experimentálne okná s voľným tvarom"</string> + <string name="desktop_mode" msgid="2389067840550544462">"Režim počítača"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Heslo pre zálohy v počítači"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Úplné zálohy v počítači nie sú momentálne chránené"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Klepnutím zmeníte alebo odstránite heslo pre úplné zálohy do počítača"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> do úplného nabitia"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do úplného nabitia"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> – nabíjanie je dočasne obmedzené"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Neznáme"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Nabíja sa"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Rýchle nabíjanie"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Pomalé nabíjanie"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Nabíja sa bezdrôtovo"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Nabíjací dok"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nenabíja sa"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Pripojené, nenabíja sa"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Nabité"</string> diff --git a/packages/SettingsLib/res/values-sl/arrays.xml b/packages/SettingsLib/res/values-sl/arrays.xml index 6e33e386d897..b2003e5efbc6 100644 --- a/packages/SettingsLib/res/values-sl/arrays.xml +++ b/packages/SettingsLib/res/values-sl/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"Uporabi sistemsko izbiro (privzeto)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"Zvok <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="2908219194098827570">"Zvok <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"Uporabi sistemsko izbiro (privzeto)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"Zvok <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="3517061573669307965">"Zvok <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"Uporabi sistemsko izbiro (privzeto)"</item> <item msgid="8003118270854840095">"44,1 kHz"</item> diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml index c35ee7a4c142..b3a29063586d 100644 --- a/packages/SettingsLib/res/values-sl/strings.xml +++ b/packages/SettingsLib/res/values-sl/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Zvok visoke kakovosti: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Zvok visoke kakovosti"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Slušni pripomočki"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE Audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Povezava s slušnimi pripomočki je vzpostavljena"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Povezano s profilom LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Povezano s profilom LE Audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Povezan s profilom za predstavnostni zvok"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Povezava s profilom za zvok telefona vzpostavljena"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Povezava s strežnikom za prenos datotek je vzpostavljena"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Poskrbi, da je ne glede na vrednosti v manifestu mogoče vsem aktivnostim spremeniti velikost za način z več okni."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Omogoči okna svobodne oblike"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Omogoči podporo za poskusna okna svobodne oblike."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Namizni način"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Geslo za varnostno kopijo namizja"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Popolne varnostne kopije namizja trenutno niso zaščitene."</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Dotaknite se, če želite spremeniti ali odstraniti geslo za popolno varnostno kopiranje namizja"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Še <xliff:g id="TIME">%1$s</xliff:g> do napolnjenosti"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – še <xliff:g id="TIME">%2$s</xliff:g> do napolnjenosti"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> – Začasno omejeno polnjenje"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Neznano"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Polnjenje"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Hitro polnjenje"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Počasno polnjenje"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Brezžično polnjenje"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Polnjenje na nosilcu"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Se ne polni"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Povezano, se ne polni"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Napolnjeno"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Kakovost zraka"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"O zasedbi"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Nadzor doma"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Hitri pregled"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"Izbira profilne slike"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"Privzeta ikona uporabnika"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"Fizična tipkovnica"</string> diff --git a/packages/SettingsLib/res/values-sq/arrays.xml b/packages/SettingsLib/res/values-sq/arrays.xml index 8a6d853e7818..ed8638016c34 100644 --- a/packages/SettingsLib/res/values-sq/arrays.xml +++ b/packages/SettingsLib/res/values-sq/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"Përdor përzgjedhjen e sistemit (e parazgjedhur)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"Audioja e <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="2908219194098827570">"Audioja e <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"Përdor përzgjedhjen e sistemit (e parazgjedhur)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"Audioja e <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="3517061573669307965">"Audioja e <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"Përdor përzgjedhjen e sistemit (e parazgjedhur)"</item> <item msgid="8003118270854840095">"44,1 kHz"</item> diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml index a92ef7c2873b..83d95a32d76a 100644 --- a/packages/SettingsLib/res/values-sq/strings.xml +++ b/packages/SettingsLib/res/values-sq/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Audio HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Audio HD"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Aparatet e dëgjimit"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"Audioja LE"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Lidhur me aparatet e dëgjimit"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Lidhur me LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"U lidh me audion LE"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"U lidh me audion e medias"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"U lidh me audion e telefonit"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"U lidh me serverin e transferimit të skedarëve"</string> @@ -183,8 +183,8 @@ <string name="running_process_item_user_label" msgid="3988506293099805796">"Përdoruesi: <xliff:g id="USER_NAME">%1$s</xliff:g>"</string> <string name="launch_defaults_some" msgid="3631650616557252926">"Disa caktime me parazgjedhje"</string> <string name="launch_defaults_none" msgid="8049374306261262709">"Nuk janë caktuar parazgjedhje"</string> - <string name="tts_settings" msgid="8130616705989351312">"Cilësimet \"tekst-në-ligjërim\""</string> - <string name="tts_settings_title" msgid="7602210956640483039">"Dalja \"tekst-në-ligjërim\""</string> + <string name="tts_settings" msgid="8130616705989351312">"Cilësimet \"Tekst në ligjërim\""</string> + <string name="tts_settings_title" msgid="7602210956640483039">"Dalja \"Tekst në ligjërim\""</string> <string name="tts_default_rate_title" msgid="3964187817364304022">"Shpejtësia e të folurit"</string> <string name="tts_default_rate_summary" msgid="3781937042151716987">"Shpejtësia me të cilën thuhet teksti"</string> <string name="tts_default_pitch_title" msgid="6988592215554485479">"Tonaliteti"</string> @@ -198,7 +198,7 @@ <string name="tts_install_data_title" msgid="1829942496472751703">"Instalo të dhënat e zërit"</string> <string name="tts_install_data_summary" msgid="3608874324992243851">"Instalo të dhënat e zërit që kërkohen për sintezën e të folurit"</string> <string name="tts_engine_security_warning" msgid="3372432853837988146">"Ky motor i sintezës së të folurit mund të mbledhë të gjithë tekstin që do të flitet, duke përfshirë të dhëna personale si fjalëkalime dhe numra kartash krediti. Ai vjen nga motori <xliff:g id="TTS_PLUGIN_ENGINE_NAME">%s</xliff:g>. Të aktivizohet përdorimi i këtij motori të sintezës së të folurit?"</string> - <string name="tts_engine_network_required" msgid="8722087649733906851">"Kjo gjuhë kërkon një lidhje funksionale interneti për daljen \"tekst-në-ligjërim\"."</string> + <string name="tts_engine_network_required" msgid="8722087649733906851">"Kjo gjuhë kërkon një lidhje funksionale interneti për daljen \"Tekst në ligjërim\"."</string> <string name="tts_default_sample_string" msgid="6388016028292967973">"Ky është një shembull i sintezës së të folurit"</string> <string name="tts_status_title" msgid="8190784181389278640">"Statusi i gjuhës së parazgjedhur"</string> <string name="tts_status_ok" msgid="8583076006537547379">"<xliff:g id="LOCALE">%1$s</xliff:g> mbështetet plotësisht"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Bëj që të gjitha aktivitetet të kenë madhësi të ndryshueshme për përdorimin me shumë dritare, pavarësisht vlerave të manifestit."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Aktivizo dritaret me formë të lirë"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Aktivizo mbështetjen për dritaret eksperimentale me formë të lirë."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Modaliteti i desktopit"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Fjalëkalimi rezervë i kompjuterit"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Rezervimet e plota në kompjuter nuk janë të mbrojtura aktualisht"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Trokit për të ndryshuar ose hequr fjalëkalimin për rezervime të plota të desktopit"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> derisa të mbushet"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> derisa të mbushet"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - Karikimi përkohësisht i kufizuar"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"I panjohur"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Po karikohet"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Karikim i shpejtë"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Po karikohet ngadalë"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Po karikohet pa tel"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Në stacion karikimi"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nuk po karikohet"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Lidhur, jo në karikim"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Karikuar"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Cilësia e ajrit"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Të dhënat e aktorëve"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Kontrollet e shtëpisë"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"Zgjidh një fotografi profili"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"Ikona e parazgjedhur e përdoruesit"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"Tastiera fizike"</string> diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml index dee7f9dadf49..ea370f617c67 100644 --- a/packages/SettingsLib/res/values-sr/strings.xml +++ b/packages/SettingsLib/res/values-sr/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD звук: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD звук"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Слушни апарати"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Повезано са слушним апаратима"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Повезано са LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Повезано са LE audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Повезано са звуком медија"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Повезано са звуком телефона"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Повезано са сервером за пренос датотека"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Омогућава промену величине свих активности за режим са више прозора, без обзира на вредности манифеста."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Омогући прозоре произвољног формата"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Омогућава подршку за експерименталне прозоре произвољног формата."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Режим за рачунаре"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Лозинка резервне копије за рачунар"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Резервне копије читавог система тренутно нису заштићене"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Додирните да бисте променили или уклонили лозинку за прављење резервних копија читавог система на рачунару"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> до краја пуњења"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до краја пуњења"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> – Пуњење је привремено ограничено"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Непознато"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Пуни се"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Брзо се пуни"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Споро се пуни"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Бежично пуњење"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Станица за пуњење"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не пуни се"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Повезано, не пуни се"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Напуњено"</string> @@ -601,7 +604,7 @@ <string name="guest_resetting" msgid="7822120170191509566">"Сесија госта се ресетује…"</string> <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Желите да ресетујете сесију госта?"</string> <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Тиме ћете покренути нову сесију госта и избрисати све апликације и податке из актуелне сесије"</string> - <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Изаћи ћете из режима госта?"</string> + <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Излазите из режима госта?"</string> <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Тиме ћете избрисати све апликације и податке из актуелне сесије госта"</string> <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Изађи"</string> <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Сачуваћете активности госта?"</string> @@ -610,7 +613,7 @@ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Сачувај"</string> <string name="guest_exit_button" msgid="5774985819191803960">"Изађи из режима госта"</string> <string name="guest_reset_button" msgid="2515069346223503479">"Ресетуј сесију госта"</string> - <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Изађи из режима госта"</string> + <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Затвори режим госта"</string> <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Све активности ће бити избрисане при излазу"</string> <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Можете да сачувате или избришете активности при излазу"</string> <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Ресетујете за брисање активности сесије, или сачувајте или избришите активности при излазу"</string> diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml index 09b3d7adb72c..970f54990b4f 100644 --- a/packages/SettingsLib/res/values-sv/strings.xml +++ b/packages/SettingsLib/res/values-sv/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-ljud: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD-ljud"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Hörapparater"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Ansluten till hörapparater"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Ansluten till LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Ansluten till LE audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Ansluten till medialjud"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Ansluten till telefonens ljud"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Ansluten till filöverföringsserver"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Gör det möjligt att ändra storleken på alla aktiviteter i flerfönsterläge, oavsett manifestvärden."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Aktivera frihandsfönster"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Aktivera stöd för experimentella frihandsfönster."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Datorläge"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Lösenord för säkerhetskopia av datorn"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"De fullständiga säkerhetskopiorna av datorn är för närvarande inte skyddade"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Tryck om du vill ändra eller ta bort lösenordet för fullständig säkerhetskopiering av datorn"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> kvar tills fulladdat"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> kvar tills fulladdat"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> – laddning har begränsats tillfälligt"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Okänd"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Laddar"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Laddas snabbt"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Laddas långsamt"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Laddas trådlöst"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Dockningsstation"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Laddar inte"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Ansluten, laddas inte"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Laddat"</string> diff --git a/packages/SettingsLib/res/values-sw/arrays.xml b/packages/SettingsLib/res/values-sw/arrays.xml index dab4279f88dc..53dc6e5440ad 100644 --- a/packages/SettingsLib/res/values-sw/arrays.xml +++ b/packages/SettingsLib/res/values-sw/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"ramani ya 13"</item> <item msgid="8147982633566548515">"ramani ya 14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"Tumia Uteuzi wa Mfumo (Chaguomsingi)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"Sauti ya <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="2908219194098827570">"Sauti ya <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"Tumia Uteuzi wa Mfumo (Chaguomsingi)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"Sauti ya <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="3517061573669307965">"Sauti ya <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"Tumia Uteuzi wa Mfumo (Chaguomsingi)"</item> <item msgid="8003118270854840095">"kHz 44.1"</item> diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml index 63a732d2e7a9..476daf971ecb 100644 --- a/packages/SettingsLib/res/values-sw/strings.xml +++ b/packages/SettingsLib/res/values-sw/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Sauti ya HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Sauti ya HD"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Vifaa vya Kusaidia Kusikia"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Imeunganishwa kwenye Vifaa vya Kusaidia Kusikia"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Imeunganishwa kwenye LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Imeunganishwa kwenye LE audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Imeunganishwa kwenye sikika ya njia ya mawasiliano"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Imeunganishwa kwenye sauti ya simu"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Imeunganishwa kwenye seva ya kuhamisha faili"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Fanya shughuli zote ziweze kubadilishwa ukubwa kwenye madirisha mengi, bila kuzingatia thamani za faili ya maelezo."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Washa madirisha yenye muundo huru"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Ruhusu uwezo wa kutumia madirisha ya majaribio yenye muundo huru."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Hali ya kompyuta ya mezani"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Nenosiri la hifadhi rudufu ya eneo kazi"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Hifadhi rudufu kamili za eneo kazi hazijalindwa kwa sasa"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Gusa ili ubadilishe au uondoe nenosiri la hifadhi rudufu kamili za eneo kazi"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Zimesalia <xliff:g id="TIME">%1$s</xliff:g> ijae chaji"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> zimesalia ijae chaji"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - Kuchaji kumedhibitiwa kwa muda"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Haijulikani"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Inachaji"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Inachaji kwa kasi"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Inachaji pole pole"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Inachaji bila kutumia waya"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Kituo cha Kuchaji"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Haichaji"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Imeunganishwa, haichaji"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Imechajiwa"</string> @@ -596,11 +599,11 @@ <string name="guest_reset_guest" msgid="6110013010356013758">"Badilisha kipindi cha mgeni"</string> <string name="guest_reset_guest_dialog_title" msgid="8047270010895437534">"Ungependa kubadilisha kipindi cha mgeni?"</string> <string name="guest_remove_guest_dialog_title" msgid="4548511006624088072">"Ungependa kumwondoa mgeni?"</string> - <string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Badilisha"</string> + <string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Weka upya"</string> <string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Ondoa"</string> <string name="guest_resetting" msgid="7822120170191509566">"Inabadilisha kipindi cha mgeni…"</string> <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Ungependa kuweka upya kipindi cha mgeni?"</string> - <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Hii itaanzisha upya kipindi cha mgeni na kufuta programu na data yote kwenye kipindi cha sasa"</string> + <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Hatua hii itaanzisha upya kipindi cha mgeni na kufuta programu na data yote kwenye kipindi cha sasa"</string> <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Utafunga matumizi ya wageni?"</string> <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Hatua hii itafuta programu na data kutoka kwenye kipindi cha mgeni cha sasa"</string> <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Funga"</string> @@ -610,7 +613,7 @@ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Hifadhi"</string> <string name="guest_exit_button" msgid="5774985819191803960">"Funga matumizi ya wageni"</string> <string name="guest_reset_button" msgid="2515069346223503479">"Weka upya kipindi cha mgeni"</string> - <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Funga utumiaji wa mgeni"</string> + <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Funga wasifu wa mgeni"</string> <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Shughuli zote zitafutwa wakati wa kufunga"</string> <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Unaweza kuhifadhi au kufuta shughuli zako wakati wa kufunga"</string> <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Weka upya ili ufute shughuli za kipindi sasa au unaweza kuhifadhi au kufuta shughuli wakati wa kufunga"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Ubora wa Hewa"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Maelezo ya Wahusika"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Udhibiti wa Vifaa Nyumbani"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"Chagua picha ya wasifu"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"Aikoni chaguomsingi ya mtumiaji"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"Kibodi halisi"</string> diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml index 1f717886fb38..962ec34b54a6 100644 --- a/packages/SettingsLib/res/values-ta/strings.xml +++ b/packages/SettingsLib/res/values-ta/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ஆடியோ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ஆடியோ"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"செவித்துணை கருவிகள்"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE ஆடியோ"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"செவித்துணை கருவிகளுடன் இணைக்கப்பட்டது"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"LE_AUDIO உடன் இணைக்கப்பட்டது"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE ஆடியோவுடன் இணைக்கப்பட்டுள்ளது"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"மீடியா ஆடியோவுடன் இணைக்கப்பட்டது"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"மொபைல் ஆடியோவுடன் இணைக்கப்பட்டது"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"ஃபைலைப் பரிமாற்றும் சேவையகத்துடன் இணைக்கப்பட்டது"</string> @@ -408,6 +408,8 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"மேனிஃபெஸ்ட் மதிப்புகளைப் பொருட்படுத்தாமல், பல சாளரத்திற்கு எல்லா செயல்பாடுகளையும் அளவுமாறக்கூடியதாக அமை."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"குறிப்பிட்ட வடிவமில்லாத சாளரங்களை இயக்கு"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"சாளரங்களை அளவுமாற்ற மற்றும் எங்கும் நகர்த்த அனுமதிக்கும் பரிசோதனைக்குரிய அம்சத்திற்கான ஆதரவை இயக்கு."</string> + <!-- no translation found for desktop_mode (2389067840550544462) --> + <skip /> <string name="local_backup_password_title" msgid="4631017948933578709">"டெஸ்க்டாப் காப்புப்பிரதி கடவுச்சொல்"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"டெஸ்க்டாப்பின் முழு காப்புப்பிரதிகள் தற்போது பாதுகாக்கப்படவில்லை"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"டெஸ்க்டாப்பின் முழுக் காப்புப் பிரதிகளுக்கான கடவுச்சொல்லை மாற்ற அல்லது அகற்ற, தட்டவும்"</string> @@ -476,13 +478,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"முழுவதும் சார்ஜாக <xliff:g id="TIME">%1$s</xliff:g> ஆகும்"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - முழுவதும் சார்ஜாக <xliff:g id="TIME">%2$s</xliff:g> ஆகும்"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - சார்ஜாவது தற்காலிகமாக வரம்பிடப்பட்டுள்ளது"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"அறியப்படாத"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"சார்ஜ் ஆகிறது"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"வேகமாக சார்ஜாகிறது"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"மெதுவாக சார்ஜாகிறது"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"வயரின்றி சார்ஜாகிறது"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"சார்ஜிங் டாக்"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"சார்ஜ் செய்யப்படவில்லை"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"இணைக்கப்பட்டுள்ளது, சார்ஜாகவில்லை"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"சார்ஜாகிவிட்டது"</string> @@ -586,7 +590,7 @@ <string name="user_set_lock_button" msgid="1427128184982594856">"பூட்டை அமை"</string> <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g>க்கு மாறு"</string> <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"புதிய பயனரை உருவாக்குகிறது…"</string> - <string name="creating_new_guest_dialog_message" msgid="1114905602181350690">"புதிய விருந்தினரை உருவாக்குகிறது…"</string> + <string name="creating_new_guest_dialog_message" msgid="1114905602181350690">"புதிய கெஸ்ட் பயனரை உருவாக்குகிறது…"</string> <string name="add_user_failed" msgid="4809887794313944872">"புதிய பயனரை உருவாக்க முடியவில்லை"</string> <string name="add_guest_failed" msgid="8074548434469843443">"புதிய விருந்தினரை உருவாக்க முடியவில்லை"</string> <string name="user_nickname" msgid="262624187455825083">"புனைப்பெயர்"</string> diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml index 592810e0e651..cc1f29171855 100644 --- a/packages/SettingsLib/res/values-te/strings.xml +++ b/packages/SettingsLib/res/values-te/strings.xml @@ -117,17 +117,17 @@ <string name="bluetooth_profile_opp" msgid="6692618568149493430">"ఫైల్ బదిలీ"</string> <string name="bluetooth_profile_hid" msgid="2969922922664315866">"ఇన్పుట్ పరికరం"</string> <string name="bluetooth_profile_pan" msgid="1006235139308318188">"ఇంటర్నెట్ యాక్సెస్"</string> - <string name="bluetooth_profile_pbap" msgid="4262303387989406171">"Contacts and call history sharing"</string> - <string name="bluetooth_profile_pbap_summary" msgid="6466456791354759132">"Use for contacts and call history sharing"</string> + <string name="bluetooth_profile_pbap" msgid="4262303387989406171">"కాంటాక్ట్లు, కాల్ హిస్టరీ షేరింగ్"</string> + <string name="bluetooth_profile_pbap_summary" msgid="6466456791354759132">"కాంటాక్ట్లు, కాల్ హిస్టరీ షేరింగ్ కోసం ఉపయోగించండి"</string> <string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"ఇంటర్నెట్ కనెక్షన్ షేరింగ్"</string> <string name="bluetooth_profile_map" msgid="8907204701162107271">"టెక్స్ట్ మెసేజ్లు"</string> <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM యాక్సెస్"</string> <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ఆడియో: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ఆడియో"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"వినికిడి మద్దతు ఉపకరణాలు"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"Le ఆడియో"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"వినికిడి మద్దతు ఉపకరణాలకు కనెక్ట్ చేయబడింది"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"LE_AUDIOకు కనెక్ట్ చేయబడింది"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE ఆడియోకు కనెక్ట్ చేయబడింది"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"మీడియా ఆడియోకు కనెక్ట్ చేయబడింది"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ఫోన్ ఆడియోకు కనెక్ట్ చేయబడింది"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"ఫైల్ బదిలీ సర్వర్కు కనెక్ట్ చేయబడింది"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"మానిఫెస్ట్ విలువలతో సంబంధం లేకుండా అన్ని యాక్టివిటీస్ను పలు రకాల విండోల్లో సరిపోయేటట్లు సైజ్ మార్చగలిగేలా చేస్తుంది."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"స్వతంత్ర రూప విండోలను ఎనేబుల్ చేయండి"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"ప్రయోగాత్మక స్వతంత్ర రూప విండోల కోసం సపోర్ట్ను ఎనేబుల్ చేస్తుంది."</string> + <string name="desktop_mode" msgid="2389067840550544462">"డెస్క్టాప్ మోడ్"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"డెస్క్టాప్ బ్యాకప్ పాస్వర్డ్"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"డెస్క్టాప్ పూర్తి బ్యాకప్లు ప్రస్తుతం రక్షించబడలేదు"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"డెస్క్టాప్ పూర్తి బ్యాకప్ల కోసం పాస్వర్డ్ను మార్చడానికి లేదా తీసివేయడానికి నొక్కండి"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g>లో పూర్తిగా ఛార్జ్ అవుతుంది"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>లో పూర్తిగా ఛార్జ్ అవుతుంది"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - ఛార్జింగ్ తాత్కాలికంగా పరిమితం చేయబడింది"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"తెలియదు"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ఛార్జ్ అవుతోంది"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"వేగవంతమైన ఛార్జింగ్"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"నెమ్మదిగా ఛార్జింగ్"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"వైర్లెస్ ఛార్జింగ్"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"ఛార్జింగ్ డాక్"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ఛార్జ్ కావడం లేదు"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"కనెక్ట్ చేయబడింది, ఛార్జ్ చేయబడలేదు"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"ఛార్జ్ చేయబడింది"</string> @@ -586,7 +589,7 @@ <string name="user_set_lock_button" msgid="1427128184982594856">"లాక్ను సెట్ చేయి"</string> <string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g>కు స్విచ్ చేయండి"</string> <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"కొత్త యూజర్ను క్రియేట్ చేస్తోంది…"</string> - <string name="creating_new_guest_dialog_message" msgid="1114905602181350690">"కొత్త అతిథిని క్రియేట్ చేస్తోంది…"</string> + <string name="creating_new_guest_dialog_message" msgid="1114905602181350690">"కొత్త గెస్ట్ను క్రియేట్ చేస్తోంది…"</string> <string name="add_user_failed" msgid="4809887794313944872">"కొత్త యూజర్ను క్రియేట్ చేయడం విఫలమైంది"</string> <string name="add_guest_failed" msgid="8074548434469843443">"కొత్త అతిథిని క్రియేట్ చేయడం విఫలమైంది"</string> <string name="user_nickname" msgid="262624187455825083">"మారుపేరు"</string> @@ -601,17 +604,17 @@ <string name="guest_resetting" msgid="7822120170191509566">"గెస్ట్ సెషన్ను రీసెట్ చేస్తోంది…"</string> <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"గెస్ట్ సెషన్ను రీసెట్ చేయాలా?"</string> <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"ఇది కొత్త గెస్ట్ సెషన్ను ప్రారంభిస్తుంది, ప్రస్తుత సెషన్ నుండి అన్ని యాప్లు, డేటాను తొలగిస్తుంది."</string> - <string name="guest_exit_dialog_title" msgid="1846494656849381804">"గెస్ట్ మోడ్ నిష్క్రమించాలా?"</string> + <string name="guest_exit_dialog_title" msgid="1846494656849381804">"గెస్ట్ మోడ్ నుండి వైదొలగాలా?"</string> <string name="guest_exit_dialog_message" msgid="1743218864242719783">"ఇది ప్రస్తుత గెస్ట్ సెషన్ నుండి యాప్లను వాటితో పాటు డేటాను తొలగిస్తుంది"</string> - <string name="guest_exit_dialog_button" msgid="1736401897067442044">"నిష్క్రమించండి"</string> + <string name="guest_exit_dialog_button" msgid="1736401897067442044">"వైదొలగండి"</string> <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"గెస్ట్ యాక్టివిటీని సేవ్ చేయాలా?"</string> <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"మీరు సెషన్ నుండి యాక్టివిటీని సేవ్ చేయవచ్చు, అన్ని యాప్లు, డేటాను తొలగించవచ్చు"</string> <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"తొలగించండి"</string> <string name="guest_exit_save_data_button" msgid="3690974510644963547">"సేవ్ చేయండి"</string> <string name="guest_exit_button" msgid="5774985819191803960">"గెస్ట్ మోడ్ నుండి వైదొలగండి"</string> <string name="guest_reset_button" msgid="2515069346223503479">"గెస్ట్ సెషన్ను రీసెట్ చేయండి"</string> - <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"గెస్ట్ మోడ్ నుండి నిష్క్రమించండి"</string> - <string name="guest_notification_ephemeral" msgid="7263252466950923871">"నిష్క్రమణ సమయంలో మొత్తం యాక్టివిటీ తొలగించబడుతుంది"</string> + <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"గెస్ట్ మోడ్ నుండి వైదొలగండి"</string> + <string name="guest_notification_ephemeral" msgid="7263252466950923871">"వైదొలగినప్పుడు యాక్టివిటీ అంతా తొలగించబడుతుంది"</string> <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"మీ నిష్క్రమణలో, యాక్టివిటీని సేవ్ చేయవచ్చు లేదా తొలగించవచ్చు"</string> <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"సెషన్ యాక్టివిటీని తొలగించడానికి ఇప్పుడే రీసెట్ చేయండి లేదా మీరు నిష్క్రమించేటప్పుడు యాక్టివిటీని సేవ్ చేయవచ్చు లేదా తొలగించవచ్చు"</string> <string name="user_image_take_photo" msgid="467512954561638530">"ఒక ఫోటో తీయండి"</string> diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml index aa01af7d88c8..65744b21f056 100644 --- a/packages/SettingsLib/res/values-th/strings.xml +++ b/packages/SettingsLib/res/values-th/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"เสียง HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"เสียง HD"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"เครื่องช่วยฟัง"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE Audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"เชื่อมต่อกับเครื่องช่วยฟังแล้ว"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"เชื่อมต่อกับ LE_AUDIO แล้ว"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"เชื่อมต่อกับ LE Audio แล้ว"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"เชื่อมต่อกับระบบเสียงของสื่อแล้ว"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"เชื่อมต่อกับระบบเสียงของโทรศัพท์แล้ว"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"เชื่อมต่อกับเซิร์ฟเวอร์สำหรับโอนไฟล์แล้ว"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"ทำให้กิจกรรมทั้งหมดปรับขนาดได้สำหรับหน้าต่างหลายบาน โดยไม่คำนึงถึงค่าในไฟล์ Manifest"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"เปิดใช้หน้าต่างรูปแบบอิสระ"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"เปิดการสนับสนุนหน้าต่างรูปแบบอิสระแบบทดลอง"</string> + <string name="desktop_mode" msgid="2389067840550544462">"โหมดเดสก์ท็อป"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"รหัสผ่านการสำรองข้อมูลในเดสก์ท็อป"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"การสำรองข้อมูลเต็มรูปแบบในเดสก์ท็อปไม่ได้รับการป้องกันในขณะนี้"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"แตะเพื่อเปลี่ยนแปลงหรือลบรหัสผ่านสำหรับการสำรองข้อมูลเต็มรูปแบบในเดสก์ท็อป"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"อีก <xliff:g id="TIME">%1$s</xliff:g>จึงจะเต็ม"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - อีก <xliff:g id="TIME">%2$s</xliff:g> จึงจะเต็ม"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - จำกัดการชาร์จชั่วคราว"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"ไม่ทราบ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"กำลังชาร์จ"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"กำลังชาร์จอย่างเร็ว"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"กำลังชาร์จอย่างช้าๆ"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"กำลังชาร์จแบบไร้สาย"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"กำลังชาร์จบนแท่น"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ไม่ได้ชาร์จ"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"เชื่อมต่ออยู่ ไม่ได้ชาร์จ"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"ชาร์จแล้ว"</string> @@ -586,7 +589,7 @@ <string name="user_set_lock_button" msgid="1427128184982594856">"ตั้งค่าล็อก"</string> <string name="user_switch_to_user" msgid="6975428297154968543">"เปลี่ยนเป็น <xliff:g id="USER_NAME">%s</xliff:g>"</string> <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"กำลังสร้างผู้ใช้ใหม่…"</string> - <string name="creating_new_guest_dialog_message" msgid="1114905602181350690">"กำลังสร้างผู้เข้าร่วมใหม่…"</string> + <string name="creating_new_guest_dialog_message" msgid="1114905602181350690">"กำลังสร้างผู้ใช้ชั่วคราวใหม่…"</string> <string name="add_user_failed" msgid="4809887794313944872">"สร้างผู้ใช้ใหม่ไม่ได้"</string> <string name="add_guest_failed" msgid="8074548434469843443">"สร้างผู้เข้าร่วมใหม่ไม่สำเร็จ"</string> <string name="user_nickname" msgid="262624187455825083">"ชื่อเล่น"</string> diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml index c2f203546667..6b1411d3655a 100644 --- a/packages/SettingsLib/res/values-tl/strings.xml +++ b/packages/SettingsLib/res/values-tl/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Mga Hearing Aid"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Nakakonekta sa Mga Hearing Aid"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Nakakonekta sa LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Nakakonekta sa LE audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Konektado sa media audio"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Nakakonekta sa audio ng telepono"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Nakakonekta sa server sa paglilipat ng file"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Gawing nare-resize ang lahat ng aktibidad para sa multi-window, anuman ang mga value ng manifest."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"I-enable ang mga freeform window"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"I-enable ang suporta para sa mga pang-eksperimentong freeform window."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Desktop mode"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Password ng pag-backup ng desktop"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Kasalukuyang hindi pinoprotektahan ang mga buong pag-backup ng desktop"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"I-tap upang baguhin o alisin ang password para sa mga kumpletong pag-back up sa desktop"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> na lang bago mapuno"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> na lang bago mapuno"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - Pansamantalang limitado ang pag-charge"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Hindi Kilala"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Nagcha-charge"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Mabilis na charge"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Mabagal na charge"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Wireless na nagcha-charge"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Charging Dock"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Hindi nagcha-charge"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Nakakonekta, hindi nagcha-charge"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Nasingil"</string> diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml index 0bc9cc252d07..af51c6881962 100644 --- a/packages/SettingsLib/res/values-tr/strings.xml +++ b/packages/SettingsLib/res/values-tr/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ses: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ses"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"İşitme Cihazları"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE Audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"İşitme Cihazlarına Bağlandı"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"LE_AUDIO\'ya bağlandı"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE Audio\'ya bağlandı"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Medya sesine bağlanıldı"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Telefon sesine bağlandı"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Dosya aktarım sunucusuna bağlandı"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Manifest değerlerinden bağımsız olarak, tüm etkinlikleri birden fazla pencerede yeniden boyutlandırılabilir yap."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Serbest biçimli pencereleri etkinleştir"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Deneysel serbest biçimli pencere desteğini etkinleştir."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Masaüstü modu"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Masaüstü yedekleme şifresi"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Masaüstü tam yedeklemeleri şu an korunmuyor"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Masaüstü tam yedeklemelerinin şifresini değiştirmek veya kaldırmak için dokunun"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Tamamen şarj olmasına <xliff:g id="TIME">%1$s</xliff:g> kaldı"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - Tamamen şarj olmasına <xliff:g id="TIME">%2$s</xliff:g> kaldı"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - Şarj etme geçici olarak sınırlı"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Bilinmiyor"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Şarj oluyor"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Hızlı şarj oluyor"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Yavaş şarj oluyor"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Kablosuz şarj oluyor"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Şarj Yuvası"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Şarj olmuyor"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Bağlandı, şarj olmuyor"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Şarj oldu"</string> diff --git a/packages/SettingsLib/res/values-uk/arrays.xml b/packages/SettingsLib/res/values-uk/arrays.xml index 0410bd6ab43d..c32da85ceab3 100644 --- a/packages/SettingsLib/res/values-uk/arrays.xml +++ b/packages/SettingsLib/res/values-uk/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"Використовувати вибір системи (за умовчанням)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"Аудіо <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="2908219194098827570">"Аудіо <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"Використовувати вибір системи (за умовчанням)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"Аудіо <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item> + <item msgid="3517061573669307965">"Аудіо <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"Використовувати вибір системи (за умовчанням)"</item> <item msgid="8003118270854840095">"44,1 кГц"</item> diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml index 03e3ef9b2cae..8e102e48413b 100644 --- a/packages/SettingsLib/res/values-uk/strings.xml +++ b/packages/SettingsLib/res/values-uk/strings.xml @@ -117,7 +117,7 @@ <string name="bluetooth_profile_opp" msgid="6692618568149493430">"Передавання файлів"</string> <string name="bluetooth_profile_hid" msgid="2969922922664315866">"Пристрій введення"</string> <string name="bluetooth_profile_pan" msgid="1006235139308318188">"Доступ до Інтернету"</string> - <string name="bluetooth_profile_pbap" msgid="4262303387989406171">"Надсилання контактів та історії викликів"</string> + <string name="bluetooth_profile_pbap" msgid="4262303387989406171">"Доступ до контактів та історії дзвінків"</string> <string name="bluetooth_profile_pbap_summary" msgid="6466456791354759132">"Використовуйте, щоб надсилати контакти й історію викликів"</string> <string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"Надання доступу до Інтернету"</string> <string name="bluetooth_profile_map" msgid="8907204701162107271">"Текстові повідомлення"</string> @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-аудіо: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD-аудіо"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Слухові апарати"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE Audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Підключено до слухових апаратів"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Підключено до LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Підключено до LE Audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Підключено до аудіоджерела"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Підключено до звуку телеф."</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Підключ. до сервера передачі файлів"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Масштабувати активність на кілька вікон, незалежно від значень у файлі маніфесту."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Увімкнути вікна довільного формату"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Увімкнути експериментальні вікна довільного формату."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Режим комп’ютера"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Пароль рез. копії на ПК"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Повні резервні копії на комп’ютері наразі не захищені"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Торкніться, щоб змінити або видалити пароль для повного резервного копіювання на комп’ютер"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> до повного заряду"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до повного заряду"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> • Заряджання тимчасово обмежено"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Невідомо"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Заряджається"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Швидке заряджання"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Повільне заряджання"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Бездротове заряджання"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Зарядка: док-станція"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не заряджається"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Підключено, не заряджається"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Заряджено"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Якість повітря"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Акторський склад"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Автоматизація дому"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"Виберіть зображення профілю"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"Значок користувача за умовчанням"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"Фізична клавіатура"</string> diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml index cac1b6c47050..9e22b904607d 100644 --- a/packages/SettingsLib/res/values-ur/strings.xml +++ b/packages/SettingsLib/res/values-ur/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD آڈیو: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD آڈیو"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"سماعتی آلات"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE آڈیو"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"سماعتی آلات سے منسلک ہے"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"LE_AUDIO سے منسلک ہے"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE آڈیو سے منسلک ہے"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"میڈیا آڈیو سے مربوط"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"فون آڈیو سے مربوط"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"فائل منتقلی سرور سے مربوط ہو گیا ہے"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"مینی فیسٹ اقدار سے قطع نظر، ملٹی ونڈو کیلئے تمام سرگرمیوں کو ری سائز ایبل بنائیں۔"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"freeform ونڈوز فعال کریں"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"تجرباتی فری فارم ونڈوز کیلئے سپورٹ فعال کریں۔"</string> + <string name="desktop_mode" msgid="2389067840550544462">"ڈیسک ٹاپ موڈ"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"ڈیسک ٹاپ کا بیک اپ پاس ورڈ"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"ڈیسک ٹاپ کے مکمل بیک اپس فی الحال محفوظ کیے ہوئے نہیں ہیں"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"ڈیسک ٹاپ کے مکمل بیک اپس کیلئے پاس ورڈ کو تبدیل کرنے یا ہٹانے کیلئے تھپتھپائیں"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"مکمل چارج ہونے میں <xliff:g id="TIME">%1$s</xliff:g> باقی ہے"</string> <string name="power_charging_duration" msgid="6127154952524919719">"مکمل چارج ہونے میں <xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> باقی ہے"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> • چارجنگ عارضی طور پر محدود ہے"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"نامعلوم"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"چارج ہو رہا ہے"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"تیزی سے چارج ہو رہا ہے"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"آہستہ چارج ہو رہی ہے"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"وائرلیس طریقے سے چارج ہو رہی ہے"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"چارجنگ ڈاک"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"چارج نہیں ہو رہا ہے"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"منسلک ہے، چارج نہیں ہو رہی ہے"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"چارج ہو گئی"</string> diff --git a/packages/SettingsLib/res/values-uz/arrays.xml b/packages/SettingsLib/res/values-uz/arrays.xml index 7d09027983a3..edbd1805b756 100644 --- a/packages/SettingsLib/res/values-uz/arrays.xml +++ b/packages/SettingsLib/res/values-uz/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"Tizim tanlovi (birlamchi)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audiokodeki"</item> + <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audiokodeki"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"Tizim tanlovi (birlamchi)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audiokodeki"</item> + <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audiokodeki"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"Tizim tanlovi (birlamchi)"</item> <item msgid="8003118270854840095">"44.1 kGs"</item> diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml index cae38142e8e7..c66b6cf937d7 100644 --- a/packages/SettingsLib/res/values-uz/strings.xml +++ b/packages/SettingsLib/res/values-uz/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Eshitish apparatlari"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Eshitish apparatlariga ulangan"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Ulandi: LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE audioga ulandi"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Audio qurilmasiga ulangan"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Telefon karnayiga ulanildi"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Fayl almashinish serveriga ulanildi"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Manifest qiymatidan qat’i nazar barcha harakatlarni ko‘p oynali rejimga moslashtirish."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Erkin shakldagi oynalarni yoqish"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Erkin shakldagi oynalar yaratish uchun mo‘ljallangan tajribaviy funksiyani yoqish."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Desktop rejimi"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Zaxira nusxa uchun parol"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Kompyuterdagi zaxira nusxalar hozirgi vaqtda himoyalanmagan"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Ish stoli to‘liq zaxira nusxalari parolini o‘zgartirish yoki o‘chirish uchun bu yerni bosing"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Toʻlishiga <xliff:g id="TIME">%1$s</xliff:g> qoldi"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – Toʻlishiga <xliff:g id="TIME">%2$s</xliff:g> qoldi"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> – Quvvatlash vaqtincha cheklangan"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Noma’lum"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Quvvat olmoqda"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Tezkor quvvat olmoqda"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Sekin quvvat olmoqda"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Simsiz quvvat olmoqda"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Quvvatlash doki"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Quvvat olmayapti"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Ulangan, quvvat olmayapti"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Quvvat oldi"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Havo sifati"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Translatsiya axboroti"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Uy boshqaruvi"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"Profil rasmini tanlash"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"Foydalanuvchining standart belgisi"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"Tashqi klaviatura"</string> diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml index 1995e705bd66..ed781085bbc0 100644 --- a/packages/SettingsLib/res/values-vi/strings.xml +++ b/packages/SettingsLib/res/values-vi/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Âm thanh HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Âm thanh HD"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Thiết bị trợ thính"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"Âm thanh LE"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Đã kết nối với Thiết bị trợ thính"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Đã kết nối với LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Đã kết nối với âm thanh LE"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Đã kết nối với âm thanh nội dung nghe nhìn"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Đã kết nối với âm thanh điện thoại"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Đã kết nối với máy chủ chuyển tệp"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Cho phép thay đổi kích thước của tất cả các hoạt động cho nhiều cửa sổ, bất kể giá trị tệp kê khai là gì."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Bật cửa sổ dạng tự do"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Bật tính năng hỗ trợ cửa sổ dạng tự do thử nghiệm."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Chế độ máy tính"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Mật khẩu sao lưu vào máy tính"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Các bản sao lưu đầy đủ vào máy tính hiện không được bảo vệ"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Nhấn để thay đổi hoặc xóa mật khẩu dành cho các bản sao lưu đầy đủ vào máy tính"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> nữa là pin đầy"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> nữa là pin đầy"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> – Mức sạc tạm thời bị giới hạn"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Không xác định"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Đang sạc"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Đang sạc nhanh"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Đang sạc chậm"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Đang sạc không dây"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Đế sạc"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Hiện không sạc"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Đã kết nối nhưng chưa sạc"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Đã sạc"</string> diff --git a/packages/SettingsLib/res/values-zh-rCN/arrays.xml b/packages/SettingsLib/res/values-zh-rCN/arrays.xml index 973d7d01fcc9..2a85d311a5c4 100644 --- a/packages/SettingsLib/res/values-zh-rCN/arrays.xml +++ b/packages/SettingsLib/res/values-zh-rCN/arrays.xml @@ -85,10 +85,26 @@ <item msgid="7073042887003102964">"map13"</item> <item msgid="8147982633566548515">"map14"</item> </string-array> - <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) --> - <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) --> - <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) --> + <string-array name="bluetooth_a2dp_codec_titles"> + <item msgid="2494959071796102843">"使用系统选择(默认)"</item> + <item msgid="4055460186095649420">"SBC"</item> + <item msgid="720249083677397051">"AAC"</item> + <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> 音频"</item> + <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> 音频"</item> + <item msgid="3825367753087348007">"LDAC"</item> + <item msgid="328951785723550863">"LC3"</item> + <item msgid="506175145534048710">"Opus"</item> + </string-array> + <string-array name="bluetooth_a2dp_codec_summaries"> + <item msgid="8868109554557331312">"使用系统选择(默认)"</item> + <item msgid="9024885861221697796">"SBC"</item> + <item msgid="4688890470703790013">"AAC"</item> + <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> 音频"</item> + <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> 音频"</item> + <item msgid="2553206901068987657">"LDAC"</item> + <item msgid="3940992993241040716">"LC3"</item> + <item msgid="7940970833006181407">"Opus"</item> + </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> <item msgid="926809261293414607">"使用系统选择(默认)"</item> <item msgid="8003118270854840095">"44.1 kHz"</item> diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml index c4780850e5fe..e1e50ae4cd51 100644 --- a/packages/SettingsLib/res/values-zh-rCN/strings.xml +++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD 音频:<xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD 音频"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"助听器"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE 音频"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"已连接到助听器"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"已连接到 LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"已连接到 LE 音频"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"已连接到媒体音频"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"已连接到手机音频"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"已连接到文件传输服务器"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"将所有 Activity 设为可配合多窗口环境调整大小(忽略清单值)。"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"启用可自由调整的窗口"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"启用可自由调整的窗口这一实验性功能。"</string> + <string name="desktop_mode" msgid="2389067840550544462">"桌面模式"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"桌面备份密码"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"桌面完整备份当前未设置密码保护"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"点按即可更改或移除用于保护桌面完整备份的密码"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"还需<xliff:g id="TIME">%1$s</xliff:g>充满"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - 还需<xliff:g id="TIME">%2$s</xliff:g>充满"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - 充电暂时受限"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"未知"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"正在充电"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"正在快速充电"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"正在慢速充电"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"正在无线充电"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"充电基座"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"未在充电"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"已连接,未充电"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"已充满电"</string> @@ -661,8 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"空气质量"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"投放信息"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"家居控制"</string> - <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) --> - <skip /> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"SmartSpace"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"选择个人资料照片"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"默认用户图标"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"实体键盘"</string> diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml index b4667a85ed3f..1a6648fc70a5 100644 --- a/packages/SettingsLib/res/values-zh-rHK/strings.xml +++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"高清音訊:<xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"高清音訊"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"助聽器"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE Audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"已連接助聽器"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"已連接 LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"已連接 LE Audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"已連接媒體音頻裝置"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"已連接手機耳機"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"已連線至檔案傳輸伺服器"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"在任何資訊清單值下,允許系統配合多重視窗環境調整所有活動的尺寸。"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"啟用自由形態視窗"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"啟用實驗版自由形態視窗的支援功能。"</string> + <string name="desktop_mode" msgid="2389067840550544462">"電腦模式"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"桌面電腦備份密碼"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"桌面電腦的完整備份目前未受保護"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"輕按即可變更或移除桌面電腦完整備份的密碼"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g>後充滿電"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>後充滿電"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - 充電暫時受限"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"未知"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"快速充電中"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"慢速充電中"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"無線充電中"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"充電插座"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"非充電中"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"已連接,非充電中"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"已充滿電"</string> @@ -661,7 +664,7 @@ <string name="dream_complication_title_aqi" msgid="4587552608957834110">"空氣質素"</string> <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"投放資料"</string> <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"智能家居"</string> - <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"智慧空間"</string> + <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string> <string name="avatar_picker_title" msgid="8492884172713170652">"選擇個人檔案相片"</string> <string name="default_user_icon_description" msgid="6554047177298972638">"預設使用者圖示"</string> <string name="physical_keyboard_title" msgid="4811935435315835220">"實體鍵盤"</string> diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml index 809702226ea1..991dcad785d2 100644 --- a/packages/SettingsLib/res/values-zh-rTW/strings.xml +++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD 高解析音訊:<xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD 高解析音訊"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"助聽器"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"LE audio"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"已連接到助聽器"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"已連上 LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"已連上 LE audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"連接至媒體音訊"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"連接至電話音訊"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"已連線到檔案傳輸伺服器"</string> @@ -241,7 +241,7 @@ <string name="adb_wireless_settings" msgid="2295017847215680229">"無線偵錯"</string> <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"如要查看並使用可用的裝置,請開啟無線偵錯功能"</string> <string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"使用 QR 圖碼配對裝置"</string> - <string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"使用 QR 圖碼掃描器配對新裝置"</string> + <string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"使用 QR code 掃描器配對新裝置"</string> <string name="adb_pair_method_code_title" msgid="1122590300445142904">"使用配對碼配對裝置"</string> <string name="adb_pair_method_code_summary" msgid="6370414511333685185">"使用六位數的配對碼配對新裝置"</string> <string name="adb_paired_devices_title" msgid="5268997341526217362">"已配對的裝置"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"將所有活動設為可配合多重視窗環境調整大小 (無論資訊清單值為何)。"</string> <string name="enable_freeform_support" msgid="7599125687603914253">"啟用自由形式視窗"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"啟用實驗版自由形式視窗的支援功能。"</string> + <string name="desktop_mode" msgid="2389067840550544462">"電腦模式"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"電腦備份密碼"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"目前尚未設定密碼來保護完整的備份檔案 (透過電腦備份的檔案)"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"輕觸即可變更或移除電腦完整備份的密碼"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g>後充飽"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>後充飽"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - 已暫時限制充電"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"不明"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"快速充電中"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"慢速充電中"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"正在進行無線充電"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"充電座架"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"非充電中"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"已連接,尚未充電"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"充電完成"</string> @@ -600,9 +603,9 @@ <string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"移除"</string> <string name="guest_resetting" msgid="7822120170191509566">"正在重設訪客…"</string> <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"要重設訪客工作階段嗎?"</string> - <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"這麼做將開始新的訪客工作階段,並刪除目前工作階段中的所有應用程式和資料"</string> + <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"如果重設,系統會開始新的訪客工作階段,並刪除目前工作階段中的所有應用程式和資料"</string> <string name="guest_exit_dialog_title" msgid="1846494656849381804">"要結束訪客模式嗎?"</string> - <string name="guest_exit_dialog_message" msgid="1743218864242719783">"這麼做將刪除目前訪客工作階段中的所有應用程式和資料"</string> + <string name="guest_exit_dialog_message" msgid="1743218864242719783">"如果結束,系統會刪除目前訪客工作階段中的所有應用程式和資料"</string> <string name="guest_exit_dialog_button" msgid="1736401897067442044">"結束"</string> <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"要儲存訪客活動嗎?"</string> <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"你可以儲存目前工作階段中的活動,也可以刪除所有應用程式和資料"</string> diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml index 2321ace413db..f1f736c7c671 100644 --- a/packages/SettingsLib/res/values-zu/strings.xml +++ b/packages/SettingsLib/res/values-zu/strings.xml @@ -125,9 +125,9 @@ <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Umsindo we-HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Umsindo we-HD"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Izinsiza zokuzwa"</string> - <string name="bluetooth_profile_le_audio" msgid="5158149987518342036">"I-LE_AUDIO"</string> + <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"Umsindo we-LE"</string> <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Kuxhumeke kwizinsiza zokuzwa"</string> - <string name="bluetooth_le_audio_profile_summary_connected" msgid="3162538609379333442">"Kuxhunywe ku-LE_AUDIO"</string> + <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Kuxhunywe kumsindo we-LE"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Ixhume emsindweni wemidiya"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Ixhunywe kumsindo wefoni"</string> <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Ixhunywe kwiseva yokudlulisa ifayela"</string> @@ -408,6 +408,7 @@ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Yenza yonke imisebenzi ibe nosayizi abasha kumawindi amaningi, ngokunganaki amavelu e-manifest."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Nika amandla amawindi e-freeform"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Nika amandla usekelo lwe-windows yokuhlola kwe-freeform."</string> + <string name="desktop_mode" msgid="2389067840550544462">"Imodi yedeskithophu"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Iphasiwedi yokusekela ngokulondoloza ye-Desktop"</string> <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Ukusekela ngokulondoloza okugcwele kwe-Desktop akuvikelekile okwamanje."</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Thepha ukushintsha noma ukususa iphasiwedi yokwenziwa kwezipele ngokugcwele kwideskithophu"</string> @@ -476,13 +477,15 @@ <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> okusele kuze kugcwale"</string> <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> okusele kuze kugcwale"</string> - <string name="power_charging_limited" msgid="7956120998372505295">"<xliff:g id="LEVEL">%1$s</xliff:g> - Ukushaja kukhawulelwe okwesikhashana"</string> + <!-- no translation found for power_charging_limited (6971664137170239141) --> + <skip /> <string name="battery_info_status_unknown" msgid="268625384868401114">"Akwaziwa"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Iyashaja"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Ishaja ngokushesha"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Ishaja kancane"</string> <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Iyashaja ngaphandle kwentambo"</string> - <string name="battery_info_status_charging_dock" msgid="3554147903321236585">"Idokhu yokushaja"</string> + <!-- no translation found for battery_info_status_charging_dock (8573274094093364791) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ayishaji"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Ixhunyiwe, ayishaji"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Kushajiwe"</string> diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 11cb9c1b54c9..949bbfbabf0b 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1125,7 +1125,7 @@ <!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration --> <string name="power_charging_duration"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="time">%2$s</xliff:g> left until full</string> <!-- [CHAR_LIMIT=80] Label for battery level chart when charge been limited --> - <string name="power_charging_limited"><xliff:g id="level">%1$s</xliff:g> - Charging temporarily limited</string> + <string name="power_charging_limited"><xliff:g id="level">%1$s</xliff:g> - Charging is paused</string> <!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed --> <string name="battery_info_status_unknown">Unknown</string> @@ -1138,7 +1138,7 @@ <!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when charging wirelessly. --> <string name="battery_info_status_charging_wireless">Charging wirelessly</string> <!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when the device is dock charging. --> - <string name="battery_info_status_charging_dock">Charging Dock</string> + <string name="battery_info_status_charging_dock">Charging</string> <!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed --> <string name="battery_info_status_discharging">Not charging</string> <!-- Battery Info screen. Value for a status item. A state which device is connected with any charger(e.g. USB, Adapter or Wireless) but not charging yet. Used for diagnostic info screens, precise translation isn't needed --> @@ -1601,21 +1601,6 @@ <!-- Content description of the no calling for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_no_calling">No calling.</string> - <!-- Screensaver overlay which displays the time. [CHAR LIMIT=20] --> - <string name="dream_complication_title_time">Time</string> - <!-- Screensaver overlay which displays the date. [CHAR LIMIT=20] --> - <string name="dream_complication_title_date">Date</string> - <!-- Screensaver overlay which displays the weather. [CHAR LIMIT=20] --> - <string name="dream_complication_title_weather">Weather</string> - <!-- Screensaver overlay which displays air quality. [CHAR LIMIT=20] --> - <string name="dream_complication_title_aqi">Air Quality</string> - <!-- Screensaver overlay which displays cast info. [CHAR LIMIT=20] --> - <string name="dream_complication_title_cast_info">Cast Info</string> - <!-- Screensaver overlay which displays home controls. [CHAR LIMIT=20] --> - <string name="dream_complication_title_home_controls">Home Controls</string> - <!-- Screensaver overlay which displays smartspace. [CHAR LIMIT=20] --> - <string name="dream_complication_title_smartspace">Smartspace</string> - <!-- Title for a screen allowing the user to choose a profile picture. [CHAR LIMIT=NONE] --> <string name="avatar_picker_title">Choose a profile picture</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index b9c4030d9d0e..a822e185479a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -600,6 +600,9 @@ public class Utils { * Returns the WifiInfo for the underlying WiFi network of the VCN network, returns null if the * input NetworkCapabilities is not for a VCN network with underlying WiFi network. * + * TODO(b/238425913): Move this method to be inside systemui not settingslib once we've migrated + * off of {@link WifiStatusTracker} and {@link NetworkControllerImpl}. + * * @param networkCapabilities NetworkCapabilities of the network. */ @Nullable diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java index 22586171e5cf..1606540da3fd 100644 --- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java +++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java @@ -17,7 +17,6 @@ package com.android.settingslib.dream; import android.annotation.IntDef; -import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -32,17 +31,14 @@ import android.os.ServiceManager; import android.provider.Settings; import android.service.dreams.DreamService; import android.service.dreams.IDreamManager; -import android.text.TextUtils; import android.util.Log; -import com.android.settingslib.R; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; -import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -64,18 +60,21 @@ public class DreamBackend { public String toString() { StringBuilder sb = new StringBuilder(DreamInfo.class.getSimpleName()); sb.append('[').append(caption); - if (isActive) + if (isActive) { sb.append(",active"); + } sb.append(',').append(componentName); - if (settingsComponentName != null) + if (settingsComponentName != null) { sb.append("settings=").append(settingsComponentName); + } return sb.append(']').toString(); } } @Retention(RetentionPolicy.SOURCE) @IntDef({WHILE_CHARGING, WHILE_DOCKED, EITHER, NEVER}) - public @interface WhenToDream {} + public @interface WhenToDream { + } public static final int WHILE_CHARGING = 0; public static final int WHILE_DOCKED = 1; @@ -96,7 +95,8 @@ public class DreamBackend { COMPLICATION_TYPE_SMARTSPACE }) @Retention(RetentionPolicy.SOURCE) - public @interface ComplicationType {} + public @interface ComplicationType { + } public static final int COMPLICATION_TYPE_TIME = 1; public static final int COMPLICATION_TYPE_DATE = 2; @@ -114,8 +114,6 @@ public class DreamBackend { private final boolean mDreamsActivatedOnDockByDefault; private final Set<ComponentName> mDisabledDreams; private final Set<Integer> mSupportedComplications; - private final Set<Integer> mDefaultEnabledComplications; - private static DreamBackend sInstance; public static DreamBackend getInstance(Context context) { @@ -147,13 +145,6 @@ public class DreamBackend { com.android.internal.R.array.config_supportedDreamComplications)) .boxed() .collect(Collectors.toSet()); - - mDefaultEnabledComplications = Arrays.stream(resources.getIntArray( - com.android.internal.R.array.config_dreamComplicationsEnabledByDefault)) - .boxed() - // A complication can only be enabled by default if it is also supported. - .filter(mSupportedComplications::contains) - .collect(Collectors.toSet()); } public List<DreamInfo> getDreamInfos() { @@ -251,11 +242,12 @@ public class DreamBackend { return null; } - public @WhenToDream int getWhenToDreamSetting() { + @WhenToDream + public int getWhenToDreamSetting() { return isActivatedOnDock() && isActivatedOnSleep() ? EITHER : isActivatedOnDock() ? WHILE_DOCKED - : isActivatedOnSleep() ? WHILE_CHARGING - : NEVER; + : isActivatedOnSleep() ? WHILE_CHARGING + : NEVER; } public void setWhenToDream(@WhenToDream int whenToDream) { @@ -283,98 +275,29 @@ public class DreamBackend { } } - /** Returns whether a particular complication is enabled */ - public boolean isComplicationEnabled(@ComplicationType int complication) { - return getEnabledComplications().contains(complication); - } - /** Gets all complications which have been enabled by the user. */ public Set<Integer> getEnabledComplications() { - final String enabledComplications = Settings.Secure.getString( - mContext.getContentResolver(), - Settings.Secure.SCREENSAVER_ENABLED_COMPLICATIONS); - - if (enabledComplications == null) { - return mDefaultEnabledComplications; - } - - return parseFromString(enabledComplications); + return getComplicationsEnabled() ? mSupportedComplications : Collections.emptySet(); } - /** Gets all dream complications which are supported on this device. **/ - public Set<Integer> getSupportedComplications() { - return mSupportedComplications; - } - - /** - * Enables or disables a particular dream complication. - * - * @param complicationType The dream complication to be enabled/disabled. - * @param value If true, the complication is enabled. Otherwise it is disabled. - */ - public void setComplicationEnabled(@ComplicationType int complicationType, boolean value) { - if (!mSupportedComplications.contains(complicationType)) return; - - Set<Integer> enabledComplications = getEnabledComplications(); - if (value) { - enabledComplications.add(complicationType); - } else { - enabledComplications.remove(complicationType); - } - - Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.SCREENSAVER_ENABLED_COMPLICATIONS, - convertToString(enabledComplications)); + /** Sets complication enabled state. */ + public void setComplicationsEnabled(boolean enabled) { + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED, enabled ? 1 : 0); } /** - * Gets the title of a particular complication type to be displayed to the user. If there - * is no title, null is returned. + * Gets whether complications are enabled on this device */ - @Nullable - public CharSequence getComplicationTitle(@ComplicationType int complicationType) { - int res = 0; - switch (complicationType) { - case COMPLICATION_TYPE_TIME: - res = R.string.dream_complication_title_time; - break; - case COMPLICATION_TYPE_DATE: - res = R.string.dream_complication_title_date; - break; - case COMPLICATION_TYPE_WEATHER: - res = R.string.dream_complication_title_weather; - break; - case COMPLICATION_TYPE_AIR_QUALITY: - res = R.string.dream_complication_title_aqi; - break; - case COMPLICATION_TYPE_CAST_INFO: - res = R.string.dream_complication_title_cast_info; - break; - case COMPLICATION_TYPE_HOME_CONTROLS: - res = R.string.dream_complication_title_home_controls; - break; - case COMPLICATION_TYPE_SMARTSPACE: - res = R.string.dream_complication_title_smartspace; - break; - default: - return null; - } - return mContext.getString(res); - } - - private static String convertToString(Set<Integer> set) { - return set.stream() - .map(String::valueOf) - .collect(Collectors.joining(",")); + public boolean getComplicationsEnabled() { + return Settings.Secure.getInt( + mContext.getContentResolver(), + Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED, 1) == 1; } - private static Set<Integer> parseFromString(String string) { - if (TextUtils.isEmpty(string)) { - return new HashSet<>(); - } - return Arrays.stream(string.split(",")) - .map(Integer::parseInt) - .collect(Collectors.toSet()); + /** Gets all dream complications which are supported on this device. **/ + public Set<Integer> getSupportedComplications() { + return mSupportedComplications; } public boolean isEnabled() { @@ -416,10 +339,11 @@ public class DreamBackend { public void setActiveDream(ComponentName dream) { logd("setActiveDream(%s)", dream); - if (mDreamManager == null) + if (mDreamManager == null) { return; + } try { - ComponentName[] dreams = { dream }; + ComponentName[] dreams = {dream}; mDreamManager.setDreamComponents(dream == null ? null : dreams); } catch (RemoteException e) { Log.w(TAG, "Failed to set active dream to " + dream, e); @@ -427,8 +351,9 @@ public class DreamBackend { } public ComponentName getActiveDream() { - if (mDreamManager == null) + if (mDreamManager == null) { return null; + } try { ComponentName[] dreams = mDreamManager.getDreamComponents(); return dreams != null && dreams.length > 0 ? dreams[0] : null; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java index 86f7850cf1f2..52b9227fb373 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; @@ -34,29 +35,35 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowSettings; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + @RunWith(RobolectricTestRunner.class) @Config(shadows = {ShadowSettings.ShadowSecure.class}) public final class DreamBackendTest { private static final int[] SUPPORTED_DREAM_COMPLICATIONS = {1, 2, 3}; - private static final int[] DEFAULT_DREAM_COMPLICATIONS = {1, 3, 4}; + private static final List<Integer> SUPPORTED_DREAM_COMPLICATIONS_LIST = Arrays.stream( + SUPPORTED_DREAM_COMPLICATIONS).boxed().collect( + Collectors.toList()); @Mock private Context mContext; + @Mock + private ContentResolver mMockResolver; private DreamBackend mBackend; @Before public void setUp() { MockitoAnnotations.initMocks(this); when(mContext.getApplicationContext()).thenReturn(mContext); + when(mContext.getContentResolver()).thenReturn(mMockResolver); final Resources res = mock(Resources.class); when(mContext.getResources()).thenReturn(res); when(res.getIntArray( com.android.internal.R.array.config_supportedDreamComplications)).thenReturn( SUPPORTED_DREAM_COMPLICATIONS); - when(res.getIntArray( - com.android.internal.R.array.config_dreamComplicationsEnabledByDefault)).thenReturn( - DEFAULT_DREAM_COMPLICATIONS); when(res.getStringArray( com.android.internal.R.array.config_disabledDreamComponents)).thenReturn( new String[]{}); @@ -69,31 +76,25 @@ public final class DreamBackendTest { } @Test - public void testSupportedComplications() { - assertThat(mBackend.getSupportedComplications()).containsExactly(1, 2, 3); - } - - @Test - public void testGetEnabledDreamComplications_default() { - assertThat(mBackend.getEnabledComplications()).containsExactly(1, 3); - } - - @Test - public void testEnableComplication() { - mBackend.setComplicationEnabled(/* complicationType= */ 2, true); - assertThat(mBackend.getEnabledComplications()).containsExactly(1, 2, 3); + public void testComplicationsEnabledByDefault() { + assertThat(mBackend.getComplicationsEnabled()).isTrue(); + assertThat(mBackend.getEnabledComplications()).containsExactlyElementsIn( + SUPPORTED_DREAM_COMPLICATIONS_LIST); } @Test - public void testEnableComplication_notSupported() { - mBackend.setComplicationEnabled(/* complicationType= */ 5, true); - assertThat(mBackend.getEnabledComplications()).containsExactly(1, 3); + public void testEnableComplicationExplicitly() { + mBackend.setComplicationsEnabled(true); + assertThat(mBackend.getEnabledComplications()).containsExactlyElementsIn( + SUPPORTED_DREAM_COMPLICATIONS_LIST); + assertThat(mBackend.getComplicationsEnabled()).isTrue(); } @Test - public void testDisableComplication() { - mBackend.setComplicationEnabled(/* complicationType= */ 1, false); - assertThat(mBackend.getEnabledComplications()).containsExactly(3); + public void testDisableComplications() { + mBackend.setComplicationsEnabled(false); + assertThat(mBackend.getEnabledComplications()).isEmpty(); + assertThat(mBackend.getComplicationsEnabled()).isFalse(); } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchBarTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchBarTest.java index d86bd014988e..24037caf4e6c 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchBarTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchBarTest.java @@ -16,6 +16,8 @@ package com.android.settingslib.widget; +import static android.graphics.text.LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE; + import static com.google.common.truth.Truth.assertThat; import android.content.Context; @@ -97,4 +99,14 @@ public class MainSwitchBarTest { assertThat(mBar.getVisibility()).isEqualTo(View.GONE); } + + @Test + public void setTitle_shouldSetCorrectLineBreakStyle() { + final String title = "title"; + + mBar.setTitle(title); + final TextView textView = ((TextView) mBar.findViewById(R.id.switch_text)); + + assertThat(textView.getLineBreakWordStyle()).isEqualTo(LINE_BREAK_WORD_STYLE_PHRASE); + } } diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index d1f10a640dbf..86d2eb85479f 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -182,6 +182,7 @@ public class SecureSettings { Settings.Secure.PEOPLE_STRIP, Settings.Secure.MEDIA_CONTROLS_RESUME, Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, + Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 4aadf72930aa..6a70230701f6 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -277,6 +277,7 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.PEOPLE_STRIP, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.MEDIA_CONTROLS_RESUME, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.MEDIA_CONTROLS_RECOMMENDATION, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.MEDIA_CONTROLS_LOCK_SCREEN, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_MODE, new InclusiveIntegerRangeValidator( Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN, diff --git a/packages/SoundPicker/res/values-sk/strings.xml b/packages/SoundPicker/res/values-sk/strings.xml index e7d444cb07e5..8ff6d12e3817 100644 --- a/packages/SoundPicker/res/values-sk/strings.xml +++ b/packages/SoundPicker/res/values-sk/strings.xml @@ -18,7 +18,7 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="ringtone_default" msgid="798836092118824500">"Predvolený tón zvonenia"</string> <string name="notification_sound_default" msgid="8133121186242636840">"Predvolený zvuk upozornení"</string> - <string name="alarm_sound_default" msgid="4787646764557462649">"Predvolený zvuk budíkov"</string> + <string name="alarm_sound_default" msgid="4787646764557462649">"Predvolený zvuk budíka"</string> <string name="add_ringtone_text" msgid="6642389991738337529">"Pridať tón zvonenia"</string> <string name="add_alarm_text" msgid="3545497316166999225">"Pridať budík"</string> <string name="add_notification_text" msgid="4431129543300614788">"Pridať upozornenie"</string> diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 6edf13addbca..78dea891bc12 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -85,6 +85,7 @@ <uses-permission android:name="android.permission.CONTROL_VPN" /> <uses-permission android:name="android.permission.PEERS_MAC_ADDRESS"/> <uses-permission android:name="android.permission.READ_WIFI_CREDENTIAL"/> + <uses-permission android:name="android.permission.NETWORK_STACK"/> <!-- Physical hardware --> <uses-permission android:name="android.permission.MANAGE_USB" /> <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" /> @@ -394,6 +395,11 @@ android:label="@string/screenshot_scroll_label" android:finishOnTaskLaunch="true" /> + <service android:name=".screenshot.ScreenshotProxyService" + android:permission="com.android.systemui.permission.SELF" + android:exported="false" /> + + <service android:name=".screenrecord.RecordingService" /> <receiver android:name=".SysuiRestartReceiver" diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS index 4b07eaf780e4..fa855ab495a2 100644 --- a/packages/SystemUI/OWNERS +++ b/packages/SystemUI/OWNERS @@ -20,6 +20,7 @@ cwren@google.com dupin@google.com ethibodeau@google.com evanlaird@google.com +florenceyang@google.com gwasserman@google.com hwwang@google.com hyunyoungs@google.com @@ -28,6 +29,8 @@ jamesoleary@google.com jbolinger@google.com jdemeulenaere@google.com jeffdq@google.com +jernej@google.com +jglazier@google.com jjaggi@google.com jonmiranda@google.com joshtrask@google.com @@ -49,8 +52,11 @@ nickchameyev@google.com nicomazz@google.com ogunwale@google.com peanutbutter@google.com +peskal@google.com pinyaoting@google.com pixel@google.com +pomini@google.com +rahulbanerjee@google.com roosa@google.com santie@google.com shanh@google.com @@ -68,12 +74,10 @@ twickham@google.com vadimt@google.com victortulias@google.com winsonc@google.com -yurilin@google.com xuqiu@google.com +yuandizhou@google.com +yurilin@google.com zakcohen@google.com -jernej@google.com -jglazier@google.com -peskal@google.com #Android Auto hseog@google.com diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp index 40218de94258..325ede613de8 100644 --- a/packages/SystemUI/compose/features/Android.bp +++ b/packages/SystemUI/compose/features/Android.bp @@ -30,6 +30,7 @@ android_library { ], static_libs: [ + "SystemUI-core", "SystemUIComposeCore", "androidx.compose.runtime_runtime", diff --git a/packages/SystemUI/compose/features/AndroidManifest.xml b/packages/SystemUI/compose/features/AndroidManifest.xml index 0aea99d4e960..eada40e6a40d 100644 --- a/packages/SystemUI/compose/features/AndroidManifest.xml +++ b/packages/SystemUI/compose/features/AndroidManifest.xml @@ -16,7 +16,38 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.systemui.compose.features"> - + xmlns:tools="http://schemas.android.com/tools" + package="com.android.systemui.compose.features"> + <application + android:name="android.app.Application" + android:appComponentFactory="androidx.core.app.AppComponentFactory" + tools:replace="android:name,android:appComponentFactory"> + <!-- Disable providers from SystemUI --> + <provider android:name="com.android.systemui.keyguard.KeyguardSliceProvider" + android:authorities="com.android.systemui.test.keyguard.disabled" + android:enabled="false" + tools:replace="android:authorities" + tools:node="remove" /> + <provider android:name="com.google.android.systemui.keyguard.KeyguardSliceProviderGoogle" + android:authorities="com.android.systemui.test.keyguard.disabled" + android:enabled="false" + tools:replace="android:authorities" + tools:node="remove" /> + <provider android:name="com.android.keyguard.clock.ClockOptionsProvider" + android:authorities="com.android.systemui.test.keyguard.clock.disabled" + android:enabled="false" + tools:replace="android:authorities" + tools:node="remove" /> + <provider android:name="com.android.systemui.people.PeopleProvider" + android:authorities="com.android.systemui.test.people.disabled" + android:enabled="false" + tools:replace="android:authorities" + tools:node="remove" /> + <provider android:name="androidx.core.content.FileProvider" + android:authorities="com.android.systemui.test.fileprovider.disabled" + android:enabled="false" + tools:replace="android:authorities" + tools:node="remove"/> + </application> </manifest> diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt new file mode 100644 index 000000000000..2bf1937a1c1e --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.people.ui.compose + +import android.annotation.StringRes +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +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.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Divider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.R +import com.android.systemui.compose.theme.LocalAndroidColorScheme +import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel +import com.android.systemui.people.ui.viewmodel.PeopleViewModel +import kotlinx.coroutines.flow.collect + +/** + * Compose the screen associated to a [PeopleViewModel]. + * + * @param viewModel the [PeopleViewModel] that should be composed. + * @param onResult the callback called with the result of this screen. Callers should usually finish + * the Activity/Fragment/View hosting this Composable once a result is available. + */ +@Composable +fun PeopleScreen( + viewModel: PeopleViewModel, + onResult: (PeopleViewModel.Result) -> Unit, +) { + val priorityTiles by viewModel.priorityTiles.collectAsState() + val recentTiles by viewModel.recentTiles.collectAsState() + + // Make sure to refresh the tiles/conversations when the lifecycle is resumed, so that it + // updates them when going back to the Activity after leaving it. + val lifecycleOwner = LocalLifecycleOwner.current + LaunchedEffect(lifecycleOwner, viewModel) { + lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { + viewModel.onTileRefreshRequested() + } + } + + // Call [onResult] this activity when the ViewModel tells us so. + LaunchedEffect(viewModel.result) { + viewModel.result.collect { result -> + if (result != null) { + viewModel.clearResult() + onResult(result) + } + } + } + + // Make sure to use the Android colors and not the default Material3 colors to have the exact + // same colors as the View implementation. + val androidColors = LocalAndroidColorScheme.current + Surface( + color = androidColors.colorBackground, + contentColor = androidColors.textColorPrimary, + modifier = Modifier.fillMaxSize(), + ) { + if (priorityTiles.isNotEmpty() || recentTiles.isNotEmpty()) { + PeopleScreenWithConversations(priorityTiles, recentTiles, viewModel::onTileClicked) + } else { + PeopleScreenEmpty(viewModel::onUserJourneyCancelled) + } + } +} + +@Composable +private fun PeopleScreenWithConversations( + priorityTiles: List<PeopleTileViewModel>, + recentTiles: List<PeopleTileViewModel>, + onTileClicked: (PeopleTileViewModel) -> Unit, +) { + Column { + Column( + Modifier.fillMaxWidth().padding(PeopleSpacePadding), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + stringResource(R.string.select_conversation_title), + style = MaterialTheme.typography.headlineSmall, + textAlign = TextAlign.Center, + ) + + Spacer(Modifier.height(24.dp)) + + Text( + stringResource(R.string.select_conversation_text), + Modifier.padding(horizontal = 24.dp), + style = MaterialTheme.typography.bodyLarge, + textAlign = TextAlign.Center, + ) + } + + LazyColumn( + Modifier.fillMaxWidth(), + contentPadding = + PaddingValues( + top = 16.dp, + bottom = PeopleSpacePadding, + start = 8.dp, + end = 8.dp, + ) + ) { + ConversationList(R.string.priority_conversations, priorityTiles, onTileClicked) + item { Spacer(Modifier.height(35.dp)) } + ConversationList(R.string.recent_conversations, recentTiles, onTileClicked) + } + } +} + +private fun LazyListScope.ConversationList( + @StringRes headerTextResource: Int, + tiles: List<PeopleTileViewModel>, + onTileClicked: (PeopleTileViewModel) -> Unit +) { + item { + Text( + stringResource(headerTextResource), + Modifier.padding(start = 16.dp), + style = MaterialTheme.typography.labelLarge, + color = LocalAndroidColorScheme.current.colorAccentPrimaryVariant, + ) + + Spacer(Modifier.height(10.dp)) + } + + tiles.forEachIndexed { index, tile -> + if (index > 0) { + item { + Divider( + color = LocalAndroidColorScheme.current.colorBackground, + thickness = 2.dp, + ) + } + } + + item(tile.key.toString()) { + Tile( + tile, + onTileClicked, + withTopCornerRadius = index == 0, + withBottomCornerRadius = index == tiles.lastIndex, + ) + } + } +} + +@Composable +private fun Tile( + tile: PeopleTileViewModel, + onTileClicked: (PeopleTileViewModel) -> Unit, + withTopCornerRadius: Boolean, + withBottomCornerRadius: Boolean, +) { + val androidColors = LocalAndroidColorScheme.current + val cornerRadius = dimensionResource(R.dimen.people_space_widget_radius) + val topCornerRadius = if (withTopCornerRadius) cornerRadius else 0.dp + val bottomCornerRadius = if (withBottomCornerRadius) cornerRadius else 0.dp + + Surface( + color = androidColors.colorSurface, + contentColor = androidColors.textColorPrimary, + shape = + RoundedCornerShape( + topStart = topCornerRadius, + topEnd = topCornerRadius, + bottomStart = bottomCornerRadius, + bottomEnd = bottomCornerRadius, + ), + ) { + Row( + Modifier.fillMaxWidth().clickable { onTileClicked(tile) }.padding(12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Image( + tile.icon.asImageBitmap(), + // TODO(b/238993727): Add a content description. + contentDescription = null, + Modifier.size(dimensionResource(R.dimen.avatar_size_for_medium)), + ) + + Text( + tile.username ?: "", + Modifier.padding(horizontal = 16.dp), + style = MaterialTheme.typography.titleLarge, + ) + } + } +} + +/** The padding applied to the PeopleSpace screen. */ +internal val PeopleSpacePadding = 24.dp diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreenEmpty.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreenEmpty.kt new file mode 100644 index 000000000000..5c9358f99858 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreenEmpty.kt @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.people.ui.compose + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.defaultMinSize +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.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import com.android.systemui.R +import com.android.systemui.compose.theme.LocalAndroidColorScheme + +@Composable +internal fun PeopleScreenEmpty( + onGotItClicked: () -> Unit, +) { + Column( + Modifier.fillMaxSize().padding(PeopleSpacePadding), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + stringResource(R.string.select_conversation_title), + style = MaterialTheme.typography.headlineSmall, + textAlign = TextAlign.Center, + ) + + Spacer(Modifier.height(50.dp)) + + Text( + stringResource(R.string.no_conversations_text), + style = MaterialTheme.typography.bodyLarge, + textAlign = TextAlign.Center, + ) + + Spacer(Modifier.weight(1f)) + ExampleTile() + Spacer(Modifier.weight(1f)) + + val androidColors = LocalAndroidColorScheme.current + Button( + onGotItClicked, + Modifier.fillMaxWidth().defaultMinSize(minHeight = 56.dp), + colors = + ButtonDefaults.buttonColors( + containerColor = androidColors.colorAccentPrimary, + contentColor = androidColors.textColorOnAccent, + ) + ) { Text(stringResource(R.string.got_it)) } + } +} + +@Composable +private fun ExampleTile() { + val androidColors = LocalAndroidColorScheme.current + Surface( + shape = RoundedCornerShape(28.dp), + color = androidColors.colorSurface, + contentColor = androidColors.textColorPrimary, + ) { + Row( + Modifier.padding(vertical = 20.dp, horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + // TODO(b/238993727): Add a content description. + Image( + painterResource(R.drawable.ic_avatar_with_badge), + contentDescription = null, + Modifier.size(40.dp), + ) + Spacer(Modifier.height(2.dp)) + Text( + stringResource(R.string.empty_user_name), + style = MaterialTheme.typography.labelMedium, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + + Spacer(Modifier.width(24.dp)) + + Text( + stringResource(R.string.empty_status), + style = MaterialTheme.typography.labelMedium, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + } +} diff --git a/packages/SystemUI/compose/gallery/Android.bp b/packages/SystemUI/compose/gallery/Android.bp index 40504dc30c33..b0f5cc112120 100644 --- a/packages/SystemUI/compose/gallery/Android.bp +++ b/packages/SystemUI/compose/gallery/Android.bp @@ -27,6 +27,7 @@ android_library { srcs: [ "src/**/*.kt", + ":SystemUI-tests-utils", ], resource_dirs: [ @@ -45,6 +46,14 @@ android_library { "androidx.navigation_navigation-compose", "androidx.appcompat_appcompat", + + // TODO(b/240431193): Remove the dependencies and depend on + // SystemUI-test-utils directly. + "androidx.test.runner", + "mockito-target-extended-minus-junit4", + "testables", + "truth-prebuilt", + "androidx.test.uiautomator", ], kotlincflags: ["-Xjvm-default=all"], diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt index c341867bfb59..bb98fb350a2e 100644 --- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt +++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt @@ -13,7 +13,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -33,6 +33,28 @@ object GalleryAppScreens { val AndroidColors = ChildScreen("android_colors") { AndroidColorsScreen() } val ExampleFeature = ChildScreen("example_feature") { ExampleFeatureScreen() } + val PeopleEmpty = + ChildScreen("people_empty") { navController -> + EmptyPeopleScreen(onResult = { navController.popBackStack() }) + } + val PeopleFew = + ChildScreen("people_few") { navController -> + FewPeopleScreen(onResult = { navController.popBackStack() }) + } + val PeopleFull = + ChildScreen("people_full") { navController -> + FullPeopleScreen(onResult = { navController.popBackStack() }) + } + val People = + ParentScreen( + "people", + mapOf( + "Empty" to PeopleEmpty, + "Few" to PeopleFew, + "Full" to PeopleFull, + ) + ) + val Home = ParentScreen( "home", @@ -41,20 +63,21 @@ object GalleryAppScreens { "Material colors" to MaterialColors, "Android colors" to AndroidColors, "Example feature" to ExampleFeature, + "People" to People, ) ) } /** The main content of the app, that shows [GalleryAppScreens.Home] by default. */ @Composable -private fun MainContent() { +private fun MainContent(onControlToggleRequested: () -> Unit) { Box(Modifier.fillMaxSize()) { val navController = rememberNavController() NavHost( navController = navController, startDestination = GalleryAppScreens.Home.identifier, ) { - screen(GalleryAppScreens.Home, navController) + screen(GalleryAppScreens.Home, navController, onControlToggleRequested) } } } @@ -69,7 +92,7 @@ fun GalleryApp( onChangeTheme: () -> Unit, ) { val systemFontScale = LocalDensity.current.fontScale - var fontScale: FontScale by remember { + var fontScale: FontScale by rememberSaveable { mutableStateOf( FontScale.values().firstOrNull { it.scale == systemFontScale } ?: FontScale.Normal ) @@ -87,7 +110,7 @@ fun GalleryApp( } val systemLayoutDirection = LocalLayoutDirection.current - var layoutDirection by remember { mutableStateOf(systemLayoutDirection) } + var layoutDirection by rememberSaveable { mutableStateOf(systemLayoutDirection) } val onChangeLayoutDirection = { layoutDirection = when (layoutDirection) { @@ -105,19 +128,24 @@ fun GalleryApp( Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background, ) { - Column(Modifier.fillMaxSize().systemBarsPadding().padding(16.dp)) { - ConfigurationControls( - theme, - fontScale, - layoutDirection, - onChangeTheme, - onChangeLayoutDirection, - onChangeFontScale, - ) + Column(Modifier.fillMaxSize().systemBarsPadding()) { + var showControls by rememberSaveable { mutableStateOf(true) } + + if (showControls) { + ConfigurationControls( + theme, + fontScale, + layoutDirection, + onChangeTheme, + onChangeLayoutDirection, + onChangeFontScale, + Modifier.padding(horizontal = 16.dp), + ) - Spacer(Modifier.height(4.dp)) + Spacer(Modifier.height(4.dp)) + } - MainContent() + MainContent(onControlToggleRequested = { showControls = !showControls }) } } } diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/PeopleScreen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/PeopleScreen.kt new file mode 100644 index 000000000000..2f0df7790ffd --- /dev/null +++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/PeopleScreen.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.compose.gallery + +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext +import com.android.systemui.people.emptyPeopleSpaceViewModel +import com.android.systemui.people.fewPeopleSpaceViewModel +import com.android.systemui.people.fullPeopleSpaceViewModel +import com.android.systemui.people.ui.compose.PeopleScreen +import com.android.systemui.people.ui.viewmodel.PeopleViewModel + +@Composable +fun EmptyPeopleScreen(onResult: (PeopleViewModel.Result) -> Unit) { + val context = LocalContext.current.applicationContext + val viewModel = emptyPeopleSpaceViewModel(context) + PeopleScreen(viewModel, onResult) +} + +@Composable +fun FewPeopleScreen(onResult: (PeopleViewModel.Result) -> Unit) { + val context = LocalContext.current.applicationContext + val viewModel = fewPeopleSpaceViewModel(context) + PeopleScreen(viewModel, onResult) +} + +@Composable +fun FullPeopleScreen(onResult: (PeopleViewModel.Result) -> Unit) { + val context = LocalContext.current.applicationContext + val viewModel = fullPeopleSpaceViewModel(context) + PeopleScreen(viewModel, onResult) +} diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt index 467dac044b79..d7d0d721b01c 100644 --- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt +++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt @@ -52,17 +52,29 @@ class ChildScreen( ) : Screen(identifier) /** Create the navigation graph for [screen]. */ -fun NavGraphBuilder.screen(screen: Screen, navController: NavController) { +fun NavGraphBuilder.screen( + screen: Screen, + navController: NavController, + onControlToggleRequested: () -> Unit, +) { when (screen) { is ChildScreen -> composable(screen.identifier) { screen.content(navController) } is ParentScreen -> { val menuRoute = "${screen.identifier}_menu" navigation(startDestination = menuRoute, route = screen.identifier) { // The menu to navigate to one of the children screens. - composable(menuRoute) { ScreenMenu(screen, navController) } + composable(menuRoute) { + ScreenMenu(screen, navController, onControlToggleRequested) + } // The content of the child screens. - screen.children.forEach { (_, child) -> screen(child, navController) } + screen.children.forEach { (_, child) -> + screen( + child, + navController, + onControlToggleRequested, + ) + } } } } @@ -72,8 +84,27 @@ fun NavGraphBuilder.screen(screen: Screen, navController: NavController) { private fun ScreenMenu( screen: ParentScreen, navController: NavController, + onControlToggleRequested: () -> Unit, ) { - LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) { + LazyColumn( + Modifier.padding(horizontal = 16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + item { + Surface( + Modifier.fillMaxWidth(), + color = MaterialTheme.colorScheme.tertiaryContainer, + shape = CircleShape, + ) { + Column( + Modifier.clickable(onClick = onControlToggleRequested).padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text("Toggle controls") + } + } + } + screen.children.forEach { (name, child) -> item { Surface( diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/people/Fakes.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/people/Fakes.kt new file mode 100644 index 000000000000..0966c3233ad5 --- /dev/null +++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/people/Fakes.kt @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.people + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.graphics.drawable.Icon +import androidx.core.graphics.drawable.toIcon +import com.android.systemui.R +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.people.data.model.PeopleTileModel +import com.android.systemui.people.ui.viewmodel.PeopleViewModel +import com.android.systemui.people.widget.PeopleTileKey + +/** A [PeopleViewModel] that does not have any conversations. */ +fun emptyPeopleSpaceViewModel(@Application context: Context): PeopleViewModel { + return fakePeopleSpaceViewModel(context, emptyList(), emptyList()) +} + +/** A [PeopleViewModel] that has a few conversations. */ +fun fewPeopleSpaceViewModel(@Application context: Context): PeopleViewModel { + return fakePeopleSpaceViewModel( + context, + priorityTiles = + listOf( + fakeTile(context, id = "0", Color.RED, "Priority"), + fakeTile(context, id = "1", Color.BLUE, "Priority NewStory", hasNewStory = true), + ), + recentTiles = + listOf( + fakeTile(context, id = "2", Color.GREEN, "Recent Important", isImportant = true), + fakeTile(context, id = "3", Color.CYAN, "Recent DndBlocking", isDndBlocking = true), + ), + ) +} + +/** A [PeopleViewModel] that has a lot of conversations. */ +fun fullPeopleSpaceViewModel(@Application context: Context): PeopleViewModel { + return fakePeopleSpaceViewModel( + context, + priorityTiles = + listOf( + fakeTile(context, id = "0", Color.RED, "Priority"), + fakeTile(context, id = "1", Color.BLUE, "Priority NewStory", hasNewStory = true), + fakeTile(context, id = "2", Color.GREEN, "Priority Important", isImportant = true), + fakeTile( + context, + id = "3", + Color.CYAN, + "Priority DndBlocking", + isDndBlocking = true, + ), + fakeTile( + context, + id = "4", + Color.MAGENTA, + "Priority NewStory Important", + hasNewStory = true, + isImportant = true, + ), + ), + recentTiles = + listOf( + fakeTile( + context, + id = "5", + Color.RED, + "Recent NewStory DndBlocking", + hasNewStory = true, + isDndBlocking = true, + ), + fakeTile( + context, + id = "6", + Color.BLUE, + "Recent Important DndBlocking", + isImportant = true, + isDndBlocking = true, + ), + fakeTile( + context, + id = "7", + Color.GREEN, + "Recent NewStory Important DndBlocking", + hasNewStory = true, + isImportant = true, + isDndBlocking = true, + ), + fakeTile(context, id = "8", Color.CYAN, "Recent"), + fakeTile(context, id = "9", Color.MAGENTA, "Recent"), + ), + ) +} + +private fun fakePeopleSpaceViewModel( + @Application context: Context, + priorityTiles: List<PeopleTileModel>, + recentTiles: List<PeopleTileModel>, +): PeopleViewModel { + return PeopleViewModel( + context, + FakePeopleTileRepository(priorityTiles, recentTiles), + FakePeopleWidgetRepository(), + ) +} + +private fun fakeTile( + @Application context: Context, + id: String, + iconColor: Int, + username: String, + hasNewStory: Boolean = false, + isImportant: Boolean = false, + isDndBlocking: Boolean = false +): PeopleTileModel { + return PeopleTileModel( + PeopleTileKey(id, /* userId= */ 0, /* packageName */ ""), + username, + fakeUserIcon(context, iconColor), + hasNewStory, + isImportant, + isDndBlocking, + ) +} + +private fun fakeUserIcon(@Application context: Context, color: Int): Icon { + val size = context.resources.getDimensionPixelSize(R.dimen.avatar_size_for_medium) + val bitmap = + Bitmap.createBitmap( + size, + size, + Bitmap.Config.ARGB_8888, + ) + val canvas = Canvas(bitmap) + val paint = Paint().apply { this.color = color } + val radius = size / 2f + canvas.drawCircle(/* cx= */ radius, /* cy= */ radius, /* radius= */ radius, paint) + return bitmap.toIcon() +} diff --git a/packages/SystemUI/res-keyguard/values-land/dimens.xml b/packages/SystemUI/res-keyguard/values-land/dimens.xml index 4e92884f39f3..a4e7a5f12db4 100644 --- a/packages/SystemUI/res-keyguard/values-land/dimens.xml +++ b/packages/SystemUI/res-keyguard/values-land/dimens.xml @@ -22,7 +22,6 @@ <dimen name="keyguard_eca_top_margin">0dp</dimen> <dimen name="keyguard_eca_bottom_margin">2dp</dimen> <dimen name="keyguard_password_height">26dp</dimen> - <dimen name="num_pad_entry_row_margin_bottom">0dp</dimen> <!-- The size of PIN text in the PIN unlock method. --> <integer name="scaled_password_text_size">26</integer> diff --git a/packages/SystemUI/res-keyguard/values-sw360dp-land/dimens.xml b/packages/SystemUI/res-keyguard/values-sw360dp-land/dimens.xml index f465be4f5228..0421135b31a5 100644 --- a/packages/SystemUI/res-keyguard/values-sw360dp-land/dimens.xml +++ b/packages/SystemUI/res-keyguard/values-sw360dp-land/dimens.xml @@ -22,7 +22,6 @@ <dimen name="keyguard_eca_top_margin">4dp</dimen> <dimen name="keyguard_eca_bottom_margin">4dp</dimen> <dimen name="keyguard_password_height">50dp</dimen> - <dimen name="num_pad_entry_row_margin_bottom">4dp</dimen> <!-- The size of PIN text in the PIN unlock method. --> <integer name="scaled_password_text_size">40</integer> diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index acf3e4dcf02a..32871f0abb4f 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -86,7 +86,7 @@ <!-- Spacing around each button used for PIN view --> <dimen name="num_pad_key_width">72dp</dimen> - <dimen name="num_pad_entry_row_margin_bottom">16dp</dimen> + <dimen name="num_pad_entry_row_margin_bottom">12dp</dimen> <dimen name="num_pad_row_margin_bottom">6dp</dimen> <dimen name="num_pad_key_margin_end">12dp</dimen> diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml index babe924c4615..8fa22048cd97 100644 --- a/packages/SystemUI/res-keyguard/values/strings.xml +++ b/packages/SystemUI/res-keyguard/values/strings.xml @@ -37,8 +37,8 @@ <!-- When the lock screen is showing and the phone plugged in, and the battery is not fully charged, say that it's wirelessly charging. [CHAR LIMIT=50] --> <string name="keyguard_plugged_in_wireless"><xliff:g id="percentage" example="20%">%s</xliff:g> • Charging wirelessly</string> - <!-- When the lock screen is showing and the phone plugged in, and the battery is not fully charged, say that it's dock charging. [CHAR LIMIT=50] --> - <string name="keyguard_plugged_in_dock"><xliff:g id="percentage" example="20%">%s</xliff:g> • Charging Dock</string> + <!-- When the lock screen is showing and the phone plugged in, and the battery is not fully charged, say that it's charging. [CHAR LIMIT=50] --> + <string name="keyguard_plugged_in_dock"><xliff:g id="percentage" example="20%">%s</xliff:g> • Charging</string> <!-- When the lock screen is showing and the phone plugged in, and the battery is not fully charged, say that it's charging. --> @@ -53,7 +53,7 @@ <string name="keyguard_plugged_in_charging_slowly"><xliff:g id="percentage">%s</xliff:g> • Charging slowly</string> <!-- When the lock screen is showing and the phone plugged in, and the defend mode is triggered, say that charging is temporarily limited. --> - <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging temporarily limited</string> + <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging is paused to protect battery</string> <!-- On the keyguard screen, when pattern lock is disabled, only tell them to press menu to unlock. This is shown in small font at the bottom. --> <string name="keyguard_instructions_when_pattern_disabled">Press Menu to unlock.</string> diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml index 7a57293f58bd..3bf44a4b85a4 100644 --- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml +++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml @@ -20,10 +20,11 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:fontFamily="@font/clock" - android:includeFontPadding="false" android:textColor="@android:color/white" android:format12Hour="@string/dream_time_complication_12_hr_time_format" android:format24Hour="@string/dream_time_complication_24_hr_time_format" android:shadowColor="@color/keyguard_shadow_color" android:shadowRadius="?attr/shadowRadius" + android:fontFeatureSettings="pnum, lnum" + android:letterSpacing="0.02" android:textSize="@dimen/dream_overlay_complication_clock_time_text_size"/> diff --git a/packages/SystemUI/res/layout/new_status_bar_wifi_group.xml b/packages/SystemUI/res/layout/new_status_bar_wifi_group.xml new file mode 100644 index 000000000000..753ba2f21faf --- /dev/null +++ b/packages/SystemUI/res/layout/new_status_bar_wifi_group.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2022, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> +<com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/wifi_combo" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="center_vertical" > + + <include layout="@layout/status_bar_wifi_group_inner" /> + +</com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView> diff --git a/packages/SystemUI/res/layout/status_bar_wifi_group.xml b/packages/SystemUI/res/layout/status_bar_wifi_group.xml index 35cce25d45a7..6cb6993bb762 100644 --- a/packages/SystemUI/res/layout/status_bar_wifi_group.xml +++ b/packages/SystemUI/res/layout/status_bar_wifi_group.xml @@ -18,70 +18,11 @@ --> <com.android.systemui.statusbar.StatusBarWifiView xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:systemui="http://schemas.android.com/apk/res-auto" android:id="@+id/wifi_combo" android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center_vertical" > - <com.android.keyguard.AlphaOptimizedLinearLayout - android:id="@+id/wifi_group" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:gravity="center_vertical" - android:layout_marginStart="2.5dp" - > - <FrameLayout - android:id="@+id/inout_container" - android:layout_height="17dp" - android:layout_width="wrap_content" - android:gravity="center_vertical" > - <ImageView - android:id="@+id/wifi_in" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:src="@drawable/ic_activity_down" - android:visibility="gone" - android:paddingEnd="2dp" - /> - <ImageView - android:id="@+id/wifi_out" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:src="@drawable/ic_activity_up" - android:paddingEnd="2dp" - android:visibility="gone" - /> - </FrameLayout> - <FrameLayout - android:id="@+id/wifi_combo" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:gravity="center_vertical" > - <com.android.systemui.statusbar.AlphaOptimizedImageView - android:id="@+id/wifi_signal" - android:layout_height="@dimen/status_bar_wifi_signal_size" - android:layout_width="@dimen/status_bar_wifi_signal_size" /> - </FrameLayout> + <include layout="@layout/status_bar_wifi_group_inner" /> - <View - android:id="@+id/wifi_signal_spacer" - android:layout_width="@dimen/status_bar_wifi_signal_spacer_width" - android:layout_height="4dp" - android:visibility="gone" /> - - <!-- Looks like CarStatusBar uses this... --> - <ViewStub - android:id="@+id/connected_device_signals_stub" - android:layout="@layout/connected_device_signal" - android:layout_width="wrap_content" - android:layout_height="wrap_content" /> - - <View - android:id="@+id/wifi_airplane_spacer" - android:layout_width="@dimen/status_bar_airplane_spacer_width" - android:layout_height="4dp" - android:visibility="gone" - /> - </com.android.keyguard.AlphaOptimizedLinearLayout> -</com.android.systemui.statusbar.StatusBarWifiView> +</com.android.systemui.statusbar.StatusBarWifiView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml b/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml new file mode 100644 index 000000000000..0ea0653ab89f --- /dev/null +++ b/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2022, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<merge xmlns:android="http://schemas.android.com/apk/res/android"> + + <com.android.keyguard.AlphaOptimizedLinearLayout + android:id="@+id/wifi_group" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="center_vertical" + android:layout_marginStart="2.5dp" + > + <FrameLayout + android:id="@+id/inout_container" + android:layout_height="17dp" + android:layout_width="wrap_content" + android:gravity="center_vertical" > + <ImageView + android:id="@+id/wifi_in" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:src="@drawable/ic_activity_down" + android:visibility="gone" + android:paddingEnd="2dp" + /> + <ImageView + android:id="@+id/wifi_out" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:src="@drawable/ic_activity_up" + android:paddingEnd="2dp" + android:visibility="gone" + /> + </FrameLayout> + <FrameLayout + android:id="@+id/wifi_combo" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:gravity="center_vertical" > + <com.android.systemui.statusbar.AlphaOptimizedImageView + android:id="@+id/wifi_signal" + android:layout_height="@dimen/status_bar_wifi_signal_size" + android:layout_width="@dimen/status_bar_wifi_signal_size" /> + </FrameLayout> + + <View + android:id="@+id/wifi_signal_spacer" + android:layout_width="@dimen/status_bar_wifi_signal_spacer_width" + android:layout_height="4dp" + android:visibility="gone" /> + + <!-- Looks like CarStatusBar uses this... --> + <ViewStub + android:id="@+id/connected_device_signals_stub" + android:layout="@layout/connected_device_signal" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + <View + android:id="@+id/wifi_airplane_spacer" + android:layout_width="@dimen/status_bar_airplane_spacer_width" + android:layout_height="4dp" + android:visibility="gone" + /> + </com.android.keyguard.AlphaOptimizedLinearLayout> +</merge> diff --git a/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_success_lottie.json b/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_success_lottie.json new file mode 100644 index 000000000000..c5ed827de0d1 --- /dev/null +++ b/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_success_lottie.json @@ -0,0 +1 @@ +{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":80,"h":80,"nm":"RearFPS_error_to_success","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".green200","cl":"green200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[28,47,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-10.556,-9.889],[7.444,6.555],[34.597,-20.486]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.658823529412,0.854901960784,0.709803921569,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":10,"s":[0]},{"t":20,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":10,"op":910,"st":10,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[39.95,40,0],"ix":2,"l":2},"a":{"a":0,"k":[30,30,0],"ix":1,"l":2},"s":{"a":0,"k":[120,120,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.721,-7.982],[1.721,-7.982],[1.721,7.5],[-1.721,7.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30.002,32.488],"ix":2},"a":{"a":0,"k":[0.002,7.488],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.659,0.6],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":0,"s":[100,100]},{"i":{"x":[0.6,0.92],"y":[1,1.096]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":4,"s":[100,110]},{"t":10,"s":[100,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top!","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.681,-1.25],[1.681,-1.25],[1.681,2.213],[-1.681,2.213]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30,38.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.6,0.6],"y":[1,1]},"o":{"x":[0.853,0.853],"y":[0,0]},"t":0,"s":[100,100]},{"i":{"x":[0.92,0.92],"y":[1.06,1.06]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":4,"s":[110,110]},{"t":10,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom!","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":86,"st":-30,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".green200","cl":"green200","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":15,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[93.5,93.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.658823529412,0.854901960784,0.709803921569,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":15,"s":[100]}],"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[0]},{"t":20,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":10,"op":21,"st":10,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[93.5,93.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[4]},{"t":10,"s":[0]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":10,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file diff --git a/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_success_lottie.json b/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_success_lottie.json new file mode 100644 index 000000000000..3eb95ef1a718 --- /dev/null +++ b/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_success_lottie.json @@ -0,0 +1 @@ +{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":80,"h":80,"nm":"RearFPS_fingerprint_to_success","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".green200","cl":"green200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[28,47,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-10.556,-9.889],[7.444,6.555],[34.597,-20.486]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.658823529412,0.854901960784,0.709803921569,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":10,"s":[0]},{"t":20,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":10,"op":910,"st":10,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".green200","cl":"green200","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":15,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[93.5,93.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.658823529412,0.854901960784,0.709803921569,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":15,"s":[100]}],"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[0]},{"t":20,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":10,"op":21,"st":10,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[100]},{"t":10,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40.091,40,0],"ix":2,"l":2},"a":{"a":0,"k":[19.341,24.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-1.701,0.42],[-1.757,0],[-1.577,-0.381],[-1.485,-0.816]],"o":[[1.455,-0.799],[1.608,-0.397],[1.719,0],[1.739,0.42],[0,0]],"v":[[-9.818,1.227],[-5.064,-0.618],[0,-1.227],[4.96,-0.643],[9.818,1.227]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,7.477],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-2.446,1.161],[-1.168,0.275],[-1.439,0],[-1.301,-0.304],[-1.225,-0.66],[-1.11,-1.844]],"o":[[1.23,-2.044],[1.024,-0.486],[1.312,-0.31],[1.425,0],[1.454,0.34],[2.122,1.143],[0,0]],"v":[[-13.091,3.273],[-7.438,-1.646],[-4.14,-2.797],[0,-3.273],[4.104,-2.805],[8.141,-1.29],[13.091,3.273]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,16.069],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Mid Top","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-6.53,0],[0,-5.793],[0,0],[2.159,0],[0.59,1.489],[0,0],[1.587,0],[0,-2.16],[-0.81,-1.363],[-0.844,-0.674],[0,0]],"o":[[-0.753,-2.095],[0,-5.793],[6.529,0],[0,0],[0,2.16],[-1.604,0],[0,0],[-0.589,-1.489],[-2.161,0],[0,1.62],[0.54,0.909],[0,0],[0,0]],"v":[[-10.702,5.728],[-11.454,1.506],[0.001,-9],[11.454,1.506],[11.454,1.817],[7.544,5.728],[3.926,3.273],[2.618,0],[-0.997,-2.454],[-4.91,1.457],[-3.657,6.014],[-1.57,8.412],[-0.818,9]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,28.341],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Inside to dot ","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.307,-0.561],[0.894,-0.16],[0.706,0],[0.844,0.193],[0.728,0.334],[0.967,0.901]],"o":[[-1.038,0.967],[-0.817,0.351],[-0.673,0.12],[-0.9,0],[-0.794,-0.182],[-1.203,-0.551],[0,0]],"v":[[8.182,-0.386],[4.642,1.931],[2.07,2.703],[-0.001,2.886],[-2.621,2.591],[-4.909,1.813],[-8.182,-0.386]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,40.614],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey700","cl":"grey700","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.278431385756,0.278431385756,0.278431385756,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[100]},{"t":20,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index f34472158d1b..229858413f99 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1459,7 +1459,7 @@ <dimen name="dream_overlay_status_bar_extra_margin">16dp</dimen> <!-- Dream overlay complications related dimensions --> - <dimen name="dream_overlay_complication_clock_time_text_size">100sp</dimen> + <dimen name="dream_overlay_complication_clock_time_text_size">86sp</dimen> <dimen name="dream_overlay_complication_clock_subtitle_text_size">24sp</dimen> <dimen name="dream_overlay_complication_preview_text_size">36sp</dimen> <dimen name="dream_overlay_complication_preview_icon_padding">28dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 7f3caeca5a62..e4fefc7c6705 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -884,7 +884,7 @@ <string name="keyguard_indication_charging_time_slowly"><xliff:g id="percentage">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="charging_time_left" example="4 hr, 2 min">%1$s</xliff:g></string> <!-- Indication on the keyguard that is shown when the device is dock charging. [CHAR LIMIT=80]--> - <string name="keyguard_indication_charging_time_dock"><xliff:g id="percentage" example="20%">%2$s</xliff:g> • Charging Dock • Full in <xliff:g id="charging_time_left" example="4 hr, 2 min">%1$s</xliff:g></string> + <string name="keyguard_indication_charging_time_dock"><xliff:g id="percentage" example="20%">%2$s</xliff:g> • Charging • Full in <xliff:g id="charging_time_left" example="4 hr, 2 min">%1$s</xliff:g></string> <!-- Related to user switcher --><skip/> diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt index 60130e1086ef..47e2d2ccd13e 100644 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt +++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt @@ -44,9 +44,13 @@ import platform.test.screenshot.DeviceEmulationSpec import platform.test.screenshot.MaterialYouColorsRule import platform.test.screenshot.ScreenshotTestRule import platform.test.screenshot.getEmulatedDevicePathConfig +import platform.test.screenshot.matchers.BitmapMatcher /** A rule for View screenshot diff unit tests. */ -class ViewScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestRule { +class ViewScreenshotTestRule( + emulationSpec: DeviceEmulationSpec, + private val matcher: BitmapMatcher = UnitTestBitmapMatcher +) : TestRule { private val colorsRule = MaterialYouColorsRule() private val deviceEmulationRule = DeviceEmulationRule(emulationSpec) private val screenshotRule = @@ -59,7 +63,6 @@ class ViewScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestRule { .around(deviceEmulationRule) .around(screenshotRule) .around(activityRule) - private val matcher = UnitTestBitmapMatcher override fun apply(base: Statement, description: Description): Statement { return delegateRule.apply(base, description) diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt index 2739d59dbf00..c2fda82cefdc 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt @@ -47,7 +47,12 @@ class AnimatableClockView @JvmOverloads constructor( defStyleRes: Int = 0 ) : TextView(context, attrs, defStyleAttr, defStyleRes) { - private var lastMeasureCall: CharSequence = "" + private var lastMeasureCall: CharSequence? = null + private var lastDraw: CharSequence? = null + private var lastTextUpdate: CharSequence? = null + private var lastOnTextChanged: CharSequence? = null + private var lastInvalidate: CharSequence? = null + private var lastTimeZoneChange: CharSequence? = null private val time = Calendar.getInstance() @@ -69,6 +74,8 @@ class AnimatableClockView @JvmOverloads constructor( private var textAnimator: TextAnimator? = null private var onTextAnimatorInitialized: Runnable? = null + var timeOverrideInMillis: Long? = null + val dozingWeight: Int get() = if (useBoldedVersion()) dozingWeightInternal + 100 else dozingWeightInternal @@ -125,7 +132,7 @@ class AnimatableClockView @JvmOverloads constructor( } fun refreshTime() { - time.timeInMillis = System.currentTimeMillis() + time.timeInMillis = timeOverrideInMillis ?: System.currentTimeMillis() contentDescription = DateFormat.format(descFormat, time) val formattedText = DateFormat.format(format, time) // Setting text actually triggers a layout pass (because the text view is set to @@ -133,18 +140,19 @@ class AnimatableClockView @JvmOverloads constructor( // relayout if the text didn't actually change. if (!TextUtils.equals(text, formattedText)) { text = formattedText + lastTextUpdate = getTimestamp() } } fun onTimeZoneChanged(timeZone: TimeZone?) { time.timeZone = timeZone refreshFormat() + lastTimeZoneChange = "${getTimestamp()} timeZone=${time.timeZone}" } @SuppressLint("DrawAllocation") override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) - lastMeasureCall = DateFormat.format(descFormat, System.currentTimeMillis()) val animator = textAnimator if (animator == null) { textAnimator = TextAnimator(layout) { invalidate() } @@ -153,13 +161,34 @@ class AnimatableClockView @JvmOverloads constructor( } else { animator.updateLayout(layout) } + lastMeasureCall = getTimestamp() } override fun onDraw(canvas: Canvas) { + lastDraw = getTimestamp() // intentionally doesn't call super.onDraw here or else the text will be rendered twice textAnimator?.draw(canvas) } + override fun invalidate() { + super.invalidate() + lastInvalidate = getTimestamp() + } + + private fun getTimestamp(): CharSequence { + return "${DateFormat.format("HH:mm:ss", System.currentTimeMillis())} text=$text" + } + + override fun onTextChanged( + text: CharSequence, + start: Int, + lengthBefore: Int, + lengthAfter: Int + ) { + super.onTextChanged(text, start, lengthBefore, lengthAfter) + lastOnTextChanged = "${getTimestamp()}" + } + fun setLineSpacingScale(scale: Float) { lineSpacingScale = scale setLineSpacing(0f, lineSpacingScale) @@ -352,7 +381,12 @@ class AnimatableClockView @JvmOverloads constructor( pw.println(" measuredWidth=$measuredWidth") pw.println(" measuredHeight=$measuredHeight") pw.println(" singleLineInternal=$isSingleLineInternal") + pw.println(" lastTextUpdate=$lastTextUpdate") + pw.println(" lastOnTextChanged=$lastOnTextChanged") + pw.println(" lastInvalidate=$lastInvalidate") pw.println(" lastMeasureCall=$lastMeasureCall") + pw.println(" lastDraw=$lastDraw") + pw.println(" lastTimeZoneChange=$lastTimeZoneChange") pw.println(" currText=$text") pw.println(" currTimeContextDesc=$contentDescription") pw.println(" time=$time") diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt index 0146795f4988..dd2e55d4e7d7 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt @@ -35,6 +35,7 @@ open class RegionSamplingInstance( ) { private var isDark = RegionDarkness.DEFAULT private var samplingBounds = Rect() + private val tmpScreenLocation = IntArray(2) @VisibleForTesting var regionSampler: RegionSamplingHelper? = null /** @@ -99,10 +100,21 @@ open class RegionSamplingInstance( isDark = convertToClockDarkness(isRegionDark) updateFun.updateColors() } - + /** + * The method getLocationOnScreen is used to obtain the view coordinates + * relative to its left and top edges on the device screen. + * Directly accessing the X and Y coordinates of the view returns the + * location relative to its parent view instead. + */ override fun getSampledRegion(sampledView: View): Rect { - samplingBounds = Rect(sampledView.left, sampledView.top, - sampledView.right, sampledView.bottom) + val screenLocation = tmpScreenLocation + sampledView.getLocationOnScreen(screenLocation) + val left = screenLocation[0] + val top = screenLocation[1] + samplingBounds.left = left + samplingBounds.top = top + samplingBounds.right = left + sampledView.width + samplingBounds.bottom = top + sampledView.height return samplingBounds } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java index b29dc835a6f4..22bffda33918 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java @@ -49,6 +49,7 @@ import android.view.accessibility.AccessibilityManager; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLoggerImpl; @@ -99,6 +100,7 @@ public class RotationButtonController { private @WindowInsetsController.Behavior int mBehavior = WindowInsetsController.BEHAVIOR_DEFAULT; private int mNavBarMode; + private boolean mTaskBarVisible = false; private boolean mSkipOverrideUserLockPrefsOnce; private final int mLightIconColor; private final int mDarkIconColor; @@ -422,6 +424,7 @@ public class RotationButtonController { } public void onTaskbarStateChange(boolean visible, boolean stashed) { + mTaskBarVisible = visible; if (getRotationButton() == null) { return; } @@ -438,9 +441,12 @@ public class RotationButtonController { * Return true when either the task bar is visible or it's in visual immersive mode. */ @SuppressLint("InlinedApi") - private boolean canShowRotationButton() { - return mIsNavigationBarShowing || mBehavior == WindowInsetsController.BEHAVIOR_DEFAULT - || isGesturalMode(mNavBarMode); + @VisibleForTesting + boolean canShowRotationButton() { + return mIsNavigationBarShowing + || mBehavior == WindowInsetsController.BEHAVIOR_DEFAULT + || isGesturalMode(mNavBarMode) + || mTaskBarVisible; } @DrawableRes @@ -624,4 +630,3 @@ public class RotationButtonController { } } } - diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index 206b8bee323c..916410856b20 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -88,6 +88,7 @@ public class KeyguardClockSwitch extends RelativeLayout { private int mClockSwitchYAmount; @VisibleForTesting boolean mChildrenAreLaidOut = false; + @VisibleForTesting boolean mAnimateOnLayout = true; public KeyguardClockSwitch(Context context, AttributeSet attrs) { super(context, attrs); @@ -305,7 +306,7 @@ public class KeyguardClockSwitch extends RelativeLayout { super.onLayout(changed, l, t, r, b); if (mDisplayedClockSize != null && !mChildrenAreLaidOut) { - post(() -> updateClockViews(mDisplayedClockSize == LARGE, /* animate */ true)); + post(() -> updateClockViews(mDisplayedClockSize == LARGE, mAnimateOnLayout)); } mChildrenAreLaidOut = true; diff --git a/packages/SystemUI/src/com/android/systemui/ActivityIntentHelper.java b/packages/SystemUI/src/com/android/systemui/ActivityIntentHelper.java index 43b3929808b3..df65bcf9c10d 100644 --- a/packages/SystemUI/src/com/android/systemui/ActivityIntentHelper.java +++ b/packages/SystemUI/src/com/android/systemui/ActivityIntentHelper.java @@ -16,6 +16,7 @@ package com.android.systemui; +import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -34,12 +35,12 @@ import javax.inject.Inject; @SysUISingleton public class ActivityIntentHelper { - private final Context mContext; + private final PackageManager mPm; @Inject public ActivityIntentHelper(Context context) { // TODO: inject a package manager, not a context. - mContext = context; + mPm = context.getPackageManager(); } /** @@ -57,6 +58,15 @@ public class ActivityIntentHelper { } /** + * @see #wouldLaunchResolverActivity(Intent, int) + */ + public boolean wouldPendingLaunchResolverActivity(PendingIntent intent, int currentUserId) { + ActivityInfo targetActivityInfo = getPendingTargetActivityInfo(intent, currentUserId, + false /* onlyDirectBootAware */); + return targetActivityInfo == null; + } + + /** * Returns info about the target Activity of a given intent, or null if the intent does not * resolve to a specific component meeting the requirements. * @@ -68,19 +78,45 @@ public class ActivityIntentHelper { */ public ActivityInfo getTargetActivityInfo(Intent intent, int currentUserId, boolean onlyDirectBootAware) { - PackageManager packageManager = mContext.getPackageManager(); - int flags = PackageManager.MATCH_DEFAULT_ONLY; + int flags = PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA; if (!onlyDirectBootAware) { flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; } - final List<ResolveInfo> appList = packageManager.queryIntentActivitiesAsUser( + final List<ResolveInfo> appList = mPm.queryIntentActivitiesAsUser( intent, flags, currentUserId); if (appList.size() == 0) { return null; } - ResolveInfo resolved = packageManager.resolveActivityAsUser(intent, - flags | PackageManager.GET_META_DATA, currentUserId); + if (appList.size() == 1) { + return appList.get(0).activityInfo; + } + ResolveInfo resolved = mPm.resolveActivityAsUser(intent, flags, currentUserId); + if (resolved == null || wouldLaunchResolverActivity(resolved, appList)) { + return null; + } else { + return resolved.activityInfo; + } + } + + /** + * @see #getTargetActivityInfo(Intent, int, boolean) + */ + public ActivityInfo getPendingTargetActivityInfo(PendingIntent intent, int currentUserId, + boolean onlyDirectBootAware) { + int flags = PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA; + if (!onlyDirectBootAware) { + flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; + } + final List<ResolveInfo> appList = intent.queryIntentComponents(flags); + if (appList.size() == 0) { + return null; + } + if (appList.size() == 1) { + return appList.get(0).activityInfo; + } + ResolveInfo resolved = mPm.resolveActivityAsUser(intent.getIntent(), flags, currentUserId); if (resolved == null || wouldLaunchResolverActivity(resolved, appList)) { return null; } else { @@ -104,6 +140,17 @@ public class ActivityIntentHelper { } /** + * @see #wouldShowOverLockscreen(Intent, int) + */ + public boolean wouldPendingShowOverLockscreen(PendingIntent intent, int currentUserId) { + ActivityInfo targetActivityInfo = getPendingTargetActivityInfo(intent, + currentUserId, false /* onlyDirectBootAware */); + return targetActivityInfo != null + && (targetActivityInfo.flags & (ActivityInfo.FLAG_SHOW_WHEN_LOCKED + | ActivityInfo.FLAG_SHOW_FOR_ALL_USERS)) > 0; + } + + /** * Determines if sending the given intent would result in starting an Intent resolver activity, * instead of resolving to a specific component. * diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt index 589ec0e72b3b..9b5f54a0a91d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt @@ -92,7 +92,7 @@ open class AuthBiometricFingerprintIconController( STATE_ERROR -> true STATE_AUTHENTICATING_ANIMATING_IN, STATE_AUTHENTICATING -> oldState == STATE_ERROR || oldState == STATE_HELP - STATE_AUTHENTICATED -> false + STATE_AUTHENTICATED -> true else -> false } @@ -114,7 +114,13 @@ open class AuthBiometricFingerprintIconController( R.raw.fingerprint_dialogue_fingerprint_to_error_lottie } } - STATE_AUTHENTICATED -> R.raw.fingerprint_dialogue_fingerprint_to_error_lottie + STATE_AUTHENTICATED -> { + if (oldState == STATE_ERROR || oldState == STATE_HELP) { + R.raw.fingerprint_dialogue_error_to_success_lottie + } else { + R.raw.fingerprint_dialogue_fingerprint_to_success_lottie + } + } else -> return null } return if (id != null) return id else null diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt index 31baa0ff1154..9cce066afe9d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt @@ -75,7 +75,7 @@ open class AuthBiometricFingerprintView( } } - override fun getDelayAfterAuthenticatedDurationMs() = 0 + override fun getDelayAfterAuthenticatedDurationMs() = 500 override fun getStateForAfterError() = STATE_AUTHENTICATING diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java index e866b9c0bb25..fc5cf9f005ed 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java @@ -468,6 +468,7 @@ public abstract class AuthBiometricView extends LinearLayout { break; case STATE_AUTHENTICATED: + removePendingAnimations(); if (mSize != AuthDialog.SIZE_SMALL) { mConfirmButton.setVisibility(View.GONE); mNegativeButton.setVisibility(View.GONE); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index cf50f7f8524b..d1589b23454d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -517,8 +517,6 @@ public class UdfpsController implements DozeReceiver { scaledMajor); Log.v(TAG, "onTouch | finger down: " + touchInfo); mTouchLogTime = mSystemClock.elapsedRealtime(); - mPowerManager.userActivity(mSystemClock.uptimeMillis(), - PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0); handled = true; } else if (sinceLastLog >= MIN_TOUCH_LOG_INTERVAL) { Log.v(TAG, "onTouch | finger move: " + touchInfo); @@ -846,6 +844,9 @@ public class UdfpsController implements DozeReceiver { return; } mLatencyTracker.onActionStart(LatencyTracker.ACTION_UDFPS_ILLUMINATE); + // Refresh screen timeout and boost process priority if possible. + mPowerManager.userActivity(mSystemClock.uptimeMillis(), + PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0); if (!mOnFingerDown) { playStartHaptic(); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java index 49e378e4a76f..d96476fbf087 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java @@ -99,12 +99,11 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable { mProgressColor = context.getColor(R.color.udfps_enroll_progress); final AccessibilityManager am = context.getSystemService(AccessibilityManager.class); mIsAccessibilityEnabled = am.isTouchExplorationEnabled(); + mOnFirstBucketFailedColor = context.getColor(R.color.udfps_moving_target_fill_error); if (!mIsAccessibilityEnabled) { mHelpColor = context.getColor(R.color.udfps_enroll_progress_help); - mOnFirstBucketFailedColor = context.getColor(R.color.udfps_moving_target_fill_error); } else { mHelpColor = context.getColor(R.color.udfps_enroll_progress_help_with_talkback); - mOnFirstBucketFailedColor = mHelpColor; } mCheckmarkDrawable = context.getDrawable(R.drawable.udfps_enroll_checkmark); mCheckmarkDrawable.mutate(); @@ -167,7 +166,8 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable { } private void updateProgress(int remainingSteps, int totalSteps, boolean showingHelp) { - if (mRemainingSteps == remainingSteps && mTotalSteps == totalSteps) { + if (mRemainingSteps == remainingSteps && mTotalSteps == totalSteps + && mShowingHelp == showingHelp) { return; } @@ -197,6 +197,7 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable { } } + mShowingHelp = showingHelp; mRemainingSteps = remainingSteps; mTotalSteps = totalSteps; diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java index 47ea27ff8ccb..08393386c27f 100644 --- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java +++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java @@ -30,6 +30,8 @@ import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; +import androidx.core.graphics.ColorUtils; + import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; @@ -42,7 +44,8 @@ import java.text.NumberFormat; * @hide */ final class WirelessChargingLayout extends FrameLayout { - private static final long RIPPLE_ANIMATION_DURATION = 1500; + private static final long CIRCLE_RIPPLE_ANIMATION_DURATION = 1500; + private static final long ROUNDED_BOX_RIPPLE_ANIMATION_DURATION = 1750; private static final int SCRIM_COLOR = 0x4C000000; private static final int SCRIM_FADE_DURATION = 300; private RippleView mRippleView; @@ -131,17 +134,30 @@ final class WirelessChargingLayout extends FrameLayout { "backgroundColor", SCRIM_COLOR, Color.TRANSPARENT); scrimFadeOutAnimator.setDuration(SCRIM_FADE_DURATION); scrimFadeOutAnimator.setInterpolator(Interpolators.LINEAR); - scrimFadeOutAnimator.setStartDelay(RIPPLE_ANIMATION_DURATION - SCRIM_FADE_DURATION); + scrimFadeOutAnimator.setStartDelay((rippleShape == RippleShape.CIRCLE + ? CIRCLE_RIPPLE_ANIMATION_DURATION : ROUNDED_BOX_RIPPLE_ANIMATION_DURATION) + - SCRIM_FADE_DURATION); AnimatorSet animatorSetScrim = new AnimatorSet(); animatorSetScrim.playTogether(scrimFadeInAnimator, scrimFadeOutAnimator); animatorSetScrim.start(); mRippleView = findViewById(R.id.wireless_charging_ripple); mRippleView.setupShader(rippleShape); + if (mRippleView.getRippleShape() == RippleShape.ROUNDED_BOX) { + mRippleView.setDuration(ROUNDED_BOX_RIPPLE_ANIMATION_DURATION); + mRippleView.setSparkleStrength(0.22f); + int color = Utils.getColorAttr(mRippleView.getContext(), + android.R.attr.colorAccent).getDefaultColor(); + mRippleView.setColor(ColorUtils.setAlphaComponent(color, 28)); + } else { + mRippleView.setDuration(CIRCLE_RIPPLE_ANIMATION_DURATION); + mRippleView.setColor(Utils.getColorAttr(mRippleView.getContext(), + android.R.attr.colorAccent).getDefaultColor()); + } + OnAttachStateChangeListener listener = new OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View view) { - mRippleView.setDuration(RIPPLE_ANIMATION_DURATION); mRippleView.startRipple(); mRippleView.removeOnAttachStateChangeListener(this); } @@ -232,13 +248,13 @@ final class WirelessChargingLayout extends FrameLayout { int height = getMeasuredHeight(); mRippleView.setCenter(width * 0.5f, height * 0.5f); if (mRippleView.getRippleShape() == RippleShape.ROUNDED_BOX) { - mRippleView.setMaxSize(width * 1.5f, height * 1.5f); + // Those magic numbers are introduced for visual polish. This aspect ratio maps with + // the tablet's docking station. + mRippleView.setMaxSize(width * 1.36f, height * 1.46f); } else { float maxSize = Math.max(width, height); mRippleView.setMaxSize(maxSize, maxSize); } - mRippleView.setColor(Utils.getColorAttr(mRippleView.getContext(), - android.R.attr.colorAccent).getDefaultColor()); } super.onLayout(changed, left, top, right, bottom); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index f32ea353bf50..e549a96079bd 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -18,6 +18,7 @@ package com.android.systemui.dagger; import android.app.INotificationManager; import android.content.Context; +import android.service.dreams.IDreamManager; import androidx.annotation.Nullable; @@ -213,6 +214,7 @@ public abstract class SystemUIModule { ShadeController shadeController, @Nullable IStatusBarService statusBarService, INotificationManager notificationManager, + IDreamManager dreamManager, NotificationVisibilityProvider visibilityProvider, NotificationInterruptStateProvider interruptionStateProvider, ZenModeController zenModeController, @@ -230,6 +232,7 @@ public abstract class SystemUIModule { shadeController, statusBarService, notificationManager, + dreamManager, visibilityProvider, interruptionStateProvider, zenModeController, @@ -238,7 +241,6 @@ public abstract class SystemUIModule { notifCollection, notifPipeline, sysUiState, - dumpManager, sysuiMainExecutor)); } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java index 7e4a108aadf1..823255c38a84 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java @@ -113,7 +113,7 @@ public class DreamOverlayStatusBarView extends ConstraintLayout { } void setExtraStatusBarItemViews(List<View> views) { - mSystemStatusViewGroup.removeAllViews(); + removeAllStatusBarItemViews(); views.forEach(view -> mSystemStatusViewGroup.addView(view)); } @@ -121,4 +121,8 @@ public class DreamOverlayStatusBarView extends ConstraintLayout { final View statusIcon = findViewById(resId); return Objects.requireNonNull(statusIcon); } + + void removeAllStatusBarItemViews() { + mSystemStatusViewGroup.removeAllViews(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java index 65cfae1ac14b..6f505504b186 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java @@ -192,6 +192,7 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve mDreamOverlayNotificationCountProvider.ifPresent( provider -> provider.removeCallback(mNotificationCountCallback)); mStatusBarItemsProvider.removeCallback(mStatusBarItemsProviderCallback); + mView.removeAllStatusBarItemViews(); mTouchInsetSession.clear(); mIsAttached = false; diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java index 83249aa324d1..bbcab60d7ba2 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java @@ -69,7 +69,7 @@ public class ComplicationTypesUpdater extends CoreStartable { }; mSecureSettings.registerContentObserverForUser( - Settings.Secure.SCREENSAVER_ENABLED_COMPLICATIONS, + Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED, settingsObserver, UserHandle.myUserId()); settingsObserver.onChange(false); diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java index 5250d44761b9..7d9f1059f3b8 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java @@ -37,7 +37,7 @@ import dagger.Provides; public interface DreamClockTimeComplicationModule { String DREAM_CLOCK_TIME_COMPLICATION_VIEW = "clock_time_complication_view"; String TAG_WEIGHT = "'wght' "; - int WEIGHT = 200; + int WEIGHT = 400; /** * Provides the complication view. diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index 7299aa597e60..0ee53cd68dbe 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -155,7 +155,11 @@ public class Flags { public static final ReleasedFlag STATUS_BAR_LETTERBOX_APPEARANCE = new ReleasedFlag(603, false); - public static final UnreleasedFlag NEW_STATUS_BAR_PIPELINE = new UnreleasedFlag(604, true); + public static final UnreleasedFlag NEW_STATUS_BAR_PIPELINE_BACKEND = + new UnreleasedFlag(604, false); + + public static final UnreleasedFlag NEW_STATUS_BAR_PIPELINE_FRONTEND = + new UnreleasedFlag(605, false); /***************************************/ // 700 - dialer/calls @@ -174,11 +178,16 @@ public class Flags { new ResourceBooleanFlag(800, R.bool.flag_monet); /***************************************/ + // 801 - region sampling + public static final UnreleasedFlag REGION_SAMPLING = new UnreleasedFlag(801); + + /***************************************/ // 900 - media public static final ReleasedFlag MEDIA_TAP_TO_TRANSFER = new ReleasedFlag(900); public static final UnreleasedFlag MEDIA_SESSION_ACTIONS = new UnreleasedFlag(901); public static final ReleasedFlag MEDIA_NEARBY_DEVICES = new ReleasedFlag(903); public static final ReleasedFlag MEDIA_MUTE_AWAIT = new ReleasedFlag(904); + public static final UnreleasedFlag MEDIA_DREAM_COMPLICATION = new UnreleasedFlag(905); // 1000 - dock public static final ReleasedFlag SIMULATE_DOCK_THROUGH_CHARGING = @@ -233,6 +242,7 @@ public class Flags { // 1300 - screenshots public static final UnreleasedFlag SCREENSHOT_REQUEST_PROCESSOR = new UnreleasedFlag(1300); + public static final UnreleasedFlag SCREENSHOT_WORK_PROFILE_POLICY = new UnreleasedFlag(1301); // Pay no attention to the reflection behind the curtain. // ========================== Curtain ========================== diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java index ca65d12e87e6..da5819a7f3bc 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java @@ -90,6 +90,8 @@ import android.widget.ImageView.ScaleType; import android.widget.LinearLayout; import android.widget.ListPopupWindow; import android.widget.TextView; +import android.window.OnBackInvokedCallback; +import android.window.OnBackInvokedDispatcher; import androidx.annotation.NonNull; import androidx.lifecycle.Lifecycle; @@ -155,6 +157,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene public static final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions"; public static final String SYSTEM_DIALOG_REASON_DREAM = "dream"; + private static final boolean DEBUG = false; + private static final String TAG = "GlobalActionsDialogLite"; private static final String INTERACTION_JANK_TAG = "global_actions"; @@ -2177,6 +2181,11 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene protected ViewGroup mContainer; + private final OnBackInvokedCallback mOnBackInvokedCallback = () -> { + logOnBackInvocation(); + dismiss(); + }; + @VisibleForTesting protected GestureDetector.SimpleOnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener() { @@ -2221,6 +2230,16 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } }; + + // this exists so that we can point it to a mock during Unit Testing + private OnBackInvokedDispatcher mOverriddenBackDispatcher; + + // the following method exists so that a Unit Test can supply a `OnBackInvokedDispatcher` + @VisibleForTesting + void setBackDispatcherOverride(OnBackInvokedDispatcher mockDispatcher) { + mOverriddenBackDispatcher = mockDispatcher; + } + ActionsDialogLite(Context context, int themeRes, MyAdapter adapter, MyOverflowAdapter overflowAdapter, SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService, @@ -2254,6 +2273,22 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene super.onCreate(savedInstanceState); initializeLayout(); mWindowDimAmount = getWindow().getAttributes().dimAmount; + getOnBackInvokedDispatcher().registerOnBackInvokedCallback( + OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback); + if (DEBUG) Log.d(TAG, "OnBackInvokedCallback handler registered"); + } + + @VisibleForTesting + @Override + public OnBackInvokedDispatcher getOnBackInvokedDispatcher() { + if (mOverriddenBackDispatcher != null) return mOverriddenBackDispatcher; + else return super.getOnBackInvokedDispatcher(); + } + + @Override + public void onDetachedFromWindow() { + getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mOnBackInvokedCallback); + if (DEBUG) Log.d(TAG, "OnBackInvokedCallback handler unregistered"); } @Override @@ -2453,7 +2488,12 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene @Override public void onBackPressed() { super.onBackPressed(); + logOnBackInvocation(); + } + + private void logOnBackInvocation() { mUiEventLogger.log(GlobalActionsEvent.GA_CLOSE_BACK); + if (DEBUG) Log.d(TAG, "onBack invoked"); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 3eb3c80f4081..6dfbd426ef30 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -323,6 +323,8 @@ public class KeyguardService extends Service { if (sEnableRemoteKeyguardOccludeAnimation) { Slog.d(TAG, "KeyguardService registerRemote: TRANSIT_KEYGUARD_(UN)OCCLUDE"); // Register for occluding + final RemoteTransition occludeTransition = new RemoteTransition( + mOccludeAnimation, getIApplicationThread()); TransitionFilter f = new TransitionFilter(); f.mFlags = TRANSIT_FLAG_KEYGUARD_LOCKED; f.mRequirements = new TransitionFilter.Requirement[]{ @@ -337,10 +339,11 @@ public class KeyguardService extends Service { f.mRequirements[1].mMustBeIndependent = false; f.mRequirements[1].mFlags = FLAG_OCCLUDES_KEYGUARD; f.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK}; - mShellTransitions.registerRemote(f, - new RemoteTransition(mOccludeAnimation, getIApplicationThread())); + mShellTransitions.registerRemote(f, occludeTransition); // Now register for un-occlude. + final RemoteTransition unoccludeTransition = new RemoteTransition( + mUnoccludeAnimation, getIApplicationThread()); f = new TransitionFilter(); f.mFlags = TRANSIT_FLAG_KEYGUARD_LOCKED; f.mRequirements = new TransitionFilter.Requirement[]{ @@ -358,8 +361,23 @@ public class KeyguardService extends Service { f.mRequirements[0].mMustBeIndependent = false; f.mRequirements[0].mFlags = FLAG_OCCLUDES_KEYGUARD; f.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT}; - mShellTransitions.registerRemote(f, - new RemoteTransition(mUnoccludeAnimation, getIApplicationThread())); + mShellTransitions.registerRemote(f, unoccludeTransition); + + // Register for specific transition type. + // Above filter cannot fulfill all conditions. + // E.g. close top activity while screen off but next activity is occluded, this should + // an occluded transition, but since the activity is invisible, the condition would + // match unoccluded transition. + // But on the contrary, if we add above condition in occluded transition, then when user + // trying to dismiss occluded activity when unlock keyguard, the condition would match + // occluded transition. + f = new TransitionFilter(); + f.mTypeSet = new int[]{TRANSIT_KEYGUARD_OCCLUDE}; + mShellTransitions.registerRemote(f, occludeTransition); + + f = new TransitionFilter(); + f.mTypeSet = new int[]{TRANSIT_KEYGUARD_UNOCCLUDE}; + mShellTransitions.registerRemote(f, unoccludeTransition); } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index dbf218a115cb..d4ef4675392d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -2383,10 +2383,10 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, private void handleHide() { Trace.beginSection("KeyguardViewMediator#handleHide"); - // It's possible that the device was unlocked in a dream state. It's time to wake up. - if (mAodShowing || mDreamOverlayShowing) { - PowerManager pm = mContext.getSystemService(PowerManager.class); - pm.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE, + // It's possible that the device was unlocked (via BOUNCER) while dozing. It's time to + // wake up. + if (mAodShowing) { + mPM.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE, "com.android.systemui:BOUNCER_DOZING"); } @@ -2415,6 +2415,13 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, null /* nonApps */, null /* finishedCallback */); }); } + + // It's possible that the device was unlocked (via BOUNCER or Fingerprint) while + // dreaming. It's time to wake up. + if (mDreamOverlayShowing) { + mPM.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE, + "com.android.systemui:UNLOCK_DREAMING"); + } } Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt index 19c6249a12c0..c4e3d4e4c1b5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt @@ -251,9 +251,21 @@ object KeyguardBottomAreaViewBinder { Utils.getColorAttr(view.context, com.android.internal.R.attr.colorSurface) view.contentDescription = view.context.getString(viewModel.contentDescriptionResourceId) - view.setOnClickListener { + view.isClickable = viewModel.isClickable + if (viewModel.isClickable) { + view.setOnClickListener(OnClickListener(viewModel, falsingManager)) + } else { + view.setOnClickListener(null) + } + } + + private class OnClickListener( + private val viewModel: KeyguardQuickAffordanceViewModel, + private val falsingManager: FalsingManager, + ) : View.OnClickListener { + override fun onClick(view: View) { if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { - return@setOnClickListener + return } if (viewModel.configKey != null) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt index 01d5e5c493ce..e3ebac60febb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.ui.viewmodel +import androidx.annotation.VisibleForTesting import com.android.systemui.doze.util.BurnInHelperWrapper import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor @@ -37,6 +38,23 @@ constructor( private val bottomAreaInteractor: KeyguardBottomAreaInteractor, private val burnInHelperWrapper: BurnInHelperWrapper, ) { + /** + * Whether quick affordances are "opaque enough" to be considered visible to and interactive by + * the user. If they are not interactive, user input should not be allowed on them. + * + * Note that there is a margin of error, where we allow very, very slightly transparent views to + * be considered "fully opaque" for the purpose of being interactive. This is to accommodate the + * error margin of floating point arithmetic. + * + * A view that is visible but with an alpha of less than our threshold either means it's not + * fully done fading in or is fading/faded out. Either way, it should not be + * interactive/clickable unless "fully opaque" to avoid issues like in b/241830987. + */ + private val areQuickAffordancesFullyOpaque: Flow<Boolean> = + bottomAreaInteractor.alpha + .map { alpha -> alpha >= AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD } + .distinctUntilChanged() + /** An observable for the view-model of the "start button" quick affordance. */ val startButton: Flow<KeyguardQuickAffordanceViewModel> = button(KeyguardQuickAffordancePosition.BOTTOM_START) @@ -77,14 +95,19 @@ constructor( return combine( quickAffordanceInteractor.quickAffordance(position), bottomAreaInteractor.animateDozingTransitions.distinctUntilChanged(), - ) { model, animateReveal -> - model.toViewModel(animateReveal) + areQuickAffordancesFullyOpaque, + ) { model, animateReveal, isFullyOpaque -> + model.toViewModel( + animateReveal = animateReveal, + isClickable = isFullyOpaque, + ) } .distinctUntilChanged() } private fun KeyguardQuickAffordanceModel.toViewModel( animateReveal: Boolean, + isClickable: Boolean, ): KeyguardQuickAffordanceViewModel { return when (this) { is KeyguardQuickAffordanceModel.Visible -> @@ -100,8 +123,20 @@ constructor( animationController = parameters.animationController, ) }, + isClickable = isClickable, ) is KeyguardQuickAffordanceModel.Hidden -> KeyguardQuickAffordanceViewModel() } } + + companion object { + // We select a value that's less than 1.0 because we want floating point math precision to + // not be a factor in determining whether the affordance UI is fully opaque. The number we + // choose needs to be close enough 1.0 such that the user can't easily tell the difference + // between the UI with an alpha at the threshold and when the alpha is 1.0. At the same + // time, we don't want the number to be too close to 1.0 such that there is a chance that we + // never treat the affordance UI as "fully opaque" as that would risk making it forever not + // clickable. + @VisibleForTesting const val AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD = 0.95f + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt index 985ab623764a..b1de27d262cf 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt @@ -31,6 +31,7 @@ data class KeyguardQuickAffordanceViewModel( val icon: ContainedDrawable = ContainedDrawable.WithResource(0), @StringRes val contentDescriptionResourceId: Int = 0, val onClicked: (OnClickedParameters) -> Unit = {}, + val isClickable: Boolean = false, ) { data class OnClickedParameters( val configKey: KClass<out KeyguardQuickAffordanceConfig>, diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index c858bc3e2c81..c2a87649adef 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -84,6 +84,14 @@ public class LogModule { return factory.create("LSShadeTransitionLog", 50); } + /** Provides a logging buffer for Shade messages. */ + @Provides + @SysUISingleton + @ShadeLog + public static LogBuffer provideShadeLogBuffer(LogBufferFactory factory) { + return factory.create("ShadeLog", 500, false); + } + /** Provides a logging buffer for all logs related to managing notification sections. */ @Provides @SysUISingleton @@ -262,7 +270,7 @@ public class LogModule { @SysUISingleton @StatusBarConnectivityLog public static LogBuffer provideStatusBarConnectivityBuffer(LogBufferFactory factory) { - return factory.create("StatusBarConnectivityLog", 64); + return factory.create("SbConnectivity", 64); } /** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */ diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java new file mode 100644 index 000000000000..bd0d298ebdee --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.android.systemui.log.LogBuffer; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +/** A {@link LogBuffer} for Shade touch handling messages. */ +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface ShadeLog { +} diff --git a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt index 237b5053ea2c..32600fba61a4 100644 --- a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt @@ -18,19 +18,25 @@ package com.android.systemui.media import android.content.Context import android.content.res.Configuration +import android.database.ContentObserver +import android.net.Uri +import android.os.Handler +import android.os.UserHandle +import android.provider.Settings import android.view.View import android.view.ViewGroup import androidx.annotation.VisibleForTesting import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.dagger.MediaModule.KEYGUARD import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.stack.MediaContainerView import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.LargeScreenUtils +import com.android.systemui.util.settings.SecureSettings import javax.inject.Inject import javax.inject.Named @@ -43,9 +49,10 @@ class KeyguardMediaController @Inject constructor( @param:Named(KEYGUARD) private val mediaHost: MediaHost, private val bypassController: KeyguardBypassController, private val statusBarStateController: SysuiStatusBarStateController, - private val notifLockscreenUserManager: NotificationLockscreenUserManager, private val context: Context, - configurationController: ConfigurationController + private val secureSettings: SecureSettings, + @Main private val handler: Handler, + configurationController: ConfigurationController, ) { init { @@ -60,6 +67,24 @@ class KeyguardMediaController @Inject constructor( } }) + val settingsObserver: ContentObserver = object : ContentObserver(handler) { + override fun onChange(selfChange: Boolean, uri: Uri?) { + if (uri == lockScreenMediaPlayerUri) { + allowMediaPlayerOnLockScreen = + secureSettings.getBoolForUser( + Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, + true, + UserHandle.USER_CURRENT + ) + refreshMediaPosition() + } + } + } + secureSettings.registerContentObserverForUser( + Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, + settingsObserver, + UserHandle.USER_ALL) + // First let's set the desired state that we want for this host mediaHost.expansion = MediaHostState.EXPANDED mediaHost.showsOnlyActiveMedia = true @@ -101,6 +126,13 @@ class KeyguardMediaController @Inject constructor( private var splitShadeContainer: ViewGroup? = null /** + * Track the media player setting status on lock screen. + */ + private var allowMediaPlayerOnLockScreen: Boolean = true + private val lockScreenMediaPlayerUri = + secureSettings.getUriFor(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN) + + /** * Attaches media container in single pane mode, situated at the top of the notifications list */ fun attachSinglePaneContainer(mediaView: MediaContainerView?) { @@ -164,7 +196,7 @@ class KeyguardMediaController @Inject constructor( visible = mediaHost.visible && !bypassController.bypassEnabled && keyguardOrUserSwitcher && - notifLockscreenUserManager.shouldShowLockscreenNotifications() + allowMediaPlayerOnLockScreen if (visible) { showMediaPlayer() } else { diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index 30ba476abce2..c8826757355a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -17,6 +17,7 @@ package com.android.systemui.media import android.app.Notification +import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME import android.app.PendingIntent import android.app.smartspace.SmartspaceConfig import android.app.smartspace.SmartspaceManager @@ -27,6 +28,7 @@ import android.content.ContentResolver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.graphics.Bitmap import android.graphics.ImageDecoder @@ -57,8 +59,8 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.BcSmartspaceDataPlugin -import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState +import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState import com.android.systemui.statusbar.notification.row.HybridGroupManager import com.android.systemui.tuner.TunerService import com.android.systemui.util.Assert @@ -633,9 +635,14 @@ class MediaDataManager( } val mediaController = mediaControllerFactory.create(token) val metadata = mediaController.metadata + val notif: Notification = sbn.notification + + val appInfo = notif.extras.getParcelable( + Notification.EXTRA_BUILDER_APPLICATION_INFO, + ApplicationInfo::class.java + ) ?: getAppInfoFromPackage(sbn.packageName) // Album art - val notif: Notification = sbn.notification var artworkBitmap = metadata?.let { loadBitmapFromUri(it) } if (artworkBitmap == null) { artworkBitmap = metadata?.getBitmap(MediaMetadata.METADATA_KEY_ART) @@ -650,8 +657,7 @@ class MediaDataManager( } // App name - val builder = Notification.Builder.recoverBuilder(context, notif) - val app = builder.loadHeaderAppName() + val appName = getAppName(sbn, appInfo) // App Icon val smallIcon = sbn.notification.smallIcon @@ -712,12 +718,7 @@ class MediaDataManager( val currentEntry = mediaEntries.get(key) val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId() - val appUid = try { - context.packageManager.getApplicationInfo(sbn.packageName, 0)?.uid!! - } catch (e: PackageManager.NameNotFoundException) { - Log.w(TAG, "Could not get app UID for ${sbn.packageName}", e) - Process.INVALID_UID - } + val appUid = appInfo?.uid ?: Process.INVALID_UID if (logEvent) { logger.logActiveMediaAdded(appUid, sbn.packageName, instanceId, playbackLocation) @@ -730,7 +731,7 @@ class MediaDataManager( val resumeAction: Runnable? = mediaEntries[key]?.resumeAction val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true val active = mediaEntries[key]?.active ?: true - onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, app, + onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, appName, smallIcon, artist, song, artWorkIcon, actionIcons, actionsToShowCollapsed, semanticActions, sbn.packageName, token, notif.contentIntent, device, active, resumeAction = resumeAction, playbackLocation = playbackLocation, @@ -740,6 +741,28 @@ class MediaDataManager( } } + private fun getAppInfoFromPackage(packageName: String): ApplicationInfo? { + try { + return context.packageManager.getApplicationInfo(packageName, 0) + } catch (e: PackageManager.NameNotFoundException) { + Log.w(TAG, "Could not get app info for $packageName", e) + } + return null + } + + private fun getAppName(sbn: StatusBarNotification, appInfo: ApplicationInfo?): String { + val name = sbn.notification.extras.getString(EXTRA_SUBSTITUTE_APP_NAME) + if (name != null) { + return name + } + + return if (appInfo != null) { + context.packageManager.getApplicationLabel(appInfo).toString() + } else { + sbn.packageName + } + } + /** * Generate action buttons based on notification actions */ diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt index 926002060da7..ae4c7c73a2ea 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt @@ -22,7 +22,12 @@ import android.animation.ValueAnimator import android.annotation.IntDef import android.content.Context import android.content.res.Configuration +import android.database.ContentObserver import android.graphics.Rect +import android.net.Uri +import android.os.Handler +import android.os.UserHandle +import android.provider.Settings import android.util.Log import android.util.MathUtils import android.view.View @@ -33,12 +38,12 @@ import com.android.keyguard.KeyguardViewController import com.android.systemui.R import com.android.systemui.animation.Interpolators import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dreams.DreamOverlayStateController import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.shade.NotifPanelEvents import com.android.systemui.statusbar.CrossFadeHelper -import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.stack.StackStateAnimator @@ -47,6 +52,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.LargeScreenUtils import com.android.systemui.util.animation.UniqueObjectHostView +import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.traceSection import javax.inject.Inject @@ -85,15 +91,23 @@ class MediaHierarchyManager @Inject constructor( private val keyguardStateController: KeyguardStateController, private val bypassController: KeyguardBypassController, private val mediaCarouselController: MediaCarouselController, - private val notifLockscreenUserManager: NotificationLockscreenUserManager, private val keyguardViewController: KeyguardViewController, private val dreamOverlayStateController: DreamOverlayStateController, configurationController: ConfigurationController, wakefulnessLifecycle: WakefulnessLifecycle, panelEventsEvents: NotifPanelEvents, + private val secureSettings: SecureSettings, + @Main private val handler: Handler, ) { /** + * Track the media player setting status on lock screen. + */ + private var allowMediaPlayerOnLockScreen: Boolean = true + private val lockScreenMediaPlayerUri = + secureSettings.getUriFor(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN) + + /** * Whether we "skip" QQS during panel expansion. * * This means that when expanding the panel we go directly to QS. Also when we are on QS and @@ -521,6 +535,23 @@ class MediaHierarchyManager @Inject constructor( updateDesiredLocation() } }) + + val settingsObserver: ContentObserver = object : ContentObserver(handler) { + override fun onChange(selfChange: Boolean, uri: Uri?) { + if (uri == lockScreenMediaPlayerUri) { + allowMediaPlayerOnLockScreen = + secureSettings.getBoolForUser( + Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, + true, + UserHandle.USER_CURRENT + ) + } + } + } + secureSettings.registerContentObserverForUser( + Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, + settingsObserver, + UserHandle.USER_ALL) } private fun updateConfiguration() { @@ -1036,7 +1067,6 @@ class MediaHierarchyManager @Inject constructor( } val onLockscreen = (!bypassController.bypassEnabled && (statusbarState == StatusBarState.KEYGUARD)) - val allowedOnLockscreen = notifLockscreenUserManager.shouldShowLockscreenNotifications() val location = when { dreamOverlayActive -> LOCATION_DREAM_OVERLAY (qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS @@ -1044,7 +1074,7 @@ class MediaHierarchyManager @Inject constructor( !hasActiveMedia -> LOCATION_QS onLockscreen && isSplitShadeExpanding() -> LOCATION_QS onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS - onLockscreen && allowedOnLockscreen -> LOCATION_LOCKSCREEN + onLockscreen && allowMediaPlayerOnLockScreen -> LOCATION_LOCKSCREEN else -> LOCATION_QQS } // When we're on lock screen and the player is not active, we should keep it in QS. @@ -1116,7 +1146,7 @@ class MediaHierarchyManager @Inject constructor( return !statusBarStateController.isDozing && !keyguardViewController.isBouncerShowing && statusBarStateController.state == StatusBarState.KEYGUARD && - notifLockscreenUserManager.shouldShowLockscreenNotifications() && + allowMediaPlayerOnLockScreen && statusBarStateController.isExpanded && !qsExpanded } diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java index e077fed7805d..c5448713970c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java +++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java @@ -16,6 +16,8 @@ package com.android.systemui.media.dream; +import static com.android.systemui.flags.Flags.MEDIA_DREAM_COMPLICATION; + import android.content.Context; import androidx.annotation.NonNull; @@ -23,6 +25,7 @@ import androidx.annotation.Nullable; import com.android.systemui.CoreStartable; import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.media.MediaData; import com.android.systemui.media.MediaDataManager; import com.android.systemui.media.SmartspaceMediaData; @@ -34,7 +37,7 @@ import javax.inject.Inject; * the media complication as appropriate */ public class MediaDreamSentinel extends CoreStartable { - private MediaDataManager.Listener mListener = new MediaDataManager.Listener() { + private final MediaDataManager.Listener mListener = new MediaDataManager.Listener() { private boolean mAdded; @Override public void onSmartspaceMediaDataRemoved(@NonNull String key, boolean immediately) { @@ -63,6 +66,10 @@ public class MediaDreamSentinel extends CoreStartable { public void onMediaDataLoaded(@NonNull String key, @Nullable String oldKey, @NonNull MediaData data, boolean immediately, int receivedSmartspaceCardLatency, boolean isSsReactivated) { + if (!mFeatureFlags.isEnabled(MEDIA_DREAM_COMPLICATION)) { + return; + } + if (mAdded) { return; } @@ -79,15 +86,18 @@ public class MediaDreamSentinel extends CoreStartable { private final MediaDataManager mMediaDataManager; private final DreamOverlayStateController mDreamOverlayStateController; private final MediaDreamComplication mComplication; + private final FeatureFlags mFeatureFlags; @Inject public MediaDreamSentinel(Context context, MediaDataManager mediaDataManager, DreamOverlayStateController dreamOverlayStateController, - MediaDreamComplication complication) { + MediaDreamComplication complication, + FeatureFlags featureFlags) { super(context); mMediaDataManager = mediaDataManager; mDreamOverlayStateController = dreamOverlayStateController; mComplication = complication; + mFeatureFlags = featureFlags; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt index 2278938c398e..3a0ac1b7d9b0 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt @@ -226,7 +226,7 @@ abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>( appIconView.contentDescription = appNameOverride ?: iconInfo.iconName appIconView.setImageDrawable(appIconDrawableOverride ?: iconInfo.icon) - return appIconView.contentDescription.toString() + return appIconView.contentDescription } /** diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt index 196ea222e50d..00a22f20e94d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt @@ -141,12 +141,13 @@ class MediaTttChipControllerReceiver @Inject constructor( override fun updateChipView(newChipInfo: ChipReceiverInfo, currentChipView: ViewGroup) { super.updateChipView(newChipInfo, currentChipView) - setIcon( + val iconName = setIcon( currentChipView, newChipInfo.routeInfo.clientPackageName, newChipInfo.appIconDrawableOverride, newChipInfo.appNameOverride ) + currentChipView.contentDescription = iconName } override fun animateChipIn(chipView: ViewGroup) { @@ -159,6 +160,8 @@ class MediaTttChipControllerReceiver @Inject constructor( .alpha(1f) .setDuration(5.frames) .start() + // Using withEndAction{} doesn't apply a11y focus when screen is unlocked. + appIconView.postOnAnimation { chipView.requestAccessibilityFocus() } startRipple(chipView.requireViewById(R.id.ripple)) } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 6ac3eadb838d..7c4c64c20089 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -814,8 +814,10 @@ public class EdgeBackGestureHandler extends CurrentUserTracker } mLogGesture = false; String logPackageName = ""; + Map<String, Integer> vocab = mVocab; // Due to privacy, only top 100 most used apps by all users can be logged. - if (mUseMLModel && mVocab.containsKey(mPackageName) && mVocab.get(mPackageName) < 100) { + if (mUseMLModel && vocab != null && vocab.containsKey(mPackageName) + && vocab.get(mPackageName) < 100) { logPackageName = mPackageName; } SysUiStatsLog.write(SysUiStatsLog.BACK_GESTURE_REPORTED_REPORTED, backType, diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt index 0834a5ae75f6..e27bfb34ee3e 100644 --- a/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt @@ -31,7 +31,6 @@ import com.android.systemui.people.data.model.PeopleTileModel import com.android.systemui.people.data.repository.PeopleTileRepository import com.android.systemui.people.data.repository.PeopleWidgetRepository import javax.inject.Inject -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -52,7 +51,7 @@ class PeopleViewModel( * reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles. */ private val _priorityTiles = MutableStateFlow(priorityTiles()) - val priorityTiles: Flow<List<PeopleTileViewModel>> = _priorityTiles.asStateFlow() + val priorityTiles: StateFlow<List<PeopleTileViewModel>> = _priorityTiles.asStateFlow() /** * The list of the priority tiles/conversations. @@ -61,7 +60,7 @@ class PeopleViewModel( * reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles. */ private val _recentTiles = MutableStateFlow(recentTiles()) - val recentTiles: Flow<List<PeopleTileViewModel>> = _recentTiles.asStateFlow() + val recentTiles: StateFlow<List<PeopleTileViewModel>> = _recentTiles.asStateFlow() /** The ID of the widget currently being edited/added. */ private val _appWidgetId = MutableStateFlow(INVALID_APPWIDGET_ID) diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java index eeb1010693fc..2a6cf66995ea 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java @@ -80,7 +80,8 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader FeatureFlags featureFlags, VariableDateViewController.Factory variableDateViewControllerFactory, BatteryMeterViewController batteryMeterViewController, - StatusBarContentInsetsProvider statusBarContentInsetsProvider) { + StatusBarContentInsetsProvider statusBarContentInsetsProvider, + StatusBarIconController.TintedIconManager.Factory tintedIconManagerFactory) { super(view); mPrivacyIconsController = headerPrivacyIconsController; mStatusBarIconController = statusBarIconController; @@ -103,7 +104,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader mView.requireViewById(R.id.date_clock) ); - mIconManager = new StatusBarIconController.TintedIconManager(mIconContainer, featureFlags); + mIconManager = tintedIconManagerFactory.create(mIconContainer); mDemoModeReceiver = new ClockDemoModeReceiver(mClockView); mColorExtractor = colorExtractor; mOnColorsChangedListener = (extractor, which) -> { diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt index 56a187429af6..db7c1fd86be0 100644 --- a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt +++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt @@ -67,7 +67,7 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C float rippleInsideAlpha = (1.-inside) * in_fadeFill; float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing; - float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * 0.45; + float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a; vec4 ripple = in_color * rippleAlpha; return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength); } @@ -83,7 +83,7 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C float rippleInsideAlpha = (1.-inside) * in_fadeFill; float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing; - float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * 0.45; + float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a; vec4 ripple = in_color * rippleAlpha; return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength); } @@ -99,7 +99,7 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C float rippleInsideAlpha = (1.-inside) * in_fadeFill; float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing; - float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * 0.45; + float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a; vec4 ripple = in_color * rippleAlpha; return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength); } diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt index 8b0120177268..60c8f3719a2e 100644 --- a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt @@ -81,6 +81,7 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a rippleShader.color = RIPPLE_DEFAULT_COLOR rippleShader.progress = 0f rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH + rippleShader.pixelDensity = resources.displayMetrics.density ripplePaint.shader = rippleShader } @@ -124,6 +125,13 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a rippleShader.rippleFill = rippleFill } + /** + * Set the intensity of the sparkles. + */ + fun setSparkleStrength(strength: Float) { + rippleShader.sparkleStrength = strength + } + override fun onDraw(canvas: Canvas?) { if (canvas == null || !canvas.isHardwareAccelerated) { // Drawing with the ripple shader requires hardware acceleration, so skip diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl b/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl new file mode 100644 index 000000000000..f7c4dadc6605 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2009, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot; + +/** Interface implemented by ScreenshotProxyService */ +interface IScreenshotProxy { + + /** Is the notification shade currently exanded? */ + boolean isNotificationShadeExpanded(); +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageCapture.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ImageCapture.kt index 39f35a59ff42..77797601ca5a 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageCapture.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageCapture.kt @@ -22,5 +22,5 @@ interface ImageCapture { fun captureDisplay(displayId: Int, crop: Rect? = null): Bitmap? - fun captureTask(taskId: Int): Bitmap? + suspend fun captureTask(taskId: Int): Bitmap? } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt index 258c4360922d..246265b2c202 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt @@ -27,13 +27,19 @@ import android.view.SurfaceControl import android.view.SurfaceControl.DisplayCaptureArgs import android.view.SurfaceControl.ScreenshotHardwareBuffer import androidx.annotation.VisibleForTesting +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.withContext private const val TAG = "ImageCaptureImpl" +@SysUISingleton open class ImageCaptureImpl @Inject constructor( private val displayManager: DisplayManager, - private val atmService: IActivityTaskManager + private val atmService: IActivityTaskManager, + @Background private val bgContext: CoroutineDispatcher ) : ImageCapture { override fun captureDisplay(displayId: Int, crop: Rect?): Bitmap? { @@ -46,8 +52,8 @@ open class ImageCaptureImpl @Inject constructor( return buffer?.asBitmap() } - override fun captureTask(taskId: Int): Bitmap? { - val snapshot = atmService.takeTaskSnapshot(taskId) + override suspend fun captureTask(taskId: Int): Bitmap? { + val snapshot = withContext(bgContext) { atmService.takeTaskSnapshot(taskId) } ?: return null return Bitmap.wrapHardwareBuffer(snapshot.hardwareBuffer, snapshot.colorSpace) } @@ -67,12 +73,17 @@ open class ImageCaptureImpl @Inject constructor( } @VisibleForTesting - open fun captureDisplay(displayToken: IBinder, width: Int, height: Int, crop: Rect): ScreenshotHardwareBuffer? { - val captureArgs = DisplayCaptureArgs.Builder(displayToken) - .setSize(width, height) - .setSourceCrop(crop) - .build() + open fun captureDisplay( + displayToken: IBinder, + width: Int, + height: Int, + crop: Rect + ): ScreenshotHardwareBuffer? { + val captureArgs = + DisplayCaptureArgs.Builder(displayToken) + .setSize(width, height) + .setSourceCrop(crop) + .build() return SurfaceControl.captureDisplay(captureArgs) } - } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt index 4397d3d85d62..a918e5d9e106 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt @@ -16,51 +16,84 @@ package com.android.systemui.screenshot -import android.net.Uri -import android.util.Log -import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN +import android.graphics.Insets import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE -import android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler import com.android.internal.util.ScreenshotHelper.ScreenshotRequest import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY import java.util.function.Consumer import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch /** * Processes a screenshot request sent from {@link ScreenshotHelper}. */ @SysUISingleton -internal class RequestProcessor @Inject constructor( - private val controller: ScreenshotController, +class RequestProcessor @Inject constructor( + private val capture: ImageCapture, + private val policy: ScreenshotPolicy, + private val flags: FeatureFlags, + /** For the Java Async version, to invoke the callback. */ + @Application private val mainScope: CoroutineScope ) { - fun processRequest( - request: ScreenshotRequest, - onSavedListener: Consumer<Uri>, - callback: RequestCallback - ) { + /** + * Inspects the incoming request, returning a potentially modified request depending on policy. + * + * @param request the request to process + */ + suspend fun process(request: ScreenshotRequest): ScreenshotRequest { + var result = request - if (request.type == TAKE_SCREENSHOT_PROVIDED_IMAGE) { - val image = HardwareBitmapBundler.bundleToHardwareBitmap(request.bitmapBundle) + // Apply work profile screenshots policy: + // + // If the focused app belongs to a work profile, transforms a full screen + // (or partial) screenshot request to a task snapshot (provided image) screenshot. - controller.handleImageAsScreenshot( - image, request.boundsInScreen, request.insets, - request.taskId, request.userId, request.topComponent, onSavedListener, callback - ) - return - } + // Whenever displayContentInfo is fetched, the topComponent is also populated + // regardless of the managed profile status. + + if (request.type != TAKE_SCREENSHOT_PROVIDED_IMAGE && + flags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY) + ) { + + val info = policy.findPrimaryContent(policy.getDefaultDisplayId()) + + result = if (policy.isManagedProfile(info.userId)) { + val image = capture.captureTask(info.taskId) + ?: error("Task snapshot returned a null Bitmap!") - when (request.type) { - TAKE_SCREENSHOT_FULLSCREEN -> - controller.takeScreenshotFullscreen(null, onSavedListener, callback) - TAKE_SCREENSHOT_SELECTED_REGION -> - controller.takeScreenshotPartial(null, onSavedListener, callback) - else -> Log.w(TAG, "Invalid screenshot option: ${request.type}") + // Provide the task snapshot as the screenshot + ScreenshotRequest( + TAKE_SCREENSHOT_PROVIDED_IMAGE, request.source, + HardwareBitmapBundler.hardwareBitmapToBundle(image), + info.bounds, Insets.NONE, info.taskId, info.userId, info.component + ) + } else { + // Create a new request of the same type which includes the top component + ScreenshotRequest(request.source, request.type, info.component) + } } + + return result } - companion object { - const val TAG: String = "RequestProcessor" + /** + * Note: This is for compatibility with existing Java. Prefer the suspending function when + * calling from a Coroutine context. + * + * @param request the request to process + * @param callback the callback to provide the processed request, invoked from the main thread + */ + fun processAsync(request: ScreenshotRequest, callback: Consumer<ScreenshotRequest>) { + mainScope.launch { + val result = process(request) + callback.accept(result) + } } } + +private const val TAG = "RequestProcessor" diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt new file mode 100644 index 000000000000..3580010cc1e8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot + +import android.annotation.UserIdInt +import android.content.ComponentName +import android.graphics.Rect +import android.view.Display + +/** + * Provides policy decision-making information to screenshot request handling. + */ +interface ScreenshotPolicy { + + /** @return true if the user is a managed profile (a.k.a. work profile) */ + suspend fun isManagedProfile(@UserIdInt userId: Int): Boolean + + /** + * Requests information about the owner of display content which occupies a majority of the + * screenshot and/or has most recently been interacted with at the time the screenshot was + * requested. + * + * @param displayId the id of the display to inspect + * @return content info for the primary content on the display + */ + suspend fun findPrimaryContent(displayId: Int): DisplayContentInfo + + data class DisplayContentInfo( + val component: ComponentName, + val bounds: Rect, + @UserIdInt val userId: Int, + val taskId: Int, + ) + + fun getDefaultDisplayId(): Int = Display.DEFAULT_DISPLAY +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt new file mode 100644 index 000000000000..ba809f676f1e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot + +import android.annotation.UserIdInt +import android.app.ActivityTaskManager +import android.app.ActivityTaskManager.RootTaskInfo +import android.app.IActivityTaskManager +import android.app.WindowConfiguration +import android.app.WindowConfiguration.activityTypeToString +import android.app.WindowConfiguration.windowingModeToString +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.graphics.Rect +import android.os.Process +import android.os.RemoteException +import android.os.UserManager +import android.util.Log +import android.view.Display.DEFAULT_DISPLAY +import com.android.internal.infra.ServiceConnector +import com.android.systemui.SystemUIService +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo +import java.util.Arrays +import javax.inject.Inject +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.withContext + +@SysUISingleton +internal class ScreenshotPolicyImpl @Inject constructor( + context: Context, + private val userMgr: UserManager, + private val atmService: IActivityTaskManager, + @Background val bgDispatcher: CoroutineDispatcher, +) : ScreenshotPolicy { + + private val systemUiContent = + DisplayContentInfo( + ComponentName(context, SystemUIService::class.java), + Rect(), + ActivityTaskManager.INVALID_TASK_ID, + Process.myUserHandle().identifier, + ) + + private val proxyConnector: ServiceConnector<IScreenshotProxy> = + ServiceConnector.Impl( + context, + Intent(context, ScreenshotProxyService::class.java), + Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE, + context.userId, + IScreenshotProxy.Stub::asInterface + ) + + override fun getDefaultDisplayId(): Int { + return DEFAULT_DISPLAY + } + + override suspend fun isManagedProfile(@UserIdInt userId: Int): Boolean { + return withContext(bgDispatcher) { userMgr.isManagedProfile(userId) } + } + + private fun nonPipVisibleTask(info: RootTaskInfo): Boolean { + return info.windowingMode != WindowConfiguration.WINDOWING_MODE_PINNED && + info.isVisible && + info.isRunning && + info.numActivities > 0 && + info.topActivity != null && + info.childTaskIds.isNotEmpty() + } + + /** + * Uses RootTaskInfo from ActivityTaskManager to guess at the primary focused task within a + * display. If no task is visible or the top task is covered by a system window, the info + * reported will reference a SystemUI component instead. + */ + override suspend fun findPrimaryContent(displayId: Int): DisplayContentInfo { + // Determine if the notification shade is expanded. If so, task windows are not + // visible behind it, so the screenshot should instead be associated with SystemUI. + if (isNotificationShadeExpanded()) { + return systemUiContent + } + + val taskInfoList = getAllRootTaskInfosOnDisplay(displayId) + if (DEBUG) { + debugLogRootTaskInfos(taskInfoList) + } + + // If no visible task is located, then report SystemUI as the foreground content + val target = taskInfoList.firstOrNull(::nonPipVisibleTask) ?: return systemUiContent + + val topActivity: ComponentName = target.topActivity ?: error("should not be null") + val topChildTask = target.childTaskIds.size - 1 + val childTaskId = target.childTaskIds[topChildTask] + val childTaskUserId = target.childTaskUserIds[topChildTask] + val childTaskBounds = target.childTaskBounds[topChildTask] + + return DisplayContentInfo(topActivity, childTaskBounds, childTaskId, childTaskUserId) + } + + private fun debugLogRootTaskInfos(taskInfoList: List<RootTaskInfo>) { + for (info in taskInfoList) { + Log.d( + TAG, + "[root task info] " + + "taskId=${info.taskId} " + + "parentTaskId=${info.parentTaskId} " + + "position=${info.position} " + + "positionInParent=${info.positionInParent} " + + "isVisible=${info.isVisible()} " + + "visible=${info.visible} " + + "isFocused=${info.isFocused} " + + "isSleeping=${info.isSleeping} " + + "isRunning=${info.isRunning} " + + "windowMode=${windowingModeToString(info.windowingMode)} " + + "activityType=${activityTypeToString(info.activityType)} " + + "topActivity=${info.topActivity} " + + "topActivityInfo=${info.topActivityInfo} " + + "numActivities=${info.numActivities} " + + "childTaskIds=${Arrays.toString(info.childTaskIds)} " + + "childUserIds=${Arrays.toString(info.childTaskUserIds)} " + + "childTaskBounds=${Arrays.toString(info.childTaskBounds)} " + + "childTaskNames=${Arrays.toString(info.childTaskNames)}" + ) + + for (j in 0 until info.childTaskIds.size) { + Log.d(TAG, " *** [$j] ******") + Log.d(TAG, " *** childTaskIds[$j]: ${info.childTaskIds[j]}") + Log.d(TAG, " *** childTaskUserIds[$j]: ${info.childTaskUserIds[j]}") + Log.d(TAG, " *** childTaskBounds[$j]: ${info.childTaskBounds[j]}") + Log.d(TAG, " *** childTaskNames[$j]: ${info.childTaskNames[j]}") + } + } + } + + private suspend fun getAllRootTaskInfosOnDisplay(displayId: Int): List<RootTaskInfo> = + withContext(bgDispatcher) { + try { + atmService.getAllRootTaskInfosOnDisplay(displayId) + } catch (e: RemoteException) { + Log.e(TAG, "getAllRootTaskInfosOnDisplay", e) + listOf() + } + } + + private suspend fun isNotificationShadeExpanded(): Boolean = suspendCoroutine { k -> + proxyConnector + .postForResult { it.isNotificationShadeExpanded } + .whenComplete { expanded, error -> + if (error != null) { + Log.e(TAG, "isNotificationShadeExpanded", error) + } + k.resume(expanded ?: false) + } + } + + companion object { + const val TAG: String = "ScreenshotPolicyImpl" + const val DEBUG: Boolean = false + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt new file mode 100644 index 000000000000..9654e03e506e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.screenshot + +import android.app.Service +import android.content.Intent +import android.os.IBinder +import android.util.Log +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager +import javax.inject.Inject + +/** + * Provides state from the main SystemUI process on behalf of the Screenshot process. + */ +internal class ScreenshotProxyService @Inject constructor( + private val mExpansionMgr: PanelExpansionStateManager +) : Service() { + + private val mBinder: IBinder = object : IScreenshotProxy.Stub() { + /** + * @return true when the notification shade is partially or fully expanded. + */ + override fun isNotificationShadeExpanded(): Boolean { + val expanded = !mExpansionMgr.isClosed() + Log.d(TAG, "isNotificationShadeExpanded(): $expanded") + return expanded + } + } + + override fun onBind(intent: Intent): IBinder? { + Log.d(TAG, "onBind: $intent") + return mBinder + } + + companion object { + const val TAG = "ScreenshotProxyService" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 7bf3217e5f15..35f32caffe21 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -22,6 +22,7 @@ import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS; import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_PROCESS_COMPLETE; import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_URI; import static com.android.systemui.flags.Flags.SCREENSHOT_REQUEST_PROCESSOR; +import static com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY; import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK; import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS; import static com.android.systemui.screenshot.LogConfig.DEBUG_SERVICE; @@ -52,8 +53,7 @@ import android.util.Log; import android.view.WindowManager; import android.widget.Toast; -import androidx.annotation.NonNull; - +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEventLogger; import com.android.internal.util.ScreenshotHelper; import com.android.systemui.R; @@ -69,7 +69,7 @@ import javax.inject.Inject; public class TakeScreenshotService extends Service { private static final String TAG = logTag(TakeScreenshotService.class); - private ScreenshotController mScreenshot; + private final ScreenshotController mScreenshot; private final UserManager mUserManager; private final DevicePolicyManager mDevicePolicyManager; @@ -97,7 +97,7 @@ public class TakeScreenshotService extends Service { }; /** Informs about coarse grained state of the Controller. */ - interface RequestCallback { + public interface RequestCallback { /** Respond to the current request indicating the screenshot request failed. */ void reportError(); @@ -124,6 +124,7 @@ public class TakeScreenshotService extends Service { mBgExecutor = bgExecutor; mFeatureFlags = featureFlags; mFeatureFlags.addListener(SCREENSHOT_REQUEST_PROCESSOR, FlagEvent::requestNoRestart); + mFeatureFlags.addListener(SCREENSHOT_WORK_PROFILE_POLICY, FlagEvent::requestNoRestart); mProcessor = processor; } @@ -135,7 +136,7 @@ public class TakeScreenshotService extends Service { } @Override - public IBinder onBind(@NonNull Intent intent) { + public IBinder onBind(Intent intent) { registerReceiver(mCloseSystemDialogs, new IntentFilter(ACTION_CLOSE_SYSTEM_DIALOGS), Context.RECEIVER_EXPORTED); final Messenger m = new Messenger(mHandler); @@ -150,10 +151,7 @@ public class TakeScreenshotService extends Service { if (DEBUG_SERVICE) { Log.d(TAG, "onUnbind"); } - if (mScreenshot != null) { - mScreenshot.removeWindow(); - mScreenshot = null; - } + mScreenshot.removeWindow(); unregisterReceiver(mCloseSystemDialogs); return false; } @@ -161,10 +159,7 @@ public class TakeScreenshotService extends Service { @Override public void onDestroy() { super.onDestroy(); - if (mScreenshot != null) { - mScreenshot.onDestroy(); - mScreenshot = null; - } + mScreenshot.onDestroy(); if (DEBUG_SERVICE) { Log.d(TAG, "onDestroy"); } @@ -188,13 +183,23 @@ public class TakeScreenshotService extends Service { } } - /** Respond to incoming Message via Binder (Messenger) */ @MainThread private boolean handleMessage(Message msg) { final Messenger replyTo = msg.replyTo; - final Consumer<Uri> uriConsumer = (uri) -> reportUri(replyTo, uri); - RequestCallback requestCallback = new RequestCallbackImpl(replyTo); + final Consumer<Uri> onSaved = (uri) -> reportUri(replyTo, uri); + RequestCallback callback = new RequestCallbackImpl(replyTo); + + ScreenshotHelper.ScreenshotRequest request = + (ScreenshotHelper.ScreenshotRequest) msg.obj; + handleRequest(request, onSaved, callback); + return true; + } + + @MainThread + @VisibleForTesting + void handleRequest(ScreenshotHelper.ScreenshotRequest request, Consumer<Uri> onSaved, + RequestCallback callback) { // If the storage for this user is locked, we have no place to store // the screenshot, so skip taking it instead of showing a misleading // animation and error notification. @@ -202,8 +207,8 @@ public class TakeScreenshotService extends Service { Log.w(TAG, "Skipping screenshot because storage is locked!"); mNotificationsController.notifyScreenshotError( R.string.screenshot_failed_to_save_user_locked_text); - requestCallback.reportError(); - return true; + callback.reportError(); + return; } if (mDevicePolicyManager.getScreenCaptureDisabled(null, UserHandle.USER_ALL)) { @@ -215,63 +220,64 @@ public class TakeScreenshotService extends Service { () -> mContext.getString(R.string.screenshot_blocked_by_admin)); mHandler.post(() -> Toast.makeText(mContext, blockedByAdminText, Toast.LENGTH_SHORT).show()); - requestCallback.reportError(); + callback.reportError(); }); - return true; + return; } - ScreenshotHelper.ScreenshotRequest screenshotRequest = - (ScreenshotHelper.ScreenshotRequest) msg.obj; - - ComponentName topComponent = screenshotRequest.getTopComponent(); - mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshotRequest.getSource()), 0, - topComponent == null ? "" : topComponent.getPackageName()); - if (mFeatureFlags.isEnabled(SCREENSHOT_REQUEST_PROCESSOR)) { Log.d(TAG, "handleMessage: Using request processor"); - mProcessor.processRequest(screenshotRequest, uriConsumer, requestCallback); - return true; + mProcessor.processAsync(request, + (r) -> dispatchToController(r, onSaved, callback)); } - switch (screenshotRequest.getType()) { + dispatchToController(request, onSaved, callback); + } + + private void dispatchToController(ScreenshotHelper.ScreenshotRequest request, + Consumer<Uri> uriConsumer, RequestCallback callback) { + + ComponentName topComponent = request.getTopComponent(); + mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(request.getSource()), 0, + topComponent == null ? "" : topComponent.getPackageName()); + + switch (request.getType()) { case WindowManager.TAKE_SCREENSHOT_FULLSCREEN: if (DEBUG_SERVICE) { Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_FULLSCREEN"); } - mScreenshot.takeScreenshotFullscreen(topComponent, uriConsumer, requestCallback); + mScreenshot.takeScreenshotFullscreen(topComponent, uriConsumer, callback); break; case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION: if (DEBUG_SERVICE) { Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_SELECTED_REGION"); } - mScreenshot.takeScreenshotPartial(topComponent, uriConsumer, requestCallback); + mScreenshot.takeScreenshotPartial(topComponent, uriConsumer, callback); break; case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE: if (DEBUG_SERVICE) { Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_PROVIDED_IMAGE"); } Bitmap screenshot = ScreenshotHelper.HardwareBitmapBundler.bundleToHardwareBitmap( - screenshotRequest.getBitmapBundle()); - Rect screenBounds = screenshotRequest.getBoundsInScreen(); - Insets insets = screenshotRequest.getInsets(); - int taskId = screenshotRequest.getTaskId(); - int userId = screenshotRequest.getUserId(); + request.getBitmapBundle()); + Rect screenBounds = request.getBoundsInScreen(); + Insets insets = request.getInsets(); + int taskId = request.getTaskId(); + int userId = request.getUserId(); if (screenshot == null) { Log.e(TAG, "Got null bitmap from screenshot message"); mNotificationsController.notifyScreenshotError( R.string.screenshot_failed_to_capture_text); - requestCallback.reportError(); + callback.reportError(); } else { mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets, - taskId, userId, topComponent, uriConsumer, requestCallback); + taskId, userId, topComponent, uriConsumer, callback); } break; default: - Log.w(TAG, "Invalid screenshot option: " + msg.what); - return false; + Log.w(TAG, "Invalid screenshot option: " + request.getType()); } - return true; } private static void sendComplete(Messenger target) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java index 3e44258744f2..fdb01000b837 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,9 @@ import android.app.Service; import com.android.systemui.screenshot.ImageCapture; import com.android.systemui.screenshot.ImageCaptureImpl; +import com.android.systemui.screenshot.ScreenshotPolicy; +import com.android.systemui.screenshot.ScreenshotPolicyImpl; +import com.android.systemui.screenshot.ScreenshotProxyService; import com.android.systemui.screenshot.TakeScreenshotService; import dagger.Binds; @@ -33,12 +36,20 @@ import dagger.multibindings.IntoMap; @Module public abstract class ScreenshotModule { - /** */ @Binds @IntoMap @ClassKey(TakeScreenshotService.class) - public abstract Service bindTakeScreenshotService(TakeScreenshotService service); + abstract Service bindTakeScreenshotService(TakeScreenshotService service); @Binds - public abstract ImageCapture bindImageCapture(ImageCaptureImpl capture); + @IntoMap + @ClassKey(ScreenshotProxyService.class) + abstract Service bindScreenshotProxyService(ScreenshotProxyService service); + + @Binds + abstract ScreenshotPolicy bindScreenshotPolicyImpl(ScreenshotPolicyImpl impl); + + @Binds + abstract ImageCapture bindImageCaptureImpl(ImageCaptureImpl capture); + } diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt index 5e908d9cd29f..1558ac533137 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt @@ -76,6 +76,6 @@ interface UserTracker : UserContentResolverProvider, UserContextProvider { * Notifies that the current user's profiles have changed. */ @JvmDefault - fun onProfilesChanged(profiles: List<UserInfo>) {} + fun onProfilesChanged(profiles: List<@JvmSuppressWildcards UserInfo>) {} } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt index 0f9ac360cbe1..fab70fc2eebd 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt @@ -77,6 +77,7 @@ import javax.inject.Named class LargeScreenShadeHeaderController @Inject constructor( @Named(LARGE_SCREEN_SHADE_HEADER) private val header: View, private val statusBarIconController: StatusBarIconController, + private val tintedIconManagerFactory: StatusBarIconController.TintedIconManager.Factory, private val privacyIconsController: HeaderPrivacyIconsController, private val insetsProvider: StatusBarContentInsetsProvider, private val configurationController: ConfigurationController, @@ -259,7 +260,7 @@ class LargeScreenShadeHeaderController @Inject constructor( batteryMeterViewController.ignoreTunerUpdates() batteryIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE) - iconManager = StatusBarIconController.TintedIconManager(iconContainer, featureFlags) + iconManager = tintedIconManagerFactory.create(iconContainer) iconManager.setTint( Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary) ) diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index cf416c52bf3a..e6d10228dc55 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -54,7 +54,6 @@ import android.graphics.ColorFilter; import android.graphics.Insets; import android.graphics.Paint; import android.graphics.PixelFormat; -import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Region; import android.graphics.drawable.Drawable; @@ -448,7 +447,6 @@ public final class NotificationPanelViewController extends PanelViewController { */ private boolean mQsAnimatorExpand; private boolean mIsLaunchTransitionFinished; - private boolean mOnlyAffordanceInThisMotion; private ValueAnimator mQsSizeChangeAnimator; private boolean mQsScrimEnabled = true; @@ -726,6 +724,7 @@ public final class NotificationPanelViewController extends PanelViewController { AccessibilityManager accessibilityManager, @DisplayId int displayId, KeyguardUpdateMonitor keyguardUpdateMonitor, MetricsLogger metricsLogger, + ShadeLogger shadeLogger, ConfigurationController configurationController, Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilder, StatusBarTouchableRegionManager statusBarTouchableRegionManager, @@ -792,6 +791,7 @@ public final class NotificationPanelViewController extends PanelViewController { panelExpansionStateManager, ambientState, interactionJankMonitor, + shadeLogger, systemClock); mView = view; mVibratorHelper = vibratorHelper; @@ -1887,6 +1887,8 @@ public final class NotificationPanelViewController extends PanelViewController { } if (mQsExpansionAnimator != null) { mInitialHeightOnTouch = mQsExpansionHeight; + mShadeLog.logMotionEvent(event, + "onQsIntercept: down action, QS tracking enabled"); mQsTracking = true; traceQsJank(true /* startTracing */, false /* wasCancelled */); mNotificationStackScrollLayoutController.cancelLongPress(); @@ -1914,12 +1916,16 @@ public final class NotificationPanelViewController extends PanelViewController { setQsExpansion(h + mInitialHeightOnTouch); trackMovement(event); return true; + } else { + mShadeLog.logMotionEvent(event, + "onQsIntercept: move ignored because qs tracking disabled"); } if ((h > getTouchSlop(event) || (h < -getTouchSlop(event) && mQsExpanded)) && Math.abs(h) > Math.abs(x - mInitialTouchX) && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) { if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept - start tracking expansion"); mView.getParent().requestDisallowInterceptTouchEvent(true); + mShadeLog.onQsInterceptMoveQsTrackingEnabled(h); mQsTracking = true; traceQsJank(true /* startTracing */, false /* wasCancelled */); onQsExpansionStarted(); @@ -1935,6 +1941,7 @@ public final class NotificationPanelViewController extends PanelViewController { case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: trackMovement(event); + mShadeLog.logMotionEvent(event, "onQsIntercept: up action, QS tracking disabled"); mQsTracking = false; break; } @@ -1972,7 +1979,6 @@ public final class NotificationPanelViewController extends PanelViewController { private void initDownStates(MotionEvent event) { if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - mOnlyAffordanceInThisMotion = false; mQsTouchAboveFalsingThreshold = mQsFullyExpanded; mDozingOnDown = isDozing(); mDownX = event.getX(); @@ -2111,6 +2117,7 @@ public final class NotificationPanelViewController extends PanelViewController { && collapsedQs && isQsExpansionEnabled(); if (action == MotionEvent.ACTION_DOWN && expandedShadeCollapsedQs) { // Down in the empty area while fully expanded - go to QS. + mShadeLog.logMotionEvent(event, "handleQsTouch: down action, QS tracking enabled"); mQsTracking = true; traceQsJank(true /* startTracing */, false /* wasCancelled */); mConflictingQsExpansionGesture = true; @@ -2125,6 +2132,8 @@ public final class NotificationPanelViewController extends PanelViewController { if (!mQsExpandImmediate && mQsTracking) { onQsTouch(event); if (!mConflictingQsExpansionGesture && !mSplitShadeEnabled) { + mShadeLog.logMotionEvent(event, + "handleQsTouch: not immediate expand or conflicting gesture"); return true; } } @@ -2192,6 +2201,7 @@ public final class NotificationPanelViewController extends PanelViewController { event.getX(), event.getY(), -1)) { if (DEBUG_LOGCAT) Log.d(TAG, "handleQsDown"); mFalsingCollector.onQsDown(); + mShadeLog.logMotionEvent(event, "handleQsDown: down action, QS tracking enabled"); mQsTracking = true; onQsExpansionStarted(); mInitialHeightOnTouch = mQsExpansionHeight; @@ -2274,6 +2284,7 @@ public final class NotificationPanelViewController extends PanelViewController { switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: + mShadeLog.logMotionEvent(event, "onQsTouch: down action, QS tracking enabled"); mQsTracking = true; traceQsJank(true /* startTracing */, false /* wasCancelled */); mInitialTouchY = y; @@ -2300,6 +2311,7 @@ public final class NotificationPanelViewController extends PanelViewController { case MotionEvent.ACTION_MOVE: if (DEBUG_LOGCAT) Log.d(TAG, "onQSTouch move"); + mShadeLog.logMotionEvent(event, "onQsTouch: move action, setting QS expansion"); setQsExpansion(h + mInitialHeightOnTouch); if (h >= getFalsingThreshold()) { mQsTouchAboveFalsingThreshold = true; @@ -2309,6 +2321,8 @@ public final class NotificationPanelViewController extends PanelViewController { case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: + mShadeLog.logMotionEvent(event, + "onQsTouch: up/cancel action, QS tracking disabled"); mQsTracking = false; mTrackingPointer = -1; trackMovement(event); @@ -3131,8 +3145,8 @@ public final class NotificationPanelViewController extends PanelViewController { positionClockAndNotifications(); } } - if (mQsExpandImmediate || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null - && !mQsExpansionFromOverscroll) { + if (mQsExpandImmediate || (mQsExpanded && !mQsTracking && mQsExpansionAnimator == null + && !mQsExpansionFromOverscroll)) { float t; if (mKeyguardShowing) { @@ -3792,13 +3806,12 @@ public final class NotificationPanelViewController extends PanelViewController { * * @param dozing {@code true} when dozing. * @param animate if transition should be animated. - * @param wakeUpTouchLocation touch event location - if woken up by SLPI sensor. */ - public void setDozing(boolean dozing, boolean animate, PointF wakeUpTouchLocation) { + public void setDozing(boolean dozing, boolean animate) { if (dozing == mDozing) return; mView.setDozing(dozing); mDozing = dozing; - mNotificationStackScrollLayoutController.setDozing(mDozing, animate, wakeUpTouchLocation); + mNotificationStackScrollLayoutController.setDozing(mDozing, animate); mKeyguardBottomArea.setDozing(mDozing, animate); mKeyguardBottomAreaInteractorProvider.get().setAnimateDozingTransitions(animate); mKeyguardStatusBarViewController.setDozing(mDozing); @@ -4224,6 +4237,7 @@ public final class NotificationPanelViewController extends PanelViewController { || mPulseExpansionHandler.isExpanding(); if (pulseShouldGetTouch && mPulseExpansionHandler.onTouchEvent(event)) { // We're expanding all the other ones shouldn't get this anymore + mShadeLog.logMotionEvent(event, "onTouch: PulseExpansionHandler handled event"); return true; } if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp() @@ -4231,14 +4245,10 @@ public final class NotificationPanelViewController extends PanelViewController { && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) { mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1); } - boolean handled = false; - if (mOnlyAffordanceInThisMotion) { - return true; - } - handled |= mHeadsUpTouchHelper.onTouchEvent(event); + boolean handled = mHeadsUpTouchHelper.onTouchEvent(event); if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) { - if (DEBUG_LOGCAT) Log.d(TAG, "handleQsTouch true"); + mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event"); return true; } if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) { @@ -4729,7 +4739,7 @@ public final class NotificationPanelViewController extends PanelViewController { * change. */ public void showAodUi() { - setDozing(true /* dozing */, false /* animate */, null); + setDozing(true /* dozing */, false /* animate */); mStatusBarStateController.setUpcomingState(KEYGUARD); mEntryManager.updateNotifications("showAodUi"); mStatusBarStateListener.onStateChanged(KEYGUARD); @@ -4810,6 +4820,8 @@ public final class NotificationPanelViewController extends PanelViewController { } } else if (!mQsExpanded && mQsExpansionAnimator == null) { setQsExpansion(mQsMinExpansionHeight + mLastOverscroll); + } else { + mShadeLog.v("onLayoutChange: qs expansion not set"); } updateExpandedHeight(getExpandedHeight()); updateHeader(); diff --git a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java index 4aad245f96fd..73eaa852e345 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java @@ -202,6 +202,8 @@ public abstract class PanelViewController { private final InteractionJankMonitor mInteractionJankMonitor; protected final SystemClock mSystemClock; + protected final ShadeLogger mShadeLog; + protected abstract void onExpandingFinished(); protected void onExpandingStarted() { @@ -242,6 +244,7 @@ public abstract class PanelViewController { PanelExpansionStateManager panelExpansionStateManager, AmbientState ambientState, InteractionJankMonitor interactionJankMonitor, + ShadeLogger shadeLogger, SystemClock systemClock) { keyguardStateController.addCallback(new KeyguardStateController.Callback() { @Override @@ -254,6 +257,7 @@ public abstract class PanelViewController { mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mLockscreenGestureLogger = lockscreenGestureLogger; mPanelExpansionStateManager = panelExpansionStateManager; + mShadeLog = shadeLogger; TouchHandler touchHandler = createTouchHandler(); mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { @Override @@ -1275,9 +1279,16 @@ public abstract class PanelViewController { @Override public boolean onTouch(View v, MotionEvent event) { - if (mInstantExpanding || (mTouchDisabled - && event.getActionMasked() != MotionEvent.ACTION_CANCEL) || (mMotionAborted - && event.getActionMasked() != MotionEvent.ACTION_DOWN)) { + if (mInstantExpanding) { + mShadeLog.logMotionEvent(event, "onTouch: touch ignored due to instant expanding"); + return false; + } + if (mTouchDisabled && event.getActionMasked() != MotionEvent.ACTION_CANCEL) { + mShadeLog.logMotionEvent(event, "onTouch: non-cancel action, touch disabled"); + return false; + } + if (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN) { + mShadeLog.logMotionEvent(event, "onTouch: non-down action, motion was aborted"); return false; } @@ -1287,6 +1298,7 @@ public abstract class PanelViewController { // Turn off tracking if it's on or the shade can get stuck in the down position. onTrackingStopped(true /* expand */); } + mShadeLog.logMotionEvent(event, "onTouch: drag not enabled"); return false; } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt new file mode 100644 index 000000000000..f1e44ce5736e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt @@ -0,0 +1,48 @@ +package com.android.systemui.shade + +import android.view.MotionEvent +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel +import com.android.systemui.log.LogMessage +import com.android.systemui.log.dagger.ShadeLog +import com.google.errorprone.annotations.CompileTimeConstant +import javax.inject.Inject + +private const val TAG = "systemui.shade" + +/** Lightweight logging utility for the Shade. */ +class ShadeLogger @Inject constructor( + @ShadeLog + private val buffer: LogBuffer +) { + fun v(@CompileTimeConstant msg: String) { + buffer.log(TAG, LogLevel.VERBOSE, msg) + } + + private inline fun log( + logLevel: LogLevel, + initializer: LogMessage.() -> Unit, + noinline printer: LogMessage.() -> String + ) { + buffer.log(TAG, logLevel, initializer, printer) + } + + fun onQsInterceptMoveQsTrackingEnabled(h: Float) { + log(LogLevel.VERBOSE, + { double1 = h.toDouble() }, + { "onQsIn[tercept: move action, QS tracking enabled. h = $double1" }) + } + + fun logMotionEvent(event: MotionEvent, message: String) { + log(LogLevel.VERBOSE, { + str1 = message + long1 = event.eventTime + long2 = event.downTime + int1 = event.action + int2 = event.classification + double1 = event.y.toDouble() + }, { + "$str1\neventTime=$long1,downTime=$long2,y=$double1,action=$int1,classification=$int2" + }) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarWifiView.kt new file mode 100644 index 000000000000..4d53064d047d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarWifiView.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar + +import android.content.Context +import android.util.AttributeSet +import android.widget.FrameLayout + +/** + * A temporary base class that's shared between our old status bar wifi view implementation + * ([StatusBarWifiView]) and our new status bar wifi view implementation + * ([ModernStatusBarWifiView]). + * + * Once our refactor is over, we should be able to delete this go-between class and the old view + * class. + */ +abstract class BaseStatusBarWifiView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttrs: Int = 0, +) : FrameLayout(context, attrs, defStyleAttrs), StatusIconDisplayable diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java index 0280e0b729a9..c04bc8289f81 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java @@ -303,11 +303,6 @@ public class StatusBarStateControllerImpl implements } @Override - public void setDozeAmount(float dozeAmount, boolean animated) { - setAndInstrumentDozeAmount(null, dozeAmount, animated); - } - - @Override public void setAndInstrumentDozeAmount(View view, float dozeAmount, boolean animated) { if (mDarkAnimator != null && mDarkAnimator.isRunning()) { if (animated && mDozeAmountTarget == dozeAmount) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java index a6986d797833..5aee62e3e89f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java @@ -28,7 +28,6 @@ import android.util.AttributeSet; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; -import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; @@ -41,8 +40,7 @@ import java.util.ArrayList; /** * Start small: StatusBarWifiView will be able to layout from a WifiIconState */ -public class StatusBarWifiView extends FrameLayout implements DarkReceiver, - StatusIconDisplayable { +public class StatusBarWifiView extends BaseStatusBarWifiView implements DarkReceiver { private static final String TAG = "StatusBarWifiView"; /// Used to show etc dots @@ -80,11 +78,6 @@ public class StatusBarWifiView extends FrameLayout implements DarkReceiver, super(context, attrs, defStyleAttr); } - public StatusBarWifiView(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - public void setSlot(String slot) { mSlot = slot; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java index 2b3190159ecd..2cc77384fb2a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java @@ -99,14 +99,6 @@ public interface SysuiStatusBarStateController extends StatusBarStateController boolean setIsDozing(boolean isDozing); /** - * Changes the current doze amount. - * - * @param dozeAmount New doze/dark amount. - * @param animated If change should be animated or not. This will cancel current animations. - */ - void setDozeAmount(float dozeAmount, boolean animated); - - /** * Changes the current doze amount, also starts the * {@link com.android.internal.jank.InteractionJankMonitor InteractionJankMonitor} as possible. * diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiIcons.java index 3c449ad270ef..7097568c4adf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiIcons.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiIcons.java @@ -23,7 +23,7 @@ import com.android.settingslib.SignalIcon.IconGroup; /** */ public class WifiIcons { - static final int[] WIFI_FULL_ICONS = { + public static final int[] WIFI_FULL_ICONS = { com.android.internal.R.drawable.ic_wifi_signal_0, com.android.internal.R.drawable.ic_wifi_signal_1, com.android.internal.R.drawable.ic_wifi_signal_2, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt index a509fcaad4ee..8f8813b80b5f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt @@ -31,11 +31,13 @@ import android.os.UserHandle import android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS import android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS import android.util.Log +import android.view.ContextThemeWrapper import android.view.View import android.view.ViewGroup import com.android.settingslib.Utils import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags @@ -46,6 +48,7 @@ import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.settings.UserTracker +import com.android.systemui.shared.regionsampling.RegionSamplingInstance import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.DeviceProvisionedController @@ -60,22 +63,23 @@ import javax.inject.Inject */ @SysUISingleton class LockscreenSmartspaceController @Inject constructor( - private val context: Context, - private val featureFlags: FeatureFlags, - private val smartspaceManager: SmartspaceManager, - private val activityStarter: ActivityStarter, - private val falsingManager: FalsingManager, - private val secureSettings: SecureSettings, - private val userTracker: UserTracker, - private val contentResolver: ContentResolver, - private val configurationController: ConfigurationController, - private val statusBarStateController: StatusBarStateController, - private val deviceProvisionedController: DeviceProvisionedController, - private val bypassController: KeyguardBypassController, - private val execution: Execution, - @Main private val uiExecutor: Executor, - @Main private val handler: Handler, - optionalPlugin: Optional<BcSmartspaceDataPlugin> + private val context: Context, + private val featureFlags: FeatureFlags, + private val smartspaceManager: SmartspaceManager, + private val activityStarter: ActivityStarter, + private val falsingManager: FalsingManager, + private val secureSettings: SecureSettings, + private val userTracker: UserTracker, + private val contentResolver: ContentResolver, + private val configurationController: ConfigurationController, + private val statusBarStateController: StatusBarStateController, + private val deviceProvisionedController: DeviceProvisionedController, + private val bypassController: KeyguardBypassController, + private val execution: Execution, + @Main private val uiExecutor: Executor, + @Background private val bgExecutor: Executor, + @Main private val handler: Handler, + optionalPlugin: Optional<BcSmartspaceDataPlugin> ) { companion object { private const val TAG = "LockscreenSmartspaceController" @@ -86,15 +90,36 @@ class LockscreenSmartspaceController @Inject constructor( // Smartspace can be used on multiple displays, such as when the user casts their screen private var smartspaceViews = mutableSetOf<SmartspaceView>() + private var regionSamplingInstances = + mutableMapOf<SmartspaceView, RegionSamplingInstance>() + + private val regionSamplingEnabled = + featureFlags.isEnabled(Flags.REGION_SAMPLING) private var showNotifications = false private var showSensitiveContentForCurrentUser = false private var showSensitiveContentForManagedUser = false private var managedUserHandle: UserHandle? = null + private val updateFun = object : RegionSamplingInstance.UpdateColorCallback { + override fun updateColors() { + updateTextColorFromRegionSampler() + } + } + var stateChangeListener = object : View.OnAttachStateChangeListener { override fun onViewAttachedToWindow(v: View) { smartspaceViews.add(v as SmartspaceView) + + var regionSamplingInstance = RegionSamplingInstance( + v, + uiExecutor, + bgExecutor, + regionSamplingEnabled, + updateFun + ) + regionSamplingInstance.startRegionSampler() + regionSamplingInstances.put(v, regionSamplingInstance) connectSession() updateTextColorFromWallpaper() @@ -104,6 +129,10 @@ class LockscreenSmartspaceController @Inject constructor( override fun onViewDetachedFromWindow(v: View) { smartspaceViews.remove(v as SmartspaceView) + var regionSamplingInstance = regionSamplingInstances.getValue(v) + regionSamplingInstance.stopRegionSampler() + regionSamplingInstances.remove(v) + if (smartspaceViews.isEmpty()) { disconnect() } @@ -332,9 +361,29 @@ class LockscreenSmartspaceController @Inject constructor( } } + private fun updateTextColorFromRegionSampler() { + smartspaceViews.forEach { + val isRegionDark = regionSamplingInstances.getValue(it).currentRegionDarkness() + val themeID = if (isRegionDark.isDark) { + R.style.Theme_SystemUI + } else { + R.style.Theme_SystemUI_LightWallpaper + } + val themedContext = ContextThemeWrapper(context, themeID) + val wallpaperTextColor = + Utils.getColorAttrDefaultColor(themedContext, R.attr.wallpaperTextColor) + it.setPrimaryTextColor(wallpaperTextColor) + } + } + private fun updateTextColorFromWallpaper() { - val wallpaperTextColor = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColor) - smartspaceViews.forEach { it.setPrimaryTextColor(wallpaperTextColor) } + if (!regionSamplingEnabled) { + val wallpaperTextColor = + Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColor) + smartspaceViews.forEach { it.setPrimaryTextColor(wallpaperTextColor) } + } else { + updateTextColorFromRegionSampler() + } } private fun reloadSmartspace() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt index 9e5dab1152ec..f8449ae8807b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt @@ -90,6 +90,18 @@ data class ListAttachState private constructor( stableIndex = -1 } + /** + * Erases bookkeeping traces stored on an entry when it is removed from the notif list. + * This can happen if the entry is removed from a group that was broken up or if the entry was + * filtered out during any of the filtering steps. + */ + fun detach() { + parent = null + section = null + promoter = null + // stableIndex = -1 // TODO(b/241229236): Clear this once we fix the stability fragility + } + companion object { @JvmStatic fun create(): ListAttachState { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index 14cc6bf1ea41..3eaa988e8389 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -958,9 +958,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { * filtered out during any of the filtering steps. */ private void annulAddition(ListEntry entry) { - entry.setParent(null); - entry.getAttachState().setSection(null); - entry.getAttachState().setPromoter(null); + entry.getAttachState().detach(); } private void assignSections() { @@ -1198,9 +1196,9 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { o2.getSectionIndex()); if (cmp != 0) return cmp; - int index1 = canReorder(o1) ? -1 : o1.getPreviousAttachState().getStableIndex(); - int index2 = canReorder(o2) ? -1 : o2.getPreviousAttachState().getStableIndex(); - cmp = Integer.compare(index1, index2); + cmp = Integer.compare( + getStableOrderIndex(o1), + getStableOrderIndex(o2)); if (cmp != 0) return cmp; NotifComparator sectionComparator = getSectionComparator(o1, o2); @@ -1214,31 +1212,32 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { if (cmp != 0) return cmp; } - final NotificationEntry rep1 = o1.getRepresentativeEntry(); - final NotificationEntry rep2 = o2.getRepresentativeEntry(); - cmp = rep1.getRanking().getRank() - rep2.getRanking().getRank(); + cmp = Integer.compare( + o1.getRepresentativeEntry().getRanking().getRank(), + o2.getRepresentativeEntry().getRanking().getRank()); if (cmp != 0) return cmp; - cmp = Long.compare( - rep2.getSbn().getNotification().when, - rep1.getSbn().getNotification().when); + cmp = -1 * Long.compare( + o1.getRepresentativeEntry().getSbn().getNotification().when, + o2.getRepresentativeEntry().getSbn().getNotification().when); return cmp; }; private final Comparator<NotificationEntry> mGroupChildrenComparator = (o1, o2) -> { - int index1 = canReorder(o1) ? -1 : o1.getPreviousAttachState().getStableIndex(); - int index2 = canReorder(o2) ? -1 : o2.getPreviousAttachState().getStableIndex(); - int cmp = Integer.compare(index1, index2); + int cmp = Integer.compare( + getStableOrderIndex(o1), + getStableOrderIndex(o2)); if (cmp != 0) return cmp; - cmp = o1.getRepresentativeEntry().getRanking().getRank() - - o2.getRepresentativeEntry().getRanking().getRank(); + cmp = Integer.compare( + o1.getRepresentativeEntry().getRanking().getRank(), + o2.getRepresentativeEntry().getRanking().getRank()); if (cmp != 0) return cmp; - cmp = Long.compare( - o2.getRepresentativeEntry().getSbn().getNotification().when, - o1.getRepresentativeEntry().getSbn().getNotification().when); + cmp = -1 * Long.compare( + o1.getRepresentativeEntry().getSbn().getNotification().when, + o2.getRepresentativeEntry().getSbn().getNotification().when); return cmp; }; @@ -1248,8 +1247,16 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { */ private boolean mForceReorderable = false; - private boolean canReorder(ListEntry entry) { - return mForceReorderable || getStabilityManager().isEntryReorderingAllowed(entry); + private int getStableOrderIndex(ListEntry entry) { + if (mForceReorderable) { + // this is used to determine if the list is correctly sorted + return -1; + } + if (getStabilityManager().isEntryReorderingAllowed(entry)) { + // let the stability manager constrain or allow reordering + return -1; + } + return entry.getPreviousAttachState().getStableIndex(); } private boolean applyFilters(NotificationEntry entry, long now, List<NotifFilter> filters) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 9ad906c83e10..855390d75ff8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -20,7 +20,6 @@ import static android.app.Notification.Action.SEMANTIC_ACTION_MARK_CONVERSATION_ import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP; -import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -236,11 +235,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView */ private boolean mIsHeadsUp; - /** - * Whether or not the notification should be redacted on the lock screen, i.e has sensitive - * content which should be redacted on the lock screen. - */ - private boolean mNeedsRedaction; private boolean mLastChronometerRunning = true; private ViewStub mChildrenContainerStub; private GroupMembershipManager mGroupMembershipManager; @@ -1502,23 +1496,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mUseIncreasedHeadsUpHeight = use; } - /** @deprecated TODO: Remove this when the old pipeline code is removed. */ - @Deprecated - public void setNeedsRedaction(boolean needsRedaction) { - if (mNeedsRedaction != needsRedaction) { - mNeedsRedaction = needsRedaction; - if (!isRemoved()) { - RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); - if (needsRedaction) { - params.requireContentViews(FLAG_CONTENT_VIEW_PUBLIC); - } else { - params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC); - } - mRowContentBindStage.requestRebind(mEntry, null /* callback */); - } - } - } - public interface ExpansionLogger { void logNotificationExpansion(String key, boolean userAction, boolean expanded); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 952bafbe6eb3..79d883b32a98 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -42,7 +42,6 @@ import android.graphics.Color; import android.graphics.Outline; import android.graphics.Paint; import android.graphics.Path; -import android.graphics.PointF; import android.graphics.Rect; import android.os.Bundle; import android.provider.Settings; @@ -4405,8 +4404,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * See {@link AmbientState#setDozing}. */ @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - public void setDozing(boolean dozing, boolean animate, - @Nullable PointF touchWakeUpScreenLocation) { + public void setDozing(boolean dozing, boolean animate) { if (mAmbientState.isDozing() == dozing) { return; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 9998fe41b775..3f6586c37b5d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -33,7 +33,6 @@ import static com.android.systemui.statusbar.phone.NotificationIconAreaControlle import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Point; -import android.graphics.PointF; import android.os.Trace; import android.os.UserHandle; import android.provider.Settings; @@ -1235,8 +1234,8 @@ public class NotificationStackScrollLayoutController { mView.setAnimationsEnabled(enabled); } - public void setDozing(boolean dozing, boolean animate, PointF wakeUpTouchLocation) { - mView.setDozing(dozing, animate, wakeUpTouchLocation); + public void setDozing(boolean dozing, boolean animate) { + mView.setDozing(dozing, animate); } public void setPulsing(boolean pulsing, boolean animatePulse) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index 65ba5adddd60..e754d5db4186 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -512,7 +512,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp Trace.beginSection("MODE_WAKE_AND_UNLOCK"); } else { Trace.beginSection("MODE_WAKE_AND_UNLOCK_FROM_DREAM"); - mUpdateMonitor.awakenFromDream(); + // Don't call awaken from Dream here. In order to avoid flickering, wait until + // later to awaken. } mNotificationShadeWindowController.setNotificationShadeFocusable(false); mKeyguardViewMediator.onWakeAndUnlocking(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 7da1d6c085b0..2031f36022af 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -68,7 +68,6 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.graphics.Point; -import android.graphics.PointF; import android.hardware.devicestate.DeviceStateManager; import android.metrics.LogMaker; import android.net.Uri; @@ -462,7 +461,6 @@ public class CentralSurfacesImpl extends CoreStartable implements @VisibleForTesting DozeServiceHost mDozeServiceHost; private boolean mWakeUpComingFromTouch; - private PointF mWakeUpTouchLocation; private LightRevealScrim mLightRevealScrim; private PowerButtonReveal mPowerButtonReveal; @@ -619,8 +617,6 @@ public class CentralSurfacesImpl extends CoreStartable implements private int mLastCameraLaunchSource; protected PowerManager.WakeLock mGestureWakeLock; - private final int[] mTmpInt2 = new int[2]; - // Fingerprint (as computed by getLoggingFingerprint() of the last logged state. private int mLastLoggedStateFingerprint; private boolean mIsLaunchingActivityOverLockscreen; @@ -1446,16 +1442,6 @@ public class CentralSurfacesImpl extends CoreStartable implements mPowerManager.wakeUp( time, PowerManager.WAKE_REASON_GESTURE, "com.android.systemui:" + why); mWakeUpComingFromTouch = true; - - // NOTE, the incoming view can sometimes be the entire container... unsure if - // this location is valuable enough - if (where != null) { - where.getLocationInWindow(mTmpInt2); - mWakeUpTouchLocation = new PointF(mTmpInt2[0] + where.getWidth() / 2, - mTmpInt2[1] + where.getHeight() / 2); - } else { - mWakeUpTouchLocation = new PointF(-1, -1); - } mFalsingCollector.onScreenOnFromTouch(); } } @@ -1972,7 +1958,6 @@ public class CentralSurfacesImpl extends CoreStartable implements PowerManager.WAKE_REASON_APPLICATION, "com.android.systemui:full_screen_intent"); mWakeUpComingFromTouch = false; - mWakeUpTouchLocation = null; } } @@ -3242,7 +3227,7 @@ public class CentralSurfacesImpl extends CoreStartable implements || (mDozing && mDozeParameters.shouldControlScreenOff() && visibleNotOccludedOrWillBe); - mNotificationPanelViewController.setDozing(mDozing, animate, mWakeUpTouchLocation); + mNotificationPanelViewController.setDozing(mDozing, animate); updateQsExpansionEnabled(); Trace.endSection(); } @@ -3573,7 +3558,6 @@ public class CentralSurfacesImpl extends CoreStartable implements mLaunchCameraWhenFinishedWaking = false; mDeviceInteractive = false; mWakeUpComingFromTouch = false; - mWakeUpTouchLocation = null; updateVisibleToUser(); updateNotificationPanelTouchState(); @@ -4082,7 +4066,7 @@ public class CentralSurfacesImpl extends CoreStartable implements final PendingIntent intent, @Nullable final Runnable intentSentUiThreadCallback, @Nullable ActivityLaunchAnimator.Controller animationController) { final boolean willLaunchResolverActivity = intent.isActivity() - && mActivityIntentHelper.wouldLaunchResolverActivity(intent.getIntent(), + && mActivityIntentHelper.wouldPendingLaunchResolverActivity(intent, mLockscreenUserManager.getCurrentUserId()); boolean animate = !willLaunchResolverActivity diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java index fc8e7d5f6aa2..deb6150f773b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java @@ -225,6 +225,7 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da public void addDemoWifiView(WifiIconState state) { Log.d(TAG, "addDemoWifiView: "); + // TODO(b/238425913): Migrate this view to {@code ModernStatusBarWifiView}. StatusBarWifiView view = StatusBarWifiView.fromContext(mContext, state.slot); int viewIndex = getChildCount(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java index 30b640b583e6..ed186ab6a10b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java @@ -40,6 +40,7 @@ import com.android.systemui.demomode.DemoModeCommandReceiver; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; +import com.android.systemui.statusbar.BaseStatusBarWifiView; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.StatusBarMobileView; import com.android.systemui.statusbar.StatusBarWifiView; @@ -47,12 +48,16 @@ import com.android.systemui.statusbar.StatusIconDisplayable; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState; +import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags; +import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView; +import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel; import com.android.systemui.util.Assert; import java.util.ArrayList; import java.util.List; import javax.inject.Inject; +import javax.inject.Provider; public interface StatusBarIconController { @@ -128,8 +133,12 @@ public interface StatusBarIconController { private final DarkIconDispatcher mDarkIconDispatcher; private int mIconHPadding; - public DarkIconManager(LinearLayout linearLayout, FeatureFlags featureFlags) { - super(linearLayout, featureFlags); + public DarkIconManager( + LinearLayout linearLayout, + FeatureFlags featureFlags, + StatusBarPipelineFlags statusBarPipelineFlags, + Provider<WifiViewModel> wifiViewModelProvider) { + super(linearLayout, featureFlags, statusBarPipelineFlags, wifiViewModelProvider); mIconHPadding = mContext.getResources().getDimensionPixelSize( R.dimen.status_bar_icon_padding); mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class); @@ -183,14 +192,40 @@ public interface StatusBarIconController { mDarkIconDispatcher.removeDarkReceiver(mDemoStatusIcons); super.exitDemoMode(); } + + @SysUISingleton + public static class Factory { + private final FeatureFlags mFeatureFlags; + private final StatusBarPipelineFlags mStatusBarPipelineFlags; + private final Provider<WifiViewModel> mWifiViewModelProvider; + + @Inject + public Factory( + FeatureFlags featureFlags, + StatusBarPipelineFlags statusBarPipelineFlags, + Provider<WifiViewModel> wifiViewModelProvider) { + mFeatureFlags = featureFlags; + mStatusBarPipelineFlags = statusBarPipelineFlags; + mWifiViewModelProvider = wifiViewModelProvider; + } + + public DarkIconManager create(LinearLayout group) { + return new DarkIconManager( + group, mFeatureFlags, mStatusBarPipelineFlags, mWifiViewModelProvider); + } + } } /** */ class TintedIconManager extends IconManager { private int mColor; - public TintedIconManager(ViewGroup group, FeatureFlags featureFlags) { - super(group, featureFlags); + public TintedIconManager( + ViewGroup group, + FeatureFlags featureFlags, + StatusBarPipelineFlags statusBarPipelineFlags, + Provider<WifiViewModel> wifiViewModelProvider) { + super(group, featureFlags, statusBarPipelineFlags, wifiViewModelProvider); } @Override @@ -223,14 +258,22 @@ public interface StatusBarIconController { @SysUISingleton public static class Factory { private final FeatureFlags mFeatureFlags; + private final StatusBarPipelineFlags mStatusBarPipelineFlags; + private final Provider<WifiViewModel> mWifiViewModelProvider; @Inject - public Factory(FeatureFlags featureFlags) { + public Factory( + FeatureFlags featureFlags, + StatusBarPipelineFlags statusBarPipelineFlags, + Provider<WifiViewModel> wifiViewModelProvider) { mFeatureFlags = featureFlags; + mStatusBarPipelineFlags = statusBarPipelineFlags; + mWifiViewModelProvider = wifiViewModelProvider; } public TintedIconManager create(ViewGroup group) { - return new TintedIconManager(group, mFeatureFlags); + return new TintedIconManager( + group, mFeatureFlags, mStatusBarPipelineFlags, mWifiViewModelProvider); } } } @@ -239,8 +282,10 @@ public interface StatusBarIconController { * Turns info from StatusBarIconController into ImageViews in a ViewGroup. */ class IconManager implements DemoModeCommandReceiver { - private final FeatureFlags mFeatureFlags; protected final ViewGroup mGroup; + private final FeatureFlags mFeatureFlags; + private final StatusBarPipelineFlags mStatusBarPipelineFlags; + private final Provider<WifiViewModel> mWifiViewModelProvider; protected final Context mContext; protected final int mIconSize; // Whether or not these icons show up in dumpsys @@ -254,9 +299,15 @@ public interface StatusBarIconController { protected ArrayList<String> mBlockList = new ArrayList<>(); - public IconManager(ViewGroup group, FeatureFlags featureFlags) { - mFeatureFlags = featureFlags; + public IconManager( + ViewGroup group, + FeatureFlags featureFlags, + StatusBarPipelineFlags statusBarPipelineFlags, + Provider<WifiViewModel> wifiViewModelProvider) { mGroup = group; + mFeatureFlags = featureFlags; + mStatusBarPipelineFlags = statusBarPipelineFlags; + mWifiViewModelProvider = wifiViewModelProvider; mContext = group.getContext(); mIconSize = mContext.getResources().getDimensionPixelSize( com.android.internal.R.dimen.status_bar_icon_size); @@ -308,7 +359,7 @@ public interface StatusBarIconController { return addIcon(index, slot, blocked, holder.getIcon()); case TYPE_WIFI: - return addSignalIcon(index, slot, holder.getWifiState()); + return addWifiIcon(index, slot, holder.getWifiState()); case TYPE_MOBILE: return addMobileIcon(index, slot, holder.getMobileState()); @@ -327,9 +378,17 @@ public interface StatusBarIconController { } @VisibleForTesting - protected StatusBarWifiView addSignalIcon(int index, String slot, WifiIconState state) { - StatusBarWifiView view = onCreateStatusBarWifiView(slot); - view.applyWifiState(state); + protected StatusIconDisplayable addWifiIcon(int index, String slot, WifiIconState state) { + final BaseStatusBarWifiView view; + if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) { + view = onCreateModernStatusBarWifiView(slot); + // When [ModernStatusBarWifiView] is created, it will automatically apply the + // correct view state so we don't need to call applyWifiState. + } else { + StatusBarWifiView wifiView = onCreateStatusBarWifiView(slot); + wifiView.applyWifiState(state); + view = wifiView; + } mGroup.addView(view, index, onCreateLayoutParams()); if (mIsInDemoMode) { @@ -359,6 +418,11 @@ public interface StatusBarIconController { return view; } + private ModernStatusBarWifiView onCreateModernStatusBarWifiView(String slot) { + return ModernStatusBarWifiView.constructAndBind( + mContext, slot, mWifiViewModelProvider.get()); + } + private StatusBarMobileView onCreateStatusBarMobileView(String slot) { StatusBarMobileView view = StatusBarMobileView.fromContext(mContext, slot); return view; @@ -415,9 +479,8 @@ public interface StatusBarIconController { onSetIcon(viewIndex, holder.getIcon()); return; case TYPE_WIFI: - onSetSignalIcon(viewIndex, holder.getWifiState()); + onSetWifiIcon(viewIndex, holder.getWifiState()); return; - case TYPE_MOBILE: onSetMobileIcon(viewIndex, holder.getMobileState()); default: @@ -425,10 +488,16 @@ public interface StatusBarIconController { } } - public void onSetSignalIcon(int viewIndex, WifiIconState state) { - StatusBarWifiView wifiView = (StatusBarWifiView) mGroup.getChildAt(viewIndex); - if (wifiView != null) { - wifiView.applyWifiState(state); + public void onSetWifiIcon(int viewIndex, WifiIconState state) { + View view = mGroup.getChildAt(viewIndex); + if (view instanceof StatusBarWifiView) { + ((StatusBarWifiView) view).applyWifiState(state); + } else if (view instanceof ModernStatusBarWifiView) { + // ModernStatusBarWifiView will automatically apply state based on its callbacks, so + // we don't need to call applyWifiState. + } else { + throw new IllegalStateException("View at " + viewIndex + " must be of type " + + "StatusBarWifiView or ModernStatusBarWifiView"); } if (mIsInDemoMode) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index 374f0916fb33..5cd2ba1b1cf3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -223,12 +223,12 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte boolean isActivityIntent = intent != null && intent.isActivity() && !isBubble; final boolean willLaunchResolverActivity = isActivityIntent - && mActivityIntentHelper.wouldLaunchResolverActivity(intent.getIntent(), + && mActivityIntentHelper.wouldPendingLaunchResolverActivity(intent, mLockscreenUserManager.getCurrentUserId()); final boolean animate = !willLaunchResolverActivity && mCentralSurfaces.shouldAnimateLaunch(isActivityIntent); boolean showOverLockscreen = mKeyguardStateController.isShowing() && intent != null - && mActivityIntentHelper.wouldShowOverLockscreen(intent.getIntent(), + && mActivityIntentHelper.wouldPendingShowOverLockscreen(intent, mLockscreenUserManager.getCurrentUserId()); ActivityStarter.OnDismissAction postKeyguardAction = new ActivityStarter.OnDismissAction() { @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java index 40b9a152057a..70af77e1eb36 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java @@ -259,8 +259,9 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks, final boolean isActivity = pendingIntent.isActivity(); if (isActivity || appRequestedAuth) { mActionClickLogger.logWaitingToCloseKeyguard(pendingIntent); - final boolean afterKeyguardGone = mActivityIntentHelper.wouldLaunchResolverActivity( - pendingIntent.getIntent(), mLockscreenUserManager.getCurrentUserId()); + final boolean afterKeyguardGone = mActivityIntentHelper + .wouldPendingLaunchResolverActivity(pendingIntent, + mLockscreenUserManager.getCurrentUserId()); mActivityStarter.dismissKeyguardThenExecute(() -> { mActionClickLogger.logKeyguardGone(pendingIntent); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt index d058b75fd1b1..53e08ea8e10d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -10,6 +10,7 @@ import android.os.PowerManager import android.provider.Settings import android.view.Surface import android.view.View +import android.view.WindowManager.fixScale import com.android.internal.jank.InteractionJankMonitor import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD @@ -138,8 +139,8 @@ class UnlockedScreenOffAnimationController @Inject constructor( } fun updateAnimatorDurationScale() { - animatorDurationScale = - globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f) + animatorDurationScale = fixScale( + globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f)) } override fun shouldDelayKeyguardShow(): Boolean = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java index c4e7a8a2c17b..627cfb718d91 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java @@ -287,6 +287,7 @@ public abstract class StatusBarViewModule { PanelExpansionStateManager panelExpansionStateManager, FeatureFlags featureFlags, StatusBarIconController statusBarIconController, + StatusBarIconController.DarkIconManager.Factory darkIconManagerFactory, StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager, KeyguardStateController keyguardStateController, NotificationPanelViewController notificationPanelViewController, @@ -308,6 +309,7 @@ public abstract class StatusBarViewModule { panelExpansionStateManager, featureFlags, statusBarIconController, + darkIconManagerFactory, statusBarHideIconsForBouncerManager, keyguardStateController, notificationPanelViewController, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index 29b4d5bfbc82..84bb82beee1c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -130,6 +130,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private final StatusBarIconController mStatusBarIconController; private final CarrierConfigTracker mCarrierConfigTracker; private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; + private final StatusBarIconController.DarkIconManager.Factory mDarkIconManagerFactory; private final SecureSettings mSecureSettings; private final Executor mMainExecutor; private final DumpManager mDumpManager; @@ -185,6 +186,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue PanelExpansionStateManager panelExpansionStateManager, FeatureFlags featureFlags, StatusBarIconController statusBarIconController, + StatusBarIconController.DarkIconManager.Factory darkIconManagerFactory, StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager, KeyguardStateController keyguardStateController, NotificationPanelViewController notificationPanelViewController, @@ -207,6 +209,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mFeatureFlags = featureFlags; mStatusBarIconController = statusBarIconController; mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager; + mDarkIconManagerFactory = darkIconManagerFactory; mKeyguardStateController = keyguardStateController; mNotificationPanelViewController = notificationPanelViewController; mNetworkController = networkController; @@ -247,7 +250,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mStatusBar.restoreHierarchyState( savedInstanceState.getSparseParcelableArray(EXTRA_PANEL_STATE)); } - mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons), mFeatureFlags); + mDarkIconManager = mDarkIconManagerFactory.create(view.findViewById(R.id.statusIcons)); mDarkIconManager.setShouldLog(true); updateBlockedIcons(); mStatusBarIconController.addIconGroup(mDarkIconManager); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt deleted file mode 100644 index 6c02b0d44db3..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.pipeline - -import com.android.systemui.statusbar.pipeline.repository.NetworkCapabilityInfo -import kotlinx.coroutines.flow.StateFlow - -/** - * Interface exposing a flow for raw connectivity information. Clients should collect on - * [rawConnectivityInfoFlow] to get updates on connectivity information. - * - * Note: [rawConnectivityInfoFlow] should be a *hot* flow, so that we only create one instance of it - * and all clients get references to the same flow. - * - * This will be used for the new status bar pipeline to compile information we need to display some - * of the icons in the RHS of the status bar. - */ -interface ConnectivityInfoCollector { - val rawConnectivityInfoFlow: StateFlow<RawConnectivityInfo> -} - -/** - * An object containing all of the raw connectivity information. - * - * Importantly, all the information in this object should not be processed at all (i.e., the data - * that we receive from callbacks should be piped straight into this object and not be filtered, - * manipulated, or processed in any way). Instead, any listeners on - * [ConnectivityInfoCollector.rawConnectivityInfoFlow] can do the processing. - * - * This allows us to keep all the processing in one place which is beneficial for logging and - * debugging purposes. - */ -data class RawConnectivityInfo( - val networkCapabilityInfo: Map<Int, NetworkCapabilityInfo> = emptyMap(), -) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt deleted file mode 100644 index 8d69422c7427..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.pipeline - -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.statusbar.pipeline.repository.NetworkCapabilitiesRepo -import kotlinx.coroutines.CoroutineScope -import javax.inject.Inject -import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn - -/** - * The real implementation of [ConnectivityInfoCollector] that will collect information from all the - * relevant connectivity callbacks and compile it into [rawConnectivityInfoFlow]. - */ -@SysUISingleton -class ConnectivityInfoCollectorImpl @Inject constructor( - networkCapabilitiesRepo: NetworkCapabilitiesRepo, - @Application scope: CoroutineScope, -) : ConnectivityInfoCollector { - override val rawConnectivityInfoFlow: StateFlow<RawConnectivityInfo> = - // TODO(b/238425913): Collect all the separate flows for individual raw information into - // this final flow. - networkCapabilitiesRepo.dataStream - .map { - RawConnectivityInfo(networkCapabilityInfo = it) - } - .stateIn(scope, started = Lazily, initialValue = RawConnectivityInfo()) -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt index 1aae25058ba8..fe846747d0c6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt @@ -20,58 +20,36 @@ import android.content.Context import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.statusbar.pipeline.repository.NetworkCapabilityInfo +import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel import javax.inject.Inject +import javax.inject.Provider import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily -import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch /** - * A processor that transforms raw connectivity information that we get from callbacks and turns it - * into a list of displayable connectivity information. + * A temporary object that collects on [WifiViewModel] flows for debugging purposes. * - * This will be used for the new status bar pipeline to calculate the list of icons that should be - * displayed in the RHS of the status bar. - * - * Anyone can listen to [processedInfoFlow] to get updates to the processed data. + * This will eventually get migrated to a view binder that will use the flow outputs to set state on + * views. For now, this just collects on flows so that the information gets logged. */ @SysUISingleton class ConnectivityInfoProcessor @Inject constructor( - connectivityInfoCollector: ConnectivityInfoCollector, context: Context, + // TODO(b/238425913): Don't use the application scope; instead, use the status bar view's + // scope so we only do work when there's UI that cares about it. @Application private val scope: CoroutineScope, - statusBarPipelineFlags: StatusBarPipelineFlags, + private val statusBarPipelineFlags: StatusBarPipelineFlags, + private val wifiViewModelProvider: Provider<WifiViewModel>, ) : CoreStartable(context) { - // Note: This flow will not start running until a client calls `collect` on it, which means that - // [connectivityInfoCollector]'s flow will also not start anything until that `collect` call - // happens. - val processedInfoFlow: Flow<ProcessedConnectivityInfo> = - if (!statusBarPipelineFlags.isNewPipelineEnabled()) - emptyFlow() - else connectivityInfoCollector.rawConnectivityInfoFlow - .map { it.process() } - .stateIn( - scope, - started = Lazily, - initialValue = ProcessedConnectivityInfo() - ) - override fun start() { - } - - private fun RawConnectivityInfo.process(): ProcessedConnectivityInfo { - // TODO(b/238425913): Actually process the raw info into meaningful data. - return ProcessedConnectivityInfo(this.networkCapabilityInfo) + if (!statusBarPipelineFlags.isNewPipelineBackendEnabled()) { + return + } + // TODO(b/238425913): The view binder should do this instead. For now, do it here so we can + // see the logs. + scope.launch { + wifiViewModelProvider.get().isActivityInVisible.collect { } + } } } - -/** - * An object containing connectivity info that has been processed into data that can be directly - * used by the status bar (and potentially other SysUI areas) to display icons. - */ -data class ProcessedConnectivityInfo( - val networkCapabilityInfo: Map<Int, NetworkCapabilityInfo> = emptyMap(), -) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLogger.kt deleted file mode 100644 index f88e9d67d25d..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLogger.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.pipeline - -import android.net.Network -import android.net.NetworkCapabilities -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.dagger.StatusBarConnectivityLog -import javax.inject.Inject - -@SysUISingleton -class ConnectivityPipelineLogger @Inject constructor( - @StatusBarConnectivityLog private val buffer: LogBuffer, -) { - fun logOnCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) { - buffer.log( - TAG, - LogLevel.INFO, - { - int1 = network.getNetId() - str1 = networkCapabilities.toString() - }, - { - "onCapabilitiesChanged: net=$int1 capabilities=$str1" - } - ) - } - - fun logOnLost(network: Network) { - buffer.log( - TAG, - LogLevel.INFO, - { - int1 = network.getNetId() - }, - { - "onLost: net=$int1" - } - ) - } -} - -private const val TAG = "SbConnectivityPipeline" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt index 589cdb8182cb..9b8b6434827e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt @@ -25,13 +25,28 @@ import javax.inject.Inject @SysUISingleton class StatusBarPipelineFlags @Inject constructor(private val featureFlags: FeatureFlags) { /** - * Returns true if we should run the new pipeline. + * Returns true if we should run the new pipeline backend. * - * TODO(b/238425913): We may want to split this out into: - * (1) isNewPipelineLoggingEnabled(), where the new pipeline runs and logs its decisions but - * doesn't change the UI at all. - * (2) isNewPipelineEnabled(), where the new pipeline runs and does change the UI (and the old - * pipeline doesn't change the UI). + * The new pipeline backend hooks up to all our external callbacks, logs those callback inputs, + * and logs the output state. */ - fun isNewPipelineEnabled(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_PIPELINE) + fun isNewPipelineBackendEnabled(): Boolean = + featureFlags.isEnabled(Flags.NEW_STATUS_BAR_PIPELINE_BACKEND) + + /** + * Returns true if we should run the new pipeline frontend *and* backend. + * + * The new pipeline frontend will use the outputted state from the new backend and will make the + * correct changes to the UI. + */ + fun isNewPipelineFrontendEnabled(): Boolean = + isNewPipelineBackendEnabled() && + featureFlags.isEnabled(Flags.NEW_STATUS_BAR_PIPELINE_FRONTEND) + + /** + * Returns true if we should apply some coloring to icons that were rendered with the new + * pipeline to help with debugging. + */ + // For now, just always apply the debug coloring if we've enabled frontend rendering. + fun useNewPipelineDebugColoring(): Boolean = isNewPipelineFrontendEnabled() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt index c4e2b732f388..88eccb5ba47f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt @@ -17,9 +17,9 @@ package com.android.systemui.statusbar.pipeline.dagger import com.android.systemui.CoreStartable -import com.android.systemui.statusbar.pipeline.ConnectivityInfoCollector -import com.android.systemui.statusbar.pipeline.ConnectivityInfoCollectorImpl import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl import dagger.Binds import dagger.Module import dagger.multibindings.ClassKey @@ -34,7 +34,5 @@ abstract class StatusBarPipelineModule { abstract fun bindConnectivityInfoProcessor(cip: ConnectivityInfoProcessor): CoreStartable @Binds - abstract fun provideConnectivityInfoCollector( - impl: ConnectivityInfoCollectorImpl - ): ConnectivityInfoCollector + abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepo.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepo.kt deleted file mode 100644 index e5980c3693ee..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepo.kt +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -@file:OptIn(ExperimentalCoroutinesApi::class) - -package com.android.systemui.statusbar.pipeline.repository - -import android.annotation.SuppressLint -import android.net.ConnectivityManager -import android.net.Network -import android.net.NetworkCapabilities -import android.net.NetworkRequest -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.statusbar.pipeline.ConnectivityPipelineLogger -import javax.inject.Inject -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.flow.stateIn - -/** Repository that contains all relevant [NetworkCapabilites] for the current networks */ -@SysUISingleton -class NetworkCapabilitiesRepo @Inject constructor( - connectivityManager: ConnectivityManager, - @Application scope: CoroutineScope, - logger: ConnectivityPipelineLogger, -) { - @SuppressLint("MissingPermission") - val dataStream: StateFlow<Map<Int, NetworkCapabilityInfo>> = run { - var state = emptyMap<Int, NetworkCapabilityInfo>() - callbackFlow { - val networkRequest: NetworkRequest = - NetworkRequest.Builder() - .clearCapabilities() - .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) - .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) - .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) - .build() - val callback = - // TODO (b/240569788): log these using [LogBuffer] - object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) { - override fun onCapabilitiesChanged( - network: Network, - networkCapabilities: NetworkCapabilities - ) { - logger.logOnCapabilitiesChanged(network, networkCapabilities) - state = - state.toMutableMap().also { - it[network.getNetId()] = - NetworkCapabilityInfo(network, networkCapabilities) - } - trySend(state) - } - - override fun onLost(network: Network) { - logger.logOnLost(network) - state = state.toMutableMap().also { it.remove(network.getNetId()) } - trySend(state) - } - } - connectivityManager.registerNetworkCallback(networkRequest, callback) - - awaitClose { connectivityManager.unregisterNetworkCallback(callback) } - } - .stateIn(scope, started = Lazily, initialValue = state) - } -} - -/** contains info about network capabilities. */ -data class NetworkCapabilityInfo( - val network: Network, - val capabilities: NetworkCapabilities, -) - -private const val TAG = "ConnectivityRepository" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt new file mode 100644 index 000000000000..2a89309f7200 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.shared + +import android.net.Network +import android.net.NetworkCapabilities +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel +import com.android.systemui.log.dagger.StatusBarConnectivityLog +import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.toString +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.onEach + +@SysUISingleton +class ConnectivityPipelineLogger @Inject constructor( + @StatusBarConnectivityLog private val buffer: LogBuffer, +) { + /** + * Logs a change in one of the **raw inputs** to the connectivity pipeline. + */ + fun logInputChange(callbackName: String, changeInfo: String) { + buffer.log( + SB_LOGGING_TAG, + LogLevel.INFO, + { + str1 = callbackName + str2 = changeInfo + }, + { + "Input: $str1: $str2" + } + ) + } + + /** + * Logs a **data transformation** that we performed within the connectivity pipeline. + */ + fun logTransformation(transformationName: String, oldValue: Any?, newValue: Any?) { + if (oldValue == newValue) { + buffer.log( + SB_LOGGING_TAG, + LogLevel.INFO, + { + str1 = transformationName + str2 = oldValue.toString() + }, + { + "Transform: $str1: $str2 (transformation didn't change it)" + } + ) + } else { + buffer.log( + SB_LOGGING_TAG, + LogLevel.INFO, + { + str1 = transformationName + str2 = oldValue.toString() + str3 = newValue.toString() + }, + { + "Transform: $str1: $str2 -> $str3" + } + ) + } + } + + /** + * Logs a change in one of the **outputs** to the connectivity pipeline. + */ + fun logOutputChange(outputParamName: String, changeInfo: String) { + buffer.log( + SB_LOGGING_TAG, + LogLevel.INFO, + { + str1 = outputParamName + str2 = changeInfo + }, + { + "Output: $str1: $str2" + } + ) + } + + fun logOnCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) { + buffer.log( + SB_LOGGING_TAG, + LogLevel.INFO, + { + int1 = network.getNetId() + str1 = networkCapabilities.toString() + }, + { + "onCapabilitiesChanged: net=$int1 capabilities=$str1" + } + ) + } + + fun logOnLost(network: Network) { + buffer.log( + SB_LOGGING_TAG, + LogLevel.INFO, + { + int1 = network.getNetId() + }, + { + "onLost: net=$int1" + } + ) + } + + companion object { + const val SB_LOGGING_TAG = "SbConnectivity" + + /** + * Log a change in one of the **outputs** to the connectivity pipeline. + * + * @param prettyPrint an optional function to transform the value into a readable string. + * [toString] is used if no custom function is provided. + */ + fun <T : Any> Flow<T>.logOutputChange( + logger: ConnectivityPipelineLogger, + outputParamName: String, + prettyPrint: (T) -> String = { it.toString() } + ): Flow<T> { + return this.onEach { logger.logOutputChange(outputParamName, prettyPrint(it)) } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt new file mode 100644 index 000000000000..44c04968041e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.wifi.data.model + +/** + * Provides information on the current wifi activity. + */ +data class WifiActivityModel( + /** True if the wifi has activity in (download). */ + val hasActivityIn: Boolean, + /** True if the wifi has activity out (upload). */ + val hasActivityOut: Boolean, +) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt new file mode 100644 index 000000000000..5566fa6b5835 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.wifi.data.model + +/** Provides information about the current wifi network. */ +sealed class WifiNetworkModel { + /** A model representing that we have no active wifi network. */ + object Inactive : WifiNetworkModel() + + /** Provides information about an active wifi network. */ + class Active( + /** + * The [android.net.Network.netId] we received from + * [android.net.ConnectivityManager.NetworkCallback] in association with this wifi network. + * + * Importantly, **not** [android.net.wifi.WifiInfo.getNetworkId]. + */ + val networkId: Int, + + /** See [android.net.wifi.WifiInfo.ssid]. */ + val ssid: String? = null, + + /** See [android.net.wifi.WifiInfo.isPasspointAp]. */ + val isPasspointAccessPoint: Boolean = false, + + /** See [android.net.wifi.WifiInfo.isOsuAp]. */ + val isOnlineSignUpForPasspointAccessPoint: Boolean = false, + + /** See [android.net.wifi.WifiInfo.passpointProviderFriendlyName]. */ + val passpointProviderFriendlyName: String? = null, + ) : WifiNetworkModel() +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt new file mode 100644 index 000000000000..43fbabca823f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.wifi.data.repository + +import android.annotation.SuppressLint +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN +import android.net.NetworkCapabilities.TRANSPORT_CELLULAR +import android.net.NetworkCapabilities.TRANSPORT_WIFI +import android.net.NetworkRequest +import android.net.wifi.WifiInfo +import android.net.wifi.WifiManager +import android.net.wifi.WifiManager.TrafficStateCallback +import android.util.Log +import com.android.settingslib.Utils +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger +import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel +import java.util.concurrent.Executor +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf + +/** + * Provides data related to the wifi state. + */ +interface WifiRepository { + /** + * Observable for the current wifi network. + */ + val wifiNetwork: Flow<WifiNetworkModel> + + /** + * Observable for the current wifi network activity. + */ + val wifiActivity: Flow<WifiActivityModel> +} + +/** Real implementation of [WifiRepository]. */ +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +@SuppressLint("MissingPermission") +class WifiRepositoryImpl @Inject constructor( + connectivityManager: ConnectivityManager, + wifiManager: WifiManager?, + @Main mainExecutor: Executor, + logger: ConnectivityPipelineLogger, +) : WifiRepository { + override val wifiNetwork: Flow<WifiNetworkModel> = conflatedCallbackFlow { + var currentWifi: WifiNetworkModel = WIFI_NETWORK_DEFAULT + + val callback = object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) { + override fun onCapabilitiesChanged( + network: Network, + networkCapabilities: NetworkCapabilities + ) { + logger.logOnCapabilitiesChanged(network, networkCapabilities) + + val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities) + if (wifiInfo?.isPrimary == true) { + val wifiNetworkModel = wifiInfoToModel(wifiInfo, network.getNetId()) + logger.logTransformation( + WIFI_NETWORK_CALLBACK_NAME, + oldValue = currentWifi, + newValue = wifiNetworkModel + ) + currentWifi = wifiNetworkModel + trySend(wifiNetworkModel) + } + } + + override fun onLost(network: Network) { + logger.logOnLost(network) + val wifi = currentWifi + if (wifi is WifiNetworkModel.Active && wifi.networkId == network.getNetId()) { + val newNetworkModel = WifiNetworkModel.Inactive + logger.logTransformation( + WIFI_NETWORK_CALLBACK_NAME, + oldValue = wifi, + newValue = newNetworkModel + ) + currentWifi = newNetworkModel + trySend(newNetworkModel) + } + } + } + + trySend(WIFI_NETWORK_DEFAULT) + connectivityManager.registerNetworkCallback(WIFI_NETWORK_CALLBACK_REQUEST, callback) + + awaitClose { connectivityManager.unregisterNetworkCallback(callback) } + } + + override val wifiActivity: Flow<WifiActivityModel> = + if (wifiManager == null) { + Log.w(SB_LOGGING_TAG, "Null WifiManager; skipping activity callback") + flowOf(ACTIVITY_DEFAULT) + } else { + conflatedCallbackFlow { + val callback = TrafficStateCallback { state -> + logger.logInputChange("onTrafficStateChange", prettyPrintActivity(state)) + trySend(trafficStateToWifiActivityModel(state)) + } + + trySend(ACTIVITY_DEFAULT) + wifiManager.registerTrafficStateCallback(mainExecutor, callback) + + awaitClose { wifiManager.unregisterTrafficStateCallback(callback) } + } + } + + companion object { + val ACTIVITY_DEFAULT = WifiActivityModel(hasActivityIn = false, hasActivityOut = false) + // Start out with no known wifi network. + // Note: [WifiStatusTracker] (the old implementation of connectivity logic) does do an + // initial fetch to get a starting wifi network. But, it uses a deprecated API + // [WifiManager.getConnectionInfo()], and the deprecation doc indicates to just use + // [ConnectivityManager.NetworkCallback] results instead. So, for now we'll just rely on the + // NetworkCallback inside [wifiNetwork] for our wifi network information. + val WIFI_NETWORK_DEFAULT = WifiNetworkModel.Inactive + + private fun trafficStateToWifiActivityModel(state: Int): WifiActivityModel { + return WifiActivityModel( + hasActivityIn = state == TrafficStateCallback.DATA_ACTIVITY_IN || + state == TrafficStateCallback.DATA_ACTIVITY_INOUT, + hasActivityOut = state == TrafficStateCallback.DATA_ACTIVITY_OUT || + state == TrafficStateCallback.DATA_ACTIVITY_INOUT, + ) + } + + private fun networkCapabilitiesToWifiInfo( + networkCapabilities: NetworkCapabilities + ): WifiInfo? { + return when { + networkCapabilities.hasTransport(TRANSPORT_WIFI) -> + networkCapabilities.transportInfo as WifiInfo? + networkCapabilities.hasTransport(TRANSPORT_CELLULAR) -> + // Sometimes, cellular networks can act as wifi networks (known as VCN -- + // virtual carrier network). So, see if this cellular network has wifi info. + Utils.tryGetWifiInfoForVcn(networkCapabilities) + else -> null + } + } + + private fun wifiInfoToModel(wifiInfo: WifiInfo, networkId: Int): WifiNetworkModel { + return WifiNetworkModel.Active( + networkId, + wifiInfo.ssid, + wifiInfo.isPasspointAp, + wifiInfo.isOsuAp, + wifiInfo.passpointProviderFriendlyName + ) + } + + private fun prettyPrintActivity(activity: Int): String { + return when (activity) { + TrafficStateCallback.DATA_ACTIVITY_NONE -> "NONE" + TrafficStateCallback.DATA_ACTIVITY_IN -> "IN" + TrafficStateCallback.DATA_ACTIVITY_OUT -> "OUT" + TrafficStateCallback.DATA_ACTIVITY_INOUT -> "INOUT" + else -> "INVALID" + } + } + + private val WIFI_NETWORK_CALLBACK_REQUEST: NetworkRequest = + NetworkRequest.Builder() + .clearCapabilities() + .addCapability(NET_CAPABILITY_NOT_VPN) + .addTransportType(TRANSPORT_WIFI) + .addTransportType(TRANSPORT_CELLULAR) + .build() + + private const val WIFI_NETWORK_CALLBACK_NAME = "wifiNetworkModel" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt new file mode 100644 index 000000000000..a9da63b43aa4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.wifi.domain.interactor + +import android.net.wifi.WifiManager +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map + +/** + * The business logic layer for the wifi icon. + * + * This interactor processes information from our data layer into information that the UI layer can + * use. + */ +@SysUISingleton +class WifiInteractor @Inject constructor( + repository: WifiRepository, +) { + private val ssid: Flow<String?> = repository.wifiNetwork.map { info -> + when (info) { + is WifiNetworkModel.Inactive -> null + is WifiNetworkModel.Active -> when { + info.isPasspointAccessPoint || info.isOnlineSignUpForPasspointAccessPoint -> + info.passpointProviderFriendlyName + info.ssid != WifiManager.UNKNOWN_SSID -> info.ssid + else -> null + } + } + } + + val hasActivityIn: Flow<Boolean> = combine(repository.wifiActivity, ssid) { activity, ssid -> + activity.hasActivityIn && ssid != null + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt new file mode 100644 index 000000000000..a19d1bdd8e62 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.wifi.shared + +import android.content.Context +import com.android.systemui.Dumpable +import com.android.systemui.R +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dump.DumpManager +import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG +import java.io.PrintWriter +import javax.inject.Inject + +/** + * An object storing constants that we use for calculating the wifi icon. Stored in a class for + * logging purposes. + */ +@SysUISingleton +class WifiConstants @Inject constructor( + context: Context, + dumpManager: DumpManager, +) : Dumpable { + init { + dumpManager.registerDumpable("$SB_LOGGING_TAG:WifiConstants", this) + } + + /** True if we should show the activityIn/activityOut icons and false otherwise. */ + val shouldShowActivityConfig = context.resources.getBoolean(R.bool.config_showActivity) + + override fun dump(pw: PrintWriter, args: Array<out String>) { + pw.apply { + println("shouldShowActivityConfig=$shouldShowActivityConfig") + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt new file mode 100644 index 000000000000..b06aaf441f5a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.wifi.ui.binder + +import android.content.res.ColorStateList +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import androidx.core.view.isVisible +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.R +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS +import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel +import kotlinx.coroutines.InternalCoroutinesApi +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch + +/** + * Binds a wifi icon in the status bar to its view-model. + * + * To use this properly, users should maintain a one-to-one relationship between the [View] and the + * view-binding, binding each view only once. It is okay and expected for the same instance of the + * view-model to be reused for multiple view/view-binder bindings. + */ +@OptIn(InternalCoroutinesApi::class) +object WifiViewBinder { + /** Binds the view to the view-model, continuing to update the former based on the latter. */ + @JvmStatic + fun bind( + view: ViewGroup, + viewModel: WifiViewModel, + ) { + val iconView = view.requireViewById<ImageView>(R.id.wifi_signal) + + view.isVisible = true + iconView.isVisible = true + iconView.setImageDrawable(view.context.getDrawable(WIFI_FULL_ICONS[2])) + + view.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + viewModel.tint.collect { tint -> + iconView.imageTintList = ColorStateList.valueOf(tint) + } + } + } + } + + // TODO(b/238425913): Hook up to [viewModel] to render actual changes to the wifi icon. + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt new file mode 100644 index 000000000000..c14a897fffab --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.wifi.ui.view + +import android.content.Context +import android.graphics.Rect +import android.util.AttributeSet +import android.view.LayoutInflater +import com.android.systemui.R +import com.android.systemui.statusbar.BaseStatusBarWifiView +import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON +import com.android.systemui.statusbar.pipeline.wifi.ui.binder.WifiViewBinder +import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel + +/** + * A new and more modern implementation of [com.android.systemui.statusbar.StatusBarWifiView] that + * is updated by [WifiViewBinder]. + */ +class ModernStatusBarWifiView( + context: Context, + attrs: AttributeSet? +) : BaseStatusBarWifiView(context, attrs) { + + private lateinit var slot: String + + override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) { + // TODO(b/238425913) + } + + override fun getSlot() = slot + + override fun setStaticDrawableColor(color: Int) { + // TODO(b/238425913) + } + + override fun setDecorColor(color: Int) { + // TODO(b/238425913) + } + + override fun setVisibleState(state: Int, animate: Boolean) { + // TODO(b/238425913) + } + + override fun getVisibleState(): Int { + // TODO(b/238425913) + return STATE_ICON + } + + override fun isIconVisible(): Boolean { + // TODO(b/238425913) + return true + } + + /** Set the slot name for this view. */ + private fun setSlot(slotName: String) { + this.slot = slotName + } + + companion object { + /** + * Inflates a new instance of [ModernStatusBarWifiView], binds it to [viewModel], and + * returns it. + */ + @JvmStatic + fun constructAndBind( + context: Context, + slot: String, + viewModel: WifiViewModel, + ): ModernStatusBarWifiView { + return ( + LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null) + as ModernStatusBarWifiView + ).also { + it.setSlot(slot) + WifiViewBinder.bind(it, viewModel) + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt new file mode 100644 index 000000000000..7a262605681d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel + +import android.graphics.Color +import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags +import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger +import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange +import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor +import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flowOf + +/** + * Models the UI state for the status bar wifi icon. + * + * TODO(b/238425913): Hook this up to the real status bar wifi view using a view binder. + */ +class WifiViewModel @Inject constructor( + statusBarPipelineFlags: StatusBarPipelineFlags, + private val constants: WifiConstants, + private val logger: ConnectivityPipelineLogger, + private val interactor: WifiInteractor, +) { + val isActivityInVisible: Flow<Boolean> + get() = + if (!constants.shouldShowActivityConfig) { + flowOf(false) + } else { + interactor.hasActivityIn + } + .logOutputChange(logger, "activityInVisible") + + /** The tint that should be applied to the icon. */ + val tint: Flow<Int> = if (!statusBarPipelineFlags.useNewPipelineDebugColoring()) { + emptyFlow() + } else { + flowOf(Color.CYAN) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 094490ba7adc..adef1823d491 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -244,7 +244,8 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { final int currentUser = mUserTracker.getUserId(); final boolean hadWallpaperColors = mCurrentColors.get(userId) != null; int latestWallpaperType = getLatestWallpaperType(userId); - if ((flags & latestWallpaperType) != 0) { + boolean eventForLatestWallpaper = (flags & latestWallpaperType) != 0; + if (eventForLatestWallpaper) { mCurrentColors.put(userId, wallpaperColors); if (DEBUG) Log.d(TAG, "got new colors: " + wallpaperColors + " where: " + flags); } @@ -280,14 +281,19 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { currentUser); boolean isDestinationBoth = (flags == (WallpaperManager.FLAG_SYSTEM | WallpaperManager.FLAG_LOCK)); + boolean isDestinationHomeOnly = (flags == WallpaperManager.FLAG_SYSTEM); try { JSONObject jsonObject = (overlayPackageJson == null) ? new JSONObject() : new JSONObject(overlayPackageJson); // The latest applied wallpaper should be the source of system colors when: // There is not preset color applied and the incoming wallpaper color is not applied - if (!COLOR_SOURCE_PRESET.equals(jsonObject.optString(OVERLAY_COLOR_SOURCE)) - && ((flags & latestWallpaperType) != 0 && !isSeedColorSet(jsonObject, - wallpaperColors))) { + String wallpaperPickerColorSource = jsonObject.optString(OVERLAY_COLOR_SOURCE); + boolean userChosePresetColor = COLOR_SOURCE_PRESET.equals(wallpaperPickerColorSource); + boolean userChoseLockScreenColor = COLOR_SOURCE_LOCK.equals(wallpaperPickerColorSource); + boolean preserveLockScreenColor = isDestinationHomeOnly && userChoseLockScreenColor; + + if (!userChosePresetColor && !preserveLockScreenColor && eventForLatestWallpaper + && !isSeedColorSet(jsonObject, wallpaperColors)) { mSkipSettingChange = true; if (jsonObject.has(OVERLAY_CATEGORY_ACCENT_COLOR) || jsonObject.has( OVERLAY_CATEGORY_SYSTEM_PALETTE)) { @@ -642,7 +648,7 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { } if (mNeedsOverlayCreation) { mNeedsOverlayCreation = false; - mThemeManager.applyCurrentUserOverlays(categoryToPackage, new FabricatedOverlay[] { + mThemeManager.applyCurrentUserOverlays(categoryToPackage, new FabricatedOverlay[]{ mSecondaryOverlay, mNeutralOverlay }, currentUser, managedProfiles); } else { diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt index 8f2a432d0ba1..fc20ac241e38 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt @@ -138,7 +138,7 @@ constructor( ensureOverlayRemoved() - val newRoot = SurfaceControlViewHost(context, context.display!!, wwm, false) + val newRoot = SurfaceControlViewHost(context, context.display!!, wwm) val newView = LightRevealScrim(context, null).apply { revealEffect = createLightRevealEffect() diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java index 345fc99f8a54..4dc78f9ec8a6 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java +++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java @@ -21,6 +21,7 @@ import android.app.Notification; import android.app.Notification.Action; import android.app.NotificationManager; import android.app.PendingIntent; +import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -215,9 +216,11 @@ public class StorageNotification extends CoreStartable { } else { // Boo, annoy the user to reinsert the private volume - final CharSequence title = mContext.getString(R.string.ext_media_missing_title, + final CharSequence title = + mContext.getString(R.string.ext_media_missing_title, rec.getNickname()); - final CharSequence text = mContext.getString(R.string.ext_media_missing_message); + final CharSequence text = + mContext.getString(R.string.ext_media_missing_message); Notification.Builder builder = new Notification.Builder(mContext, NotificationChannels.STORAGE) @@ -381,8 +384,8 @@ public class StorageNotification extends CoreStartable { if (rec.isSnoozed() && disk.isAdoptable()) { return null; } - - if (disk.isAdoptable() && !rec.isInited()) { + if (disk.isAdoptable() && !rec.isInited() && rec.getType() != VolumeInfo.TYPE_PUBLIC + && rec.getType() != VolumeInfo.TYPE_PRIVATE) { final CharSequence title = disk.getDescription(); final CharSequence text = mContext.getString( R.string.ext_media_new_notification_message, disk.getDescription()); diff --git a/packages/SystemUI/src/com/android/systemui/util/SysuiLifecycle.java b/packages/SystemUI/src/com/android/systemui/util/SysuiLifecycle.java deleted file mode 100644 index d73175310802..000000000000 --- a/packages/SystemUI/src/com/android/systemui/util/SysuiLifecycle.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.util; - -import static androidx.lifecycle.Lifecycle.State.DESTROYED; -import static androidx.lifecycle.Lifecycle.State.RESUMED; - -import android.view.View; -import android.view.View.OnAttachStateChangeListener; - -import androidx.annotation.NonNull; -import androidx.lifecycle.Lifecycle; -import androidx.lifecycle.LifecycleOwner; -import androidx.lifecycle.LifecycleRegistry; - -/** - * Tools for generating lifecycle from sysui objects. - */ -public class SysuiLifecycle { - - private SysuiLifecycle() { - } - - /** - * Get a lifecycle that will be put into the resumed state when the view is attached - * and goes to the destroyed state when the view is detached. - */ - public static LifecycleOwner viewAttachLifecycle(View v) { - return new ViewLifecycle(v); - } - - private static class ViewLifecycle implements LifecycleOwner, OnAttachStateChangeListener { - private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this); - - ViewLifecycle(View v) { - v.addOnAttachStateChangeListener(this); - if (v.isAttachedToWindow()) { - mLifecycle.markState(RESUMED); - } - } - - @NonNull - @Override - public Lifecycle getLifecycle() { - return mLifecycle; - } - - @Override - public void onViewAttachedToWindow(View v) { - mLifecycle.markState(RESUMED); - } - - @Override - public void onViewDetachedFromWindow(View v) { - mLifecycle.markState(DESTROYED); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/IpcSerializer.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/IpcSerializer.kt new file mode 100644 index 000000000000..c0331e6000bf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/IpcSerializer.kt @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.kotlin + +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.Job +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.runBlocking + +/** + * A utility for handling incoming IPCs from a Binder interface in the order that they are received. + * + * This class serves as a replacement for the common [android.os.Handler] message-queue pattern, + * where IPCs can arrive on arbitrary threads and are all enqueued onto a queue and processed by the + * Handler in-order. + * + * class MyService : Service() { + * + * private val serializer = IpcSerializer() + * + * // Need to invoke process() in order to actually process IPCs sent over the serializer. + * override fun onStart(...) = lifecycleScope.launch { + * serializer.process() + * } + * + * // In your binder implementation, use runSerializedBlocking to enqueue a function onto + * // the serializer. + * override fun onBind(intent: Intent?) = object : IAidlService.Stub() { + * override fun ipcMethodFoo() = serializer.runSerializedBlocking { + * ... + * } + * + * override fun ipcMethodBar() = serializer.runSerializedBlocking { + * ... + * } + * } + * } + */ +class IpcSerializer { + + private val channel = Channel<Pair<CompletableDeferred<Unit>, Job>>() + + /** + * Runs functions enqueued via usage of [runSerialized] and [runSerializedBlocking] serially. + * This method will never complete normally, so it must be launched in its own coroutine; if + * this is not actively running, no enqueued functions will be evaluated. + */ + suspend fun process(): Nothing { + for ((start, finish) in channel) { + // Signal to the sender that serializer has reached this message + start.complete(Unit) + // Wait to hear from the sender that it has finished running it's work, before handling + // the next message + finish.join() + } + error("Unexpected end of serialization channel") + } + + /** + * Enqueues [block] for evaluation by the serializer, suspending the caller until it has + * completed. It is up to the caller to define what thread this is evaluated in, determined + * by the [kotlin.coroutines.CoroutineContext] used. + */ + suspend fun <R> runSerialized(block: suspend () -> R): R { + val start = CompletableDeferred(Unit) + val finish = CompletableDeferred(Unit) + // Enqueue our message on the channel. + channel.send(start to finish) + // Wait for the serializer to reach our message + start.await() + // Now evaluate the block + val result = block() + // Notify the serializer that we've completed evaluation + finish.complete(Unit) + return result + } + + /** + * Enqueues [block] for evaluation by the serializer, blocking the binder thread until it has + * completed. Evaluation occurs on the binder thread, so methods like + * [android.os.Binder.getCallingUid] that depend on the current thread will work as expected. + */ + fun <R> runSerializedBlocking(block: suspend () -> R): R = runBlocking { runSerialized(block) } +} diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java index 4c762702892a..199048ec7b2e 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java @@ -38,6 +38,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; import android.provider.Settings; +import android.service.dreams.IDreamManager; import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.ZenModeConfig; import android.util.Log; @@ -49,9 +50,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; -import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dump.DumpManager; import com.android.systemui.model.SysUiState; import com.android.systemui.shade.ShadeController; import com.android.systemui.shared.system.QuickStepContract; @@ -76,7 +75,6 @@ import com.android.wm.shell.bubbles.Bubble; import com.android.wm.shell.bubbles.BubbleEntry; import com.android.wm.shell.bubbles.Bubbles; -import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -91,7 +89,7 @@ import java.util.function.IntConsumer; * The SysUi side bubbles manager which communicate with other SysUi components. */ @SysUISingleton -public class BubblesManager implements Dumpable { +public class BubblesManager { private static final String TAG = TAG_WITH_CLASS_NAME ? "BubblesManager" : TAG_BUBBLES; @@ -101,6 +99,7 @@ public class BubblesManager implements Dumpable { private final ShadeController mShadeController; private final IStatusBarService mBarService; private final INotificationManager mNotificationManager; + private final IDreamManager mDreamManager; private final NotificationVisibilityProvider mVisibilityProvider; private final NotificationInterruptStateProvider mNotificationInterruptStateProvider; private final NotificationLockscreenUserManager mNotifUserManager; @@ -126,6 +125,7 @@ public class BubblesManager implements Dumpable { ShadeController shadeController, @Nullable IStatusBarService statusBarService, INotificationManager notificationManager, + IDreamManager dreamManager, NotificationVisibilityProvider visibilityProvider, NotificationInterruptStateProvider interruptionStateProvider, ZenModeController zenModeController, @@ -134,7 +134,6 @@ public class BubblesManager implements Dumpable { CommonNotifCollection notifCollection, NotifPipeline notifPipeline, SysUiState sysUiState, - DumpManager dumpManager, Executor sysuiMainExecutor) { if (bubblesOptional.isPresent()) { return new BubblesManager(context, @@ -144,6 +143,7 @@ public class BubblesManager implements Dumpable { shadeController, statusBarService, notificationManager, + dreamManager, visibilityProvider, interruptionStateProvider, zenModeController, @@ -152,7 +152,6 @@ public class BubblesManager implements Dumpable { notifCollection, notifPipeline, sysUiState, - dumpManager, sysuiMainExecutor); } else { return null; @@ -167,6 +166,7 @@ public class BubblesManager implements Dumpable { ShadeController shadeController, @Nullable IStatusBarService statusBarService, INotificationManager notificationManager, + IDreamManager dreamManager, NotificationVisibilityProvider visibilityProvider, NotificationInterruptStateProvider interruptionStateProvider, ZenModeController zenModeController, @@ -175,13 +175,13 @@ public class BubblesManager implements Dumpable { CommonNotifCollection notifCollection, NotifPipeline notifPipeline, SysUiState sysUiState, - DumpManager dumpManager, Executor sysuiMainExecutor) { mContext = context; mBubbles = bubbles; mNotificationShadeWindowController = notificationShadeWindowController; mShadeController = shadeController; mNotificationManager = notificationManager; + mDreamManager = dreamManager; mVisibilityProvider = visibilityProvider; mNotificationInterruptStateProvider = interruptionStateProvider; mNotifUserManager = notifUserManager; @@ -197,13 +197,11 @@ public class BubblesManager implements Dumpable { setupNotifPipeline(); - dumpManager.registerDumpable(TAG, this); - keyguardStateController.addCallback(new KeyguardStateController.Callback() { @Override public void onKeyguardShowingChanged() { boolean isUnlockedShade = !keyguardStateController.isShowing() - && !keyguardStateController.isOccluded(); + && !isDreamingOrInPreview(); bubbles.onStatusBarStateChanged(isUnlockedShade); } }); @@ -397,6 +395,15 @@ public class BubblesManager implements Dumpable { mBubbles.setSysuiProxy(mSysuiProxy); } + private boolean isDreamingOrInPreview() { + try { + return mDreamManager.isDreamingOrInPreview(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to query dream manager.", e); + return false; + } + } + private void setupNotifPipeline() { mNotifPipeline.addCollectionListener(new NotifCollectionListener() { @Override @@ -633,11 +640,6 @@ public class BubblesManager implements Dumpable { } } - @Override - public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { - mBubbles.dump(pw, args); - } - /** Checks whether bubbles are enabled for this user, handles negative userIds. */ public static boolean areBubblesEnabled(@NonNull Context context, @NonNull UserHandle user) { if (user.getIdentifier() < 0) { diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index eba279587629..a4a59fc9d4a7 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -29,14 +29,16 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_S import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED; import android.content.Context; +import android.content.pm.UserInfo; import android.content.res.Configuration; import android.graphics.Rect; -import android.graphics.drawable.Drawable; import android.inputmethodservice.InputMethodService; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.view.KeyEvent; +import androidx.annotation.NonNull; + import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; @@ -47,11 +49,11 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.model.SysUiState; +import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.tracing.ProtoTraceable; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.tracing.ProtoTracer; import com.android.systemui.tracing.nano.SystemUiTraceProto; import com.android.wm.shell.nano.WmShellTraceProto; @@ -66,6 +68,7 @@ import com.android.wm.shell.sysui.ShellInterface; import java.io.PrintWriter; import java.util.Arrays; +import java.util.List; import java.util.Optional; import java.util.concurrent.Executor; @@ -115,7 +118,7 @@ public final class WMShell extends CoreStartable private final SysUiState mSysUiState; private final WakefulnessLifecycle mWakefulnessLifecycle; private final ProtoTracer mProtoTracer; - private final UserInfoController mUserInfoController; + private final UserTracker mUserTracker; private final Executor mSysUiMainExecutor; // Listeners and callbacks. Note that we prefer member variable over anonymous class here to @@ -144,9 +147,20 @@ public final class WMShell extends CoreStartable mShell.onKeyguardDismissAnimationFinished(); } }; + private final UserTracker.Callback mUserChangedCallback = + new UserTracker.Callback() { + @Override + public void onUserChanged(int newUser, @NonNull Context userContext) { + mShell.onUserChanged(newUser, userContext); + } + + @Override + public void onProfilesChanged(@NonNull List<UserInfo> profiles) { + mShell.onUserProfilesChanged(profiles); + } + }; private boolean mIsSysUiStateValid; - private KeyguardUpdateMonitorCallback mOneHandedKeyguardCallback; private WakefulnessLifecycle.Observer mWakefulnessObserver; @Inject @@ -163,7 +177,7 @@ public final class WMShell extends CoreStartable SysUiState sysUiState, ProtoTracer protoTracer, WakefulnessLifecycle wakefulnessLifecycle, - UserInfoController userInfoController, + UserTracker userTracker, @Main Executor sysUiMainExecutor) { super(context); mShell = shell; @@ -178,7 +192,7 @@ public final class WMShell extends CoreStartable mOneHandedOptional = oneHandedOptional; mWakefulnessLifecycle = wakefulnessLifecycle; mProtoTracer = protoTracer; - mUserInfoController = userInfoController; + mUserTracker = userTracker; mSysUiMainExecutor = sysUiMainExecutor; } @@ -192,8 +206,9 @@ public final class WMShell extends CoreStartable mKeyguardStateController.addCallback(mKeyguardStateCallback); mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback); - // TODO: Consider piping config change and other common calls to a shell component to - // delegate internally + // Subscribe to user changes + mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor()); + mProtoTracer.add(this); mCommandQueue.addCallback(this); mPipOptional.ifPresent(this::initPip); @@ -214,10 +229,6 @@ public final class WMShell extends CoreStartable mIsSysUiStateValid = (sysUiStateFlag & INVALID_SYSUI_STATE_MASK) == 0; pip.onSystemUiStateChanged(mIsSysUiStateValid, sysUiStateFlag); }); - - // The media session listener needs to be re-registered when switching users - mUserInfoController.addCallback((String name, Drawable picture, String userAccount) -> - pip.registerSessionListenerForCurrentUser()); } @VisibleForTesting @@ -267,15 +278,6 @@ public final class WMShell extends CoreStartable } }); - // TODO: Either move into ShellInterface or register a receiver on the Shell side directly - mOneHandedKeyguardCallback = new KeyguardUpdateMonitorCallback() { - @Override - public void onUserSwitchComplete(int userId) { - oneHanded.onUserSwitch(userId); - } - }; - mKeyguardUpdateMonitor.registerCallback(mOneHandedKeyguardCallback); - mWakefulnessObserver = new WakefulnessLifecycle.Observer() { @Override diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml index 763a5cb810f9..ba2804572ef5 100644 --- a/packages/SystemUI/tests/AndroidManifest.xml +++ b/packages/SystemUI/tests/AndroidManifest.xml @@ -53,7 +53,8 @@ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.REGISTER_WINDOW_MANAGER_LISTENERS" /> - <application android:debuggable="true" android:largeHeap="true"> + <application android:debuggable="true" android:largeHeap="true" + android:enableOnBackInvokedCallback="true" > <uses-library android:name="android.test.runner" /> <receiver android:name="com.android.systemui.SliceBroadcastRelayHandlerTest$Receiver" diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt index 328ad39cddd5..58d906907488 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt @@ -41,11 +41,13 @@ import org.mockito.junit.MockitoJUnit @SmallTest class AuthBiometricFingerprintAndFaceViewTest : SysuiTestCase() { - @JvmField @Rule + @JvmField + @Rule var mockitoRule = MockitoJUnit.rule() @Mock private lateinit var callback: AuthBiometricView.Callback + @Mock private lateinit var panelController: AuthPanelController @@ -67,6 +69,7 @@ class AuthBiometricFingerprintAndFaceViewTest : SysuiTestCase() { fun fingerprintSuccessDoesNotRequireExplicitConfirmation() { biometricView.onDialogAnimatedIn() biometricView.onAuthenticationSucceeded(TYPE_FINGERPRINT) + TestableLooper.get(this).moveTimeForward(1000) waitForIdleSync() assertThat(biometricView.isAuthenticated).isTrue() @@ -86,6 +89,7 @@ class AuthBiometricFingerprintAndFaceViewTest : SysuiTestCase() { // icon acts as confirm button biometricView.mIconView.performClick() + TestableLooper.get(this).moveTimeForward(1000) waitForIdleSync() assertThat(biometricView.isAuthenticated).isTrue() @@ -102,6 +106,7 @@ class AuthBiometricFingerprintAndFaceViewTest : SysuiTestCase() { verify(callback, never()).onAction(AuthBiometricView.Callback.ACTION_ERROR) biometricView.onError(TYPE_FINGERPRINT, "that's a nope") + TestableLooper.get(this).moveTimeForward(1000) waitForIdleSync() verify(callback).onAction(AuthBiometricView.Callback.ACTION_ERROR) diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt index 687cb517b2f4..bce98cf116d4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt @@ -42,20 +42,23 @@ import org.mockito.junit.MockitoJUnit @SmallTest class AuthBiometricFingerprintViewTest : SysuiTestCase() { - @JvmField @Rule + @JvmField + @Rule val mockitoRule = MockitoJUnit.rule() @Mock private lateinit var callback: AuthBiometricView.Callback + @Mock private lateinit var panelController: AuthPanelController private lateinit var biometricView: AuthBiometricView private fun createView(allowDeviceCredential: Boolean = false): AuthBiometricFingerprintView { - val view = R.layout.auth_biometric_fingerprint_view.asTestAuthBiometricView( + val view: AuthBiometricFingerprintView = + R.layout.auth_biometric_fingerprint_view.asTestAuthBiometricView( mContext, callback, panelController, allowDeviceCredential = allowDeviceCredential - ) as AuthBiometricFingerprintView + ) waitForIdleSync() return view } @@ -73,6 +76,7 @@ class AuthBiometricFingerprintViewTest : SysuiTestCase() { @Test fun testOnAuthenticationSucceeded_noConfirmationRequired_sendsActionAuthenticated() { biometricView.onAuthenticationSucceeded(BiometricAuthenticator.TYPE_FINGERPRINT) + TestableLooper.get(this).moveTimeForward(1000) waitForIdleSync() assertThat(biometricView.isAuthenticated).isTrue() @@ -83,6 +87,7 @@ class AuthBiometricFingerprintViewTest : SysuiTestCase() { fun testOnAuthenticationSucceeded_confirmationRequired_updatesDialogContents() { biometricView.setRequireConfirmation(true) biometricView.onAuthenticationSucceeded(BiometricAuthenticator.TYPE_FINGERPRINT) + TestableLooper.get(this).moveTimeForward(1000) waitForIdleSync() // TODO: this should be tested in the subclasses @@ -104,6 +109,7 @@ class AuthBiometricFingerprintViewTest : SysuiTestCase() { @Test fun testPositiveButton_sendsActionAuthenticated() { biometricView.mConfirmButton.performClick() + TestableLooper.get(this).moveTimeForward(1000) waitForIdleSync() verify(callback).onAction(AuthBiometricView.Callback.ACTION_AUTHENTICATED) @@ -114,6 +120,7 @@ class AuthBiometricFingerprintViewTest : SysuiTestCase() { fun testNegativeButton_beforeAuthentication_sendsActionButtonNegative() { biometricView.onDialogAnimatedIn() biometricView.mNegativeButton.performClick() + TestableLooper.get(this).moveTimeForward(1000) waitForIdleSync() verify(callback).onAction(AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE) @@ -126,6 +133,7 @@ class AuthBiometricFingerprintViewTest : SysuiTestCase() { assertThat(biometricView.mNegativeButton.visibility).isEqualTo(View.GONE) biometricView.mCancelButton.performClick() + TestableLooper.get(this).moveTimeForward(1000) waitForIdleSync() verify(callback).onAction(AuthBiometricView.Callback.ACTION_USER_CANCELED) @@ -134,6 +142,7 @@ class AuthBiometricFingerprintViewTest : SysuiTestCase() { @Test fun testTryAgainButton_sendsActionTryAgain() { biometricView.mTryAgainButton.performClick() + TestableLooper.get(this).moveTimeForward(1000) waitForIdleSync() verify(callback).onAction(AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN) @@ -144,6 +153,7 @@ class AuthBiometricFingerprintViewTest : SysuiTestCase() { @Test fun testOnErrorSendsActionError() { biometricView.onError(BiometricAuthenticator.TYPE_FACE, "testError") + TestableLooper.get(this).moveTimeForward(1000) waitForIdleSync() verify(callback).onAction(eq(AuthBiometricView.Callback.ACTION_ERROR)) @@ -156,6 +166,7 @@ class AuthBiometricFingerprintViewTest : SysuiTestCase() { val message = "another error" biometricView.onError(BiometricAuthenticator.TYPE_FACE, message) + TestableLooper.get(this).moveTimeForward(1000) waitForIdleSync() assertThat(biometricView.isAuthenticating).isFalse() @@ -178,6 +189,7 @@ class AuthBiometricFingerprintViewTest : SysuiTestCase() { val view = View(mContext) biometricView.setBackgroundView(view) biometricView.onAuthenticationSucceeded(BiometricAuthenticator.TYPE_FINGERPRINT) + waitForIdleSync() view.performClick() verify(callback, never()) @@ -225,14 +237,14 @@ class AuthBiometricFingerprintViewTest : SysuiTestCase() { biometricView.onSaveState(state) assertThat(biometricView.mTryAgainButton.visibility).isEqualTo(View.GONE) assertThat(state.getInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY)) - .isEqualTo(View.GONE) + .isEqualTo(View.GONE) assertThat(state.getInt(AuthDialog.KEY_BIOMETRIC_STATE)) - .isEqualTo(AuthBiometricView.STATE_ERROR) + .isEqualTo(AuthBiometricView.STATE_ERROR) assertThat(biometricView.mIndicatorView.visibility).isEqualTo(View.VISIBLE) assertThat(state.getBoolean(AuthDialog.KEY_BIOMETRIC_INDICATOR_ERROR_SHOWING)).isTrue() assertThat(biometricView.mIndicatorView.text).isEqualTo(failureMessage) assertThat(state.getString(AuthDialog.KEY_BIOMETRIC_INDICATOR_STRING)) - .isEqualTo(failureMessage) + .isEqualTo(failureMessage) // TODO: Test dialog size. Should move requireConfirmation to buildBiometricPromptBundle diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java index 01309f86a137..7f6b79b48939 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java @@ -308,6 +308,12 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { } @Test + public void testOnViewDetachedRemovesViews() { + mController.onViewDetached(); + verify(mView).removeAllStatusBarItemViews(); + } + + @Test public void testWifiIconHiddenWhenWifiBecomesAvailable() { // Make sure wifi starts out unavailable when onViewAttached is called, and then returns // true on the second query. diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java index 09976e0e6192..571dd3d1faf3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java @@ -106,7 +106,7 @@ public class ComplicationTypesUpdaterTest extends SysuiTestCase { private ContentObserver captureSettingsObserver() { verify(mSecureSettings).registerContentObserverForUser( - eq(Settings.Secure.SCREENSAVER_ENABLED_COMPLICATIONS), + eq(Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED), mSettingsObserverCaptor.capture(), eq(UserHandle.myUserId())); return mSettingsObserverCaptor.getValue(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java index 141a213a5b6a..b42b7695cedd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java @@ -42,8 +42,11 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.GestureDetector; import android.view.IWindowManager; +import android.view.KeyEvent; import android.view.MotionEvent; import android.view.WindowManagerPolicyConstants; +import android.window.OnBackInvokedCallback; +import android.window.OnBackInvokedDispatcher; import androidx.test.filters.SmallTest; @@ -73,6 +76,8 @@ import com.android.systemui.util.settings.SecureSettings; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -117,6 +122,8 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { @Mock private CentralSurfaces mCentralSurfaces; @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock private DialogLaunchAnimator mDialogLaunchAnimator; + @Mock private OnBackInvokedDispatcher mOnBackInvokedDispatcher; + @Captor private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback; private TestableLooper mTestableLooper; @@ -203,6 +210,58 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { } @Test + public void testPredictiveBackCallbackRegisteredAndUnregistered() { + mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite); + doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems(); + doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any()); + doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any()); + String[] actions = { + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART, + }; + doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions(); + + GlobalActionsDialogLite.ActionsDialogLite dialog = mGlobalActionsDialogLite.createDialog(); + dialog.setBackDispatcherOverride(mOnBackInvokedDispatcher); + dialog.create(); + mTestableLooper.processAllMessages(); + verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback( + eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), any()); + dialog.onDetachedFromWindow(); + mTestableLooper.processAllMessages(); + verify(mOnBackInvokedDispatcher).unregisterOnBackInvokedCallback(any()); + } + + @Test + public void testPredictiveBackInvocationDismissesDialog() { + mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite); + doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems(); + doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any()); + doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any()); + String[] actions = { + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART, + }; + doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions(); + + GlobalActionsDialogLite.ActionsDialogLite dialog = mGlobalActionsDialogLite.createDialog(); + dialog.create(); + dialog.show(); + mTestableLooper.processAllMessages(); + dialog.getWindow().injectInputEvent( + new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK)); + dialog.getWindow().injectInputEvent( + new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK)); + mTestableLooper.processAllMessages(); + verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_CLOSE_BACK); + assertThat(dialog.isShowing()).isFalse(); + } + + @Test public void testSingleTap_logAndDismiss() { mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite); doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt index 19491f41a0c1..14b85b8b5e56 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt @@ -37,6 +37,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat +import kotlin.math.max +import kotlin.math.min import kotlin.reflect.KClass import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -127,6 +129,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { val testConfig = TestConfig( isVisible = true, + isClickable = true, icon = mock(), canShowWhileLocked = false, intent = Intent("action"), @@ -154,6 +157,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { val config = TestConfig( isVisible = true, + isClickable = true, icon = mock(), canShowWhileLocked = false, intent = null, // This will cause it to tell the system that the click was handled. @@ -201,6 +205,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { val testConfig = TestConfig( isVisible = true, + isClickable = true, icon = mock(), canShowWhileLocked = false, intent = Intent("action"), @@ -260,6 +265,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { testConfig = TestConfig( isVisible = true, + isClickable = true, icon = mock(), canShowWhileLocked = true, ) @@ -269,6 +275,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { testConfig = TestConfig( isVisible = true, + isClickable = true, icon = mock(), canShowWhileLocked = false, ) @@ -342,6 +349,129 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { job.cancel() } + @Test + fun `isClickable - true when alpha at threshold`() = runBlockingTest { + repository.setKeyguardShowing(true) + repository.setBottomAreaAlpha( + KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD + ) + + val testConfig = + TestConfig( + isVisible = true, + isClickable = true, + icon = mock(), + canShowWhileLocked = false, + intent = Intent("action"), + ) + val configKey = + setUpQuickAffordanceModel( + position = KeyguardQuickAffordancePosition.BOTTOM_START, + testConfig = testConfig, + ) + + var latest: KeyguardQuickAffordanceViewModel? = null + val job = underTest.startButton.onEach { latest = it }.launchIn(this) + + assertQuickAffordanceViewModel( + viewModel = latest, + testConfig = testConfig, + configKey = configKey, + ) + job.cancel() + } + + @Test + fun `isClickable - true when alpha above threshold`() = runBlockingTest { + repository.setKeyguardShowing(true) + var latest: KeyguardQuickAffordanceViewModel? = null + val job = underTest.startButton.onEach { latest = it }.launchIn(this) + repository.setBottomAreaAlpha( + min(1f, KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD + 0.1f), + ) + + val testConfig = + TestConfig( + isVisible = true, + isClickable = true, + icon = mock(), + canShowWhileLocked = false, + intent = Intent("action"), + ) + val configKey = + setUpQuickAffordanceModel( + position = KeyguardQuickAffordancePosition.BOTTOM_START, + testConfig = testConfig, + ) + + assertQuickAffordanceViewModel( + viewModel = latest, + testConfig = testConfig, + configKey = configKey, + ) + job.cancel() + } + + @Test + fun `isClickable - false when alpha below threshold`() = runBlockingTest { + repository.setKeyguardShowing(true) + var latest: KeyguardQuickAffordanceViewModel? = null + val job = underTest.startButton.onEach { latest = it }.launchIn(this) + repository.setBottomAreaAlpha( + max(0f, KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD - 0.1f), + ) + + val testConfig = + TestConfig( + isVisible = true, + isClickable = false, + icon = mock(), + canShowWhileLocked = false, + intent = Intent("action"), + ) + val configKey = + setUpQuickAffordanceModel( + position = KeyguardQuickAffordancePosition.BOTTOM_START, + testConfig = testConfig, + ) + + assertQuickAffordanceViewModel( + viewModel = latest, + testConfig = testConfig, + configKey = configKey, + ) + job.cancel() + } + + @Test + fun `isClickable - false when alpha at zero`() = runBlockingTest { + repository.setKeyguardShowing(true) + var latest: KeyguardQuickAffordanceViewModel? = null + val job = underTest.startButton.onEach { latest = it }.launchIn(this) + repository.setBottomAreaAlpha(0f) + + val testConfig = + TestConfig( + isVisible = true, + isClickable = false, + icon = mock(), + canShowWhileLocked = false, + intent = Intent("action"), + ) + val configKey = + setUpQuickAffordanceModel( + position = KeyguardQuickAffordancePosition.BOTTOM_START, + testConfig = testConfig, + ) + + assertQuickAffordanceViewModel( + viewModel = latest, + testConfig = testConfig, + configKey = configKey, + ) + job.cancel() + } + private suspend fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float { repository.setDozeAmount(dozeAmount) return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET) @@ -384,6 +514,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { ) { checkNotNull(viewModel) assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible) + assertThat(viewModel.isClickable).isEqualTo(testConfig.isClickable) if (testConfig.isVisible) { assertThat(viewModel.icon).isEqualTo(testConfig.icon) viewModel.onClicked.invoke( @@ -404,6 +535,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { private data class TestConfig( val isVisible: Boolean, + val isClickable: Boolean = false, val icon: ContainedDrawable? = null, val canShowWhileLocked: Boolean = false, val intent: Intent? = null, diff --git a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/InstantTaskExecutorRule.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/InstantTaskExecutorRule.kt new file mode 100644 index 000000000000..373af5cdf4b7 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/InstantTaskExecutorRule.kt @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.lifecycle + +import androidx.arch.core.executor.ArchTaskExecutor +import androidx.arch.core.executor.TaskExecutor +import org.junit.rules.TestWatcher +import org.junit.runner.Description + +/** + * Test rule that makes ArchTaskExecutor main thread assertions pass. There is one such assert + * in LifecycleRegistry. + */ +class InstantTaskExecutorRule : TestWatcher() { + // TODO(b/240620122): This is a copy of + // androidx/arch/core/executor/testing/InstantTaskExecutorRule which should be replaced + // with a dependency on the real library once b/ is cleared. + override fun starting(description: Description) { + super.starting(description) + ArchTaskExecutor.getInstance() + .setDelegate( + object : TaskExecutor() { + override fun executeOnDiskIO(runnable: Runnable) { + runnable.run() + } + + override fun postToMainThread(runnable: Runnable) { + runnable.run() + } + + override fun isMainThread(): Boolean { + return true + } + } + ) + } + + override fun finished(description: Description) { + super.finished(description) + ArchTaskExecutor.getInstance().setDelegate(null) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt index 80f3e46b848f..91a6de6ae4c0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt @@ -20,8 +20,6 @@ package com.android.systemui.lifecycle import android.testing.TestableLooper.RunWithLooper import android.view.View import android.view.ViewTreeObserver -import androidx.arch.core.executor.ArchTaskExecutor -import androidx.arch.core.executor.TaskExecutor import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.test.filters.SmallTest @@ -35,8 +33,6 @@ import kotlinx.coroutines.test.runBlockingTest import org.junit.Before import org.junit.Rule import org.junit.Test -import org.junit.rules.TestWatcher -import org.junit.runner.Description import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.mockito.Mock @@ -282,38 +278,4 @@ class RepeatWhenAttachedTest : SysuiTestCase() { _invocations.add(Invocation(lifecycleOwner)) } } - - /** - * Test rule that makes ArchTaskExecutor main thread assertions pass. There is one such assert - * in LifecycleRegistry. - */ - class InstantTaskExecutorRule : TestWatcher() { - // TODO(b/240620122): This is a copy of - // androidx/arch/core/executor/testing/InstantTaskExecutorRule which should be replaced - // with a dependency on the real library once b/ is cleared. - override fun starting(description: Description) { - super.starting(description) - ArchTaskExecutor.getInstance() - .setDelegate( - object : TaskExecutor() { - override fun executeOnDiskIO(runnable: Runnable) { - runnable.run() - } - - override fun postToMainThread(runnable: Runnable) { - runnable.run() - } - - override fun isMainThread(): Boolean { - return true - } - } - ) - } - - override fun finished(description: Description) { - super.finished(description) - ArchTaskExecutor.getInstance().setDelegate(null) - } - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt index fcfef4a44128..c41fac71a179 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt @@ -16,19 +16,22 @@ package com.android.systemui.media +import android.provider.Settings import android.test.suitebuilder.annotation.SmallTest import android.testing.AndroidTestingRunner +import android.testing.TestableLooper import android.view.View.GONE import android.view.View.VISIBLE import android.widget.FrameLayout import com.android.systemui.SysuiTestCase -import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.stack.MediaContainerView import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.animation.UniqueObjectHostView +import com.android.systemui.util.settings.FakeSettings +import com.android.systemui.utils.os.FakeHandler import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertTrue import org.junit.Before @@ -37,11 +40,12 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.verify -import org.mockito.junit.MockitoJUnit import org.mockito.Mockito.`when` as whenever +import org.mockito.junit.MockitoJUnit @SmallTest @RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper class KeyguardMediaControllerTest : SysuiTestCase() { @Mock @@ -53,31 +57,33 @@ class KeyguardMediaControllerTest : SysuiTestCase() { @Mock private lateinit var configurationController: ConfigurationController - @Mock - private lateinit var notificationLockscreenUserManager: NotificationLockscreenUserManager @JvmField @Rule val mockito = MockitoJUnit.rule() private val mediaContainerView: MediaContainerView = MediaContainerView(context, null) private val hostView = UniqueObjectHostView(context) + private val settings = FakeSettings() private lateinit var keyguardMediaController: KeyguardMediaController + private lateinit var testableLooper: TestableLooper + private lateinit var fakeHandler: FakeHandler @Before fun setup() { // default state is positive, media should show up whenever(mediaHost.visible).thenReturn(true) whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) - whenever(notificationLockscreenUserManager.shouldShowLockscreenNotifications()) - .thenReturn(true) whenever(mediaHost.hostView).thenReturn(hostView) hostView.layoutParams = FrameLayout.LayoutParams(100, 100) + testableLooper = TestableLooper.get(this) + fakeHandler = FakeHandler(testableLooper.looper) keyguardMediaController = KeyguardMediaController( mediaHost, bypassController, statusBarStateController, - notificationLockscreenUserManager, context, - configurationController + settings, + fakeHandler, + configurationController, ) keyguardMediaController.attachSinglePaneContainer(mediaContainerView) keyguardMediaController.useSplitShade = false @@ -106,9 +112,8 @@ class KeyguardMediaControllerTest : SysuiTestCase() { } @Test - fun testHiddenOnKeyguard_whenNotificationsAreHidden() { - whenever(notificationLockscreenUserManager.shouldShowLockscreenNotifications()) - .thenReturn(false) + fun testHiddenOnKeyguard_whenMediaOnLockScreenDisabled() { + settings.putInt(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, 0) keyguardMediaController.refreshMediaPosition() @@ -116,6 +121,15 @@ class KeyguardMediaControllerTest : SysuiTestCase() { } @Test + fun testAvailableOnKeyguard_whenMediaOnLockScreenEnabled() { + settings.putInt(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, 1) + + keyguardMediaController.refreshMediaPosition() + + assertThat(mediaContainerView.visibility).isEqualTo(VISIBLE) + } + + @Test fun testActivatesSplitShadeContainerInSplitShadeMode() { val splitShadeContainer = FrameLayout(context) keyguardMediaController.attachSplitShadeContainer(splitShadeContainer) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt index 1cce7cfb5b8a..d1ed8e983cdd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt @@ -50,11 +50,10 @@ import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.never import org.mockito.Mockito.reset -import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions -import org.mockito.junit.MockitoJUnit import org.mockito.Mockito.`when` as whenever +import org.mockito.junit.MockitoJUnit private const val KEY = "KEY" private const val KEY_2 = "KEY_2" @@ -287,6 +286,30 @@ class MediaDataManagerTest : SysuiTestCase() { } @Test + fun testOnNotificationAdded_hasSubstituteName_isUsed() { + val subName = "Substitute Name" + val notif = SbnBuilder().run { + modifyNotification(context).also { + it.extras = Bundle().apply { + putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, subName) + } + it.setStyle(MediaStyle().apply { + setMediaSession(session.sessionToken) + }) + } + build() + } + + mediaDataManager.onNotificationAdded(KEY, notif) + assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) + assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) + verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true), + eq(0), eq(false)) + + assertThat(mediaDataCaptor.value!!.app).isEqualTo(subName) + } + + @Test fun testLoadMediaDataInBg_invalidTokenNoCrash() { val bundle = Bundle() // wrong data type diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt index 369913d1ea73..18bfd04102b7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.media import android.graphics.Rect +import android.provider.Settings import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.ViewGroup @@ -30,7 +31,6 @@ import com.android.systemui.dreams.DreamOverlayStateController import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.shade.testing.FakeNotifPanelEvents -import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.phone.KeyguardBypassController @@ -38,6 +38,8 @@ import com.android.systemui.statusbar.policy.FakeConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.animation.UniqueObjectHostView import com.android.systemui.util.mockito.any +import com.android.systemui.util.settings.FakeSettings +import com.android.systemui.utils.os.FakeHandler import com.google.common.truth.Truth.assertThat import org.junit.Assert.assertNotNull import org.junit.Before @@ -53,7 +55,6 @@ import org.mockito.Mock import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.times import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit @@ -68,7 +69,6 @@ class MediaHierarchyManagerTest : SysuiTestCase() { @Mock private lateinit var bypassController: KeyguardBypassController @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController - @Mock private lateinit var notificationLockscreenUserManager: NotificationLockscreenUserManager @Mock private lateinit var mediaCarouselController: MediaCarouselController @Mock private lateinit var mediaCarouselScrollHandler: MediaCarouselScrollHandler @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle @@ -82,37 +82,42 @@ class MediaHierarchyManagerTest : SysuiTestCase() { @JvmField @Rule val mockito = MockitoJUnit.rule() - private lateinit var mediaHiearchyManager: MediaHierarchyManager + private lateinit var mediaHierarchyManager: MediaHierarchyManager private lateinit var mediaFrame: ViewGroup private val configurationController = FakeConfigurationController() private val notifPanelEvents = FakeNotifPanelEvents() + private val settings = FakeSettings() + private lateinit var testableLooper: TestableLooper + private lateinit var fakeHandler: FakeHandler @Before fun setup() { context.getOrCreateTestableResources().addOverride( R.bool.config_use_split_notification_shade, false) mediaFrame = FrameLayout(context) - `when`(mediaCarouselController.mediaFrame).thenReturn(mediaFrame) - mediaHiearchyManager = MediaHierarchyManager( + testableLooper = TestableLooper.get(this) + fakeHandler = FakeHandler(testableLooper.looper) + whenever(mediaCarouselController.mediaFrame).thenReturn(mediaFrame) + mediaHierarchyManager = MediaHierarchyManager( context, statusBarStateController, keyguardStateController, bypassController, mediaCarouselController, - notificationLockscreenUserManager, keyguardViewController, dreamOverlayStateController, configurationController, wakefulnessLifecycle, notifPanelEvents, - ) + settings, + fakeHandler,) verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture()) verify(statusBarStateController).addCallback(statusBarCallback.capture()) setupHost(lockHost, MediaHierarchyManager.LOCATION_LOCKSCREEN, LOCKSCREEN_TOP) setupHost(qsHost, MediaHierarchyManager.LOCATION_QS, QS_TOP) setupHost(qqsHost, MediaHierarchyManager.LOCATION_QQS, QQS_TOP) - `when`(statusBarStateController.state).thenReturn(StatusBarState.SHADE) - `when`(mediaCarouselController.mediaCarouselScrollHandler) + whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE) + whenever(mediaCarouselController.mediaCarouselScrollHandler) .thenReturn(mediaCarouselScrollHandler) val observer = wakefullnessObserver.value assertNotNull("lifecycle observer wasn't registered", observer) @@ -122,30 +127,30 @@ class MediaHierarchyManagerTest : SysuiTestCase() { } private fun setupHost(host: MediaHost, location: Int, top: Int) { - `when`(host.location).thenReturn(location) - `when`(host.currentBounds).thenReturn(Rect(0, top, 0, top)) - `when`(host.hostView).thenReturn(uniqueObjectHostView) - `when`(host.visible).thenReturn(true) - mediaHiearchyManager.register(host) + whenever(host.location).thenReturn(location) + whenever(host.currentBounds).thenReturn(Rect(0, top, 0, top)) + whenever(host.hostView).thenReturn(uniqueObjectHostView) + whenever(host.visible).thenReturn(true) + mediaHierarchyManager.register(host) } @Test fun testHostViewSetOnRegister() { - val host = mediaHiearchyManager.register(lockHost) + val host = mediaHierarchyManager.register(lockHost) verify(lockHost).hostView = eq(host) } @Test fun testBlockedWhenScreenTurningOff() { // Let's set it onto QS: - mediaHiearchyManager.qsExpansion = 1.0f + mediaHierarchyManager.qsExpansion = 1.0f verify(mediaCarouselController).onDesiredLocationChanged(ArgumentMatchers.anyInt(), any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong()) val observer = wakefullnessObserver.value assertNotNull("lifecycle observer wasn't registered", observer) observer.onStartedGoingToSleep() clearInvocations(mediaCarouselController) - mediaHiearchyManager.qsExpansion = 0.0f + mediaHierarchyManager.qsExpansion = 0.0f verify(mediaCarouselController, times(0)) .onDesiredLocationChanged(ArgumentMatchers.anyInt(), any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong()) @@ -154,13 +159,13 @@ class MediaHierarchyManagerTest : SysuiTestCase() { @Test fun testAllowedWhenNotTurningOff() { // Let's set it onto QS: - mediaHiearchyManager.qsExpansion = 1.0f + mediaHierarchyManager.qsExpansion = 1.0f verify(mediaCarouselController).onDesiredLocationChanged(ArgumentMatchers.anyInt(), any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong()) val observer = wakefullnessObserver.value assertNotNull("lifecycle observer wasn't registered", observer) clearInvocations(mediaCarouselController) - mediaHiearchyManager.qsExpansion = 0.0f + mediaHierarchyManager.qsExpansion = 0.0f verify(mediaCarouselController).onDesiredLocationChanged(ArgumentMatchers.anyInt(), any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong()) } @@ -170,7 +175,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { goToLockscreen() // Let's transition all the way to full shade - mediaHiearchyManager.setTransitionToFullShadeAmount(100000f) + mediaHierarchyManager.setTransitionToFullShadeAmount(100000f) verify(mediaCarouselController).onDesiredLocationChanged( eq(MediaHierarchyManager.LOCATION_QQS), any(MediaHostState::class.java), @@ -180,7 +185,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { clearInvocations(mediaCarouselController) // Let's go back to the lock screen - mediaHiearchyManager.setTransitionToFullShadeAmount(0.0f) + mediaHierarchyManager.setTransitionToFullShadeAmount(0.0f) verify(mediaCarouselController).onDesiredLocationChanged( eq(MediaHierarchyManager.LOCATION_LOCKSCREEN), any(MediaHostState::class.java), @@ -189,7 +194,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { anyLong()) // Let's make sure alpha is set - mediaHiearchyManager.setTransitionToFullShadeAmount(2.0f) + mediaHierarchyManager.setTransitionToFullShadeAmount(2.0f) assertThat(mediaFrame.alpha).isNotEqualTo(1.0f) } @@ -198,7 +203,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { goToLockscreen() expandQS() - val transformType = mediaHiearchyManager.calculateTransformationType() + val transformType = mediaHierarchyManager.calculateTransformationType() assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_FADE) } @@ -206,7 +211,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { fun calculateTransformationType_notOnLockscreen_returnsTransition() { expandQS() - val transformType = mediaHiearchyManager.calculateTransformationType() + val transformType = mediaHierarchyManager.calculateTransformationType() assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_TRANSITION) } @@ -216,7 +221,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { goToLockscreen() expandQS() - val transformType = mediaHiearchyManager.calculateTransformationType() + val transformType = mediaHierarchyManager.calculateTransformationType() assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_FADE) } @@ -226,9 +231,9 @@ class MediaHierarchyManagerTest : SysuiTestCase() { enableSplitShade() goToLockscreen() expandQS() - mediaHiearchyManager.setTransitionToFullShadeAmount(10000f) + mediaHierarchyManager.setTransitionToFullShadeAmount(10000f) - val transformType = mediaHiearchyManager.calculateTransformationType() + val transformType = mediaHierarchyManager.calculateTransformationType() assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_TRANSITION) } @@ -238,9 +243,9 @@ class MediaHierarchyManagerTest : SysuiTestCase() { goToLockscreen() expandQS() whenever(lockHost.visible).thenReturn(false) - mediaHiearchyManager.setTransitionToFullShadeAmount(10000f) + mediaHierarchyManager.setTransitionToFullShadeAmount(10000f) - val transformType = mediaHiearchyManager.calculateTransformationType() + val transformType = mediaHierarchyManager.calculateTransformationType() assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_FADE) } @@ -250,9 +255,9 @@ class MediaHierarchyManagerTest : SysuiTestCase() { goToLockscreen() goToLockedShade() expandQS() - mediaHiearchyManager.setTransitionToFullShadeAmount(0f) + mediaHierarchyManager.setTransitionToFullShadeAmount(0f) - val transformType = mediaHiearchyManager.calculateTransformationType() + val transformType = mediaHierarchyManager.calculateTransformationType() assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_FADE) } @@ -261,13 +266,13 @@ class MediaHierarchyManagerTest : SysuiTestCase() { goToLockscreen() goToLockedShade() - val transformType = mediaHiearchyManager.calculateTransformationType() + val transformType = mediaHierarchyManager.calculateTransformationType() assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_FADE) } @Test fun testCloseGutsRelayToCarousel() { - mediaHiearchyManager.closeGuts() + mediaHierarchyManager.closeGuts() verify(mediaCarouselController).closeGuts() } @@ -281,7 +286,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { @Test fun getGuidedTransformationTranslationY_notInGuidedTransformation_returnsNegativeNumber() { - assertThat(mediaHiearchyManager.getGuidedTransformationTranslationY()).isLessThan(0) + assertThat(mediaHierarchyManager.getGuidedTransformationTranslationY()).isLessThan(0) } @Test @@ -289,7 +294,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { enterGuidedTransformation() val expectedTranslation = LOCKSCREEN_TOP - QS_TOP - assertThat(mediaHiearchyManager.getGuidedTransformationTranslationY()) + assertThat(mediaHierarchyManager.getGuidedTransformationTranslationY()) .isEqualTo(expectedTranslation) } @@ -301,7 +306,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { whenever(qsHost.visible).thenReturn(true) whenever(qqsHost.visible).thenReturn(true) - assertThat(mediaHiearchyManager.isCurrentlyInGuidedTransformation()).isTrue() + assertThat(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).isTrue() } @Test @@ -313,7 +318,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { whenever(qsHost.visible).thenReturn(true) whenever(qqsHost.visible).thenReturn(true) - assertThat(mediaHiearchyManager.isCurrentlyInGuidedTransformation()).isFalse() + assertThat(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).isFalse() } @Test @@ -324,7 +329,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { whenever(qsHost.visible).thenReturn(true) whenever(qqsHost.visible).thenReturn(true) - assertThat(mediaHiearchyManager.isCurrentlyInGuidedTransformation()).isFalse() + assertThat(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).isFalse() } private fun enableSplitShade() { @@ -336,9 +341,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { private fun goToLockscreen() { whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) - whenever(notificationLockscreenUserManager.shouldShowLockscreenNotifications()).thenReturn( - true - ) + settings.putInt(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, 1) statusBarCallback.value.onStatePreChange(StatusBarState.SHADE, StatusBarState.KEYGUARD) clearInvocations(mediaCarouselController) } @@ -352,13 +355,13 @@ class MediaHierarchyManagerTest : SysuiTestCase() { } private fun expandQS() { - mediaHiearchyManager.qsExpansion = 1.0f + mediaHierarchyManager.qsExpansion = 1.0f } private fun enterGuidedTransformation() { - mediaHiearchyManager.qsExpansion = 1.0f + mediaHierarchyManager.qsExpansion = 1.0f goToLockscreen() - mediaHiearchyManager.setTransitionToFullShadeAmount(123f) + mediaHierarchyManager.setTransitionToFullShadeAmount(123f) } companion object { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java index 247316a32473..c101b9ffd495 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java @@ -16,6 +16,8 @@ package com.android.systemui.media.dream; +import static com.android.systemui.flags.Flags.MEDIA_DREAM_COMPLICATION; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; @@ -28,6 +30,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.media.MediaData; import com.android.systemui.media.MediaDataManager; @@ -50,6 +53,9 @@ public class MediaDreamSentinelTest extends SysuiTestCase { @Mock MediaDreamComplication mComplication; + @Mock + FeatureFlags mFeatureFlags; + final String mKey = "key"; final String mOldKey = "old_key"; @@ -59,21 +65,18 @@ public class MediaDreamSentinelTest extends SysuiTestCase { @Before public void setup() { MockitoAnnotations.initMocks(this); + + when(mFeatureFlags.isEnabled(MEDIA_DREAM_COMPLICATION)).thenReturn(true); } @Test public void testComplicationAddition() { final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager, - mDreamOverlayStateController, mComplication); + mDreamOverlayStateController, mComplication, mFeatureFlags); sentinel.start(); - ArgumentCaptor<MediaDataManager.Listener> listenerCaptor = - ArgumentCaptor.forClass(MediaDataManager.Listener.class); - verify(mMediaDataManager).addListener(listenerCaptor.capture()); - - final MediaDataManager.Listener listener = listenerCaptor.getValue(); - + final MediaDataManager.Listener listener = captureMediaDataListener(); when(mMediaDataManager.hasActiveMedia()).thenReturn(false); listener.onMediaDataLoaded(mKey, mOldKey, mData, /* immediately= */ true, /* receivedSmartspaceCardLatency= */ 0, /* isSsReactived= */ false); @@ -92,4 +95,27 @@ public class MediaDreamSentinelTest extends SysuiTestCase { verify(mDreamOverlayStateController).removeComplication(eq(mComplication)); } + @Test + public void testMediaDreamSentinel_mediaComplicationDisabled_doNotAddComplication() { + when(mFeatureFlags.isEnabled(MEDIA_DREAM_COMPLICATION)).thenReturn(false); + + final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager, + mDreamOverlayStateController, mComplication, mFeatureFlags); + + sentinel.start(); + + final MediaDataManager.Listener listener = captureMediaDataListener(); + when(mMediaDataManager.hasActiveMedia()).thenReturn(true); + listener.onMediaDataLoaded(mKey, mOldKey, mData, /* immediately= */true, + /* receivedSmartspaceCardLatency= */0, /* isSsReactived= */ false); + verify(mDreamOverlayStateController, never()).addComplication(any()); + } + + private MediaDataManager.Listener captureMediaDataListener() { + final ArgumentCaptor<MediaDataManager.Listener> listenerCaptor = + ArgumentCaptor.forClass(MediaDataManager.Listener.class); + verify(mMediaDataManager).addListener(listenerCaptor.capture()); + + return listenerCaptor.getValue(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt index be14cc51ef96..cb4f08e6c552 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt @@ -93,6 +93,10 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { private lateinit var featureFlags: FeatureFlags @Mock private lateinit var insetsProvider: StatusBarContentInsetsProvider + @Mock + private lateinit var iconManagerFactory: StatusBarIconController.TintedIconManager.Factory + @Mock + private lateinit var iconManager: StatusBarIconController.TintedIconManager private val qsExpansionPathInterpolator = QSExpansionPathInterpolator() @@ -106,6 +110,7 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { `when`(qsCarrierGroupControllerBuilder.build()).thenReturn(qsCarrierGroupController) `when`(variableDateViewControllerFactory.create(any())) .thenReturn(variableDateViewController) + `when`(iconManagerFactory.create(any())).thenReturn(iconManager) `when`(view.resources).thenReturn(mContext.resources) `when`(view.isAttachedToWindow).thenReturn(true) `when`(view.context).thenReturn(context) @@ -122,7 +127,8 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { featureFlags, variableDateViewControllerFactory, batteryMeterViewController, - insetsProvider + insetsProvider, + iconManagerFactory, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeImageCapture.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeImageCapture.kt new file mode 100644 index 000000000000..447e28cd9527 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeImageCapture.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.screenshot + +import android.graphics.Bitmap +import android.graphics.Rect + +internal class FakeImageCapture : ImageCapture { + + var requestedDisplayId: Int? = null + var requestedDisplayCrop: Rect? = null + var requestedTaskId: Int? = null + + var image: Bitmap? = null + + override fun captureDisplay(displayId: Int, crop: Rect?): Bitmap? { + requestedDisplayId = displayId + requestedDisplayCrop = crop + return image + } + + override suspend fun captureTask(taskId: Int): Bitmap? { + requestedTaskId = taskId + return image + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScreenshotPolicy.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScreenshotPolicy.kt new file mode 100644 index 000000000000..28d53c72640f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScreenshotPolicy.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot + +import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo + +internal class FakeScreenshotPolicy : ScreenshotPolicy { + + private val userTypes = mutableMapOf<Int, Boolean>() + private val contentInfo = mutableMapOf<Int, DisplayContentInfo?>() + + fun setManagedProfile(userId: Int, managedUser: Boolean) { + userTypes[userId] = managedUser + } + override suspend fun isManagedProfile(userId: Int): Boolean { + return userTypes[userId] ?: error("No managedProfile value set for userId $userId") + } + + fun setDisplayContentInfo(userId: Int, contentInfo: DisplayContentInfo) { + this.contentInfo[userId] = contentInfo + } + + override suspend fun findPrimaryContent(displayId: Int): DisplayContentInfo { + return contentInfo[displayId] ?: error("No DisplayContentInfo set for displayId $displayId") + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt index ce3f20d4d39d..00f38081c5c9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt @@ -27,6 +27,8 @@ import android.view.SurfaceControl.ScreenshotHardwareBuffer import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers import org.junit.Test import org.junit.runner.RunWith @@ -37,7 +39,10 @@ import org.junit.runner.RunWith class ImageCaptureImplTest : SysuiTestCase() { private val displayManager = mock<DisplayManager>() private val atmService = mock<IActivityTaskManager>() - private val capture = TestableImageCaptureImpl(displayManager, atmService) + private val capture = TestableImageCaptureImpl( + displayManager, + atmService, + Dispatchers.Unconfined) @Test fun captureDisplayWithCrop() { @@ -59,9 +64,10 @@ class ImageCaptureImplTest : SysuiTestCase() { class TestableImageCaptureImpl( displayManager: DisplayManager, - atmService: IActivityTaskManager + atmService: IActivityTaskManager, + bgDispatcher: CoroutineDispatcher ) : - ImageCaptureImpl(displayManager, atmService) { + ImageCaptureImpl(displayManager, atmService, bgDispatcher) { var token: IBinder? = null var width: Int? = null @@ -81,4 +87,4 @@ class ImageCaptureImplTest : SysuiTestCase() { return ScreenshotHardwareBuffer(null, null, false, false) } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt index 024d3bd8eb0e..48fbd354b98d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt @@ -22,86 +22,253 @@ import android.graphics.ColorSpace import android.graphics.Insets import android.graphics.Rect import android.hardware.HardwareBuffer -import android.net.Uri +import android.os.Bundle import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN -import android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE - +import android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler +import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler.bundleToHardwareBitmap import com.android.internal.util.ScreenshotHelper.ScreenshotRequest -import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback -import com.android.systemui.util.mockito.argumentCaptor -import com.android.systemui.util.mockito.mock +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo import com.google.common.truth.Truth.assertThat -import java.util.function.Consumer +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking import org.junit.Test -import org.mockito.Mockito.eq -import org.mockito.Mockito.verify -import org.mockito.Mockito.isNull + +private const val USER_ID = 1 +private const val TASK_ID = 1 class RequestProcessorTest { - private val controller = mock<ScreenshotController>() - private val bitmapCaptor = argumentCaptor<Bitmap>() + private val imageCapture = FakeImageCapture() + private val component = ComponentName("android.test", "android.test.Component") + private val bounds = Rect(25, 25, 75, 75) + + private val scope = CoroutineScope(Dispatchers.Unconfined) + private val dispatcher = Dispatchers.Unconfined + private val policy = FakeScreenshotPolicy() + private val flags = FakeFeatureFlags() + + /** Tests the Java-compatible function wrapper, ensures callback is invoked. */ + @Test + fun testProcessAsync() { + flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false) + + val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD) + val processor = RequestProcessor(imageCapture, policy, flags, scope) + + var result: ScreenshotRequest? = null + var callbackCount = 0 + val callback: (ScreenshotRequest) -> Unit = { processedRequest: ScreenshotRequest -> + result = processedRequest + callbackCount++ + } + + // runs synchronously, using Unconfined Dispatcher + processor.processAsync(request, callback) + + // Callback invoked once returning the same request (no changes) + assertThat(callbackCount).isEqualTo(1) + assertThat(result).isEqualTo(request) + } + + @Test + fun testFullScreenshot_workProfilePolicyDisabled() = runBlocking { + flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false) + + val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD) + val processor = RequestProcessor(imageCapture, policy, flags, scope) + + val processedRequest = processor.process(request) + + // No changes + assertThat(processedRequest).isEqualTo(request) + } @Test - fun testFullScreenshot() { + fun testFullScreenshot() = runBlocking { + flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true) + + // Indicate that the primary content belongs to a normal user + policy.setManagedProfile(USER_ID, false) + policy.setDisplayContentInfo( + policy.getDefaultDisplayId(), + DisplayContentInfo(component, bounds, USER_ID, TASK_ID)) + val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD) - val onSavedListener = mock<Consumer<Uri>>() - val callback = mock<RequestCallback>() - val processor = RequestProcessor(controller) + val processor = RequestProcessor(imageCapture, policy, flags, scope) - processor.processRequest(request, onSavedListener, callback) + val processedRequest = processor.process(request) - verify(controller).takeScreenshotFullscreen(/* topComponent */ isNull(), - eq(onSavedListener), eq(callback)) + // Request has topComponent added, but otherwise unchanged. + assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_FULLSCREEN) + assertThat(processedRequest.topComponent).isEqualTo(component) } @Test - fun testSelectedRegionScreenshot() { + fun testFullScreenshot_managedProfile() = runBlocking { + flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true) + + // Provide a fake task bitmap when asked + val bitmap = makeHardwareBitmap(100, 100) + imageCapture.image = bitmap + + // Indicate that the primary content belongs to a manged profile + policy.setManagedProfile(USER_ID, true) + policy.setDisplayContentInfo(policy.getDefaultDisplayId(), + DisplayContentInfo(component, bounds, USER_ID, TASK_ID)) + + val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD) + val processor = RequestProcessor(imageCapture, policy, flags, scope) + + val processedRequest = processor.process(request) + + // Expect a task snapshot is taken, overriding the full screen mode + assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_PROVIDED_IMAGE) + assertThat(bitmap.equalsHardwareBitmapBundle(processedRequest.bitmapBundle)).isTrue() + assertThat(processedRequest.boundsInScreen).isEqualTo(bounds) + assertThat(processedRequest.insets).isEqualTo(Insets.NONE) + assertThat(processedRequest.taskId).isEqualTo(TASK_ID) + assertThat(imageCapture.requestedTaskId).isEqualTo(TASK_ID) + assertThat(processedRequest.userId).isEqualTo(USER_ID) + assertThat(processedRequest.topComponent).isEqualTo(component) + } + + @Test + fun testSelectedRegionScreenshot_workProfilePolicyDisabled() = runBlocking { + flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false) + + val request = ScreenshotRequest(TAKE_SCREENSHOT_SELECTED_REGION, SCREENSHOT_KEY_CHORD) + val processor = RequestProcessor(imageCapture, policy, flags, scope) + + val processedRequest = processor.process(request) + + // No changes + assertThat(processedRequest).isEqualTo(request) + } + + @Test + fun testSelectedRegionScreenshot() = runBlocking { + flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true) + val request = ScreenshotRequest(TAKE_SCREENSHOT_SELECTED_REGION, SCREENSHOT_KEY_CHORD) - val onSavedListener = mock<Consumer<Uri>>() - val callback = mock<RequestCallback>() - val processor = RequestProcessor(controller) + val processor = RequestProcessor(imageCapture, policy, flags, scope) - processor.processRequest(request, onSavedListener, callback) + policy.setManagedProfile(USER_ID, false) + policy.setDisplayContentInfo(policy.getDefaultDisplayId(), + DisplayContentInfo(component, bounds, USER_ID, TASK_ID)) - verify(controller).takeScreenshotPartial(/* topComponent */ isNull(), - eq(onSavedListener), eq(callback)) + val processedRequest = processor.process(request) + + // Request has topComponent added, but otherwise unchanged. + assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_FULLSCREEN) + assertThat(processedRequest.topComponent).isEqualTo(component) + } + + @Test + fun testSelectedRegionScreenshot_managedProfile() = runBlocking { + flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true) + + // Provide a fake task bitmap when asked + val bitmap = makeHardwareBitmap(100, 100) + imageCapture.image = bitmap + + val request = ScreenshotRequest(TAKE_SCREENSHOT_SELECTED_REGION, SCREENSHOT_KEY_CHORD) + val processor = RequestProcessor(imageCapture, policy, flags, scope) + + // Indicate that the primary content belongs to a manged profile + policy.setManagedProfile(USER_ID, true) + policy.setDisplayContentInfo(policy.getDefaultDisplayId(), + DisplayContentInfo(component, bounds, USER_ID, TASK_ID)) + + val processedRequest = processor.process(request) + + // Expect a task snapshot is taken, overriding the selected region mode + assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_PROVIDED_IMAGE) + assertThat(bitmap.equalsHardwareBitmapBundle(processedRequest.bitmapBundle)).isTrue() + assertThat(processedRequest.boundsInScreen).isEqualTo(bounds) + assertThat(processedRequest.insets).isEqualTo(Insets.NONE) + assertThat(processedRequest.taskId).isEqualTo(TASK_ID) + assertThat(imageCapture.requestedTaskId).isEqualTo(TASK_ID) + assertThat(processedRequest.userId).isEqualTo(USER_ID) + assertThat(processedRequest.topComponent).isEqualTo(component) } @Test - fun testProvidedImageScreenshot() { - val taskId = 1111 - val userId = 2222 + fun testProvidedImageScreenshot_workProfilePolicyDisabled() = runBlocking { + flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false) + val bounds = Rect(50, 50, 150, 150) - val topComponent = ComponentName("test", "test") - val processor = RequestProcessor(controller) + val processor = RequestProcessor(imageCapture, policy, flags, scope) - val buffer = HardwareBuffer.create(100, 100, HardwareBuffer.RGBA_8888, 1, - HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE) - val bitmap = Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!! + val bitmap = makeHardwareBitmap(100, 100) val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap) val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER, - bitmapBundle, bounds, Insets.NONE, taskId, userId, topComponent) + bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, component) - val onSavedListener = mock<Consumer<Uri>>() - val callback = mock<RequestCallback>() + val processedRequest = processor.process(request) - processor.processRequest(request, onSavedListener, callback) + // No changes + assertThat(processedRequest).isEqualTo(request) + } + + @Test + fun testProvidedImageScreenshot() = runBlocking { + flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true) - verify(controller).handleImageAsScreenshot( - bitmapCaptor.capture(), eq(bounds), eq(Insets.NONE), eq(taskId), eq(userId), - eq(topComponent), eq(onSavedListener), eq(callback) - ) + val bounds = Rect(50, 50, 150, 150) + val processor = RequestProcessor(imageCapture, policy, flags, scope) + + policy.setManagedProfile(USER_ID, false) + + val bitmap = makeHardwareBitmap(100, 100) + val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap) - assertThat(bitmapCaptor.value.equalsHardwareBitmap(bitmap)).isTrue() + val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER, + bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, component) + + val processedRequest = processor.process(request) + + // No changes + assertThat(processedRequest).isEqualTo(request) + } + + @Test + fun testProvidedImageScreenshot_managedProfile() = runBlocking { + flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true) + + val bounds = Rect(50, 50, 150, 150) + val processor = RequestProcessor(imageCapture, policy, flags, scope) + + // Indicate that the screenshot belongs to a manged profile + policy.setManagedProfile(USER_ID, true) + + val bitmap = makeHardwareBitmap(100, 100) + val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap) + + val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER, + bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, component) + + val processedRequest = processor.process(request) + + // Work profile, but already a task snapshot, so no changes + assertThat(processedRequest).isEqualTo(request) + } + + private fun makeHardwareBitmap(width: Int, height: Int): Bitmap { + val buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888, 1, + HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE) + return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!! } - private fun Bitmap.equalsHardwareBitmap(bitmap: Bitmap): Boolean { - return bitmap.hardwareBuffer == this.hardwareBuffer && - bitmap.colorSpace == this.colorSpace + private fun Bitmap.equalsHardwareBitmapBundle(bundle: Bundle): Boolean { + val provided = bundleToHardwareBitmap(bundle) + return provided.hardwareBuffer == this.hardwareBuffer && + provided.colorSpace == this.colorSpace } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt new file mode 100644 index 000000000000..83e56daf1fbc --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.screenshot + +import android.app.Application +import android.app.admin.DevicePolicyManager +import android.app.admin.DevicePolicyResources.Strings.SystemUi.SCREENSHOT_BLOCKED_BY_ADMIN +import android.app.admin.DevicePolicyResourcesManager +import android.content.ComponentName +import android.graphics.Bitmap +import android.graphics.Bitmap.Config.HARDWARE +import android.graphics.ColorSpace +import android.graphics.Insets +import android.graphics.Rect +import android.hardware.HardwareBuffer +import android.os.UserHandle +import android.os.UserManager +import android.testing.AndroidTestingRunner +import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD +import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW +import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN +import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE +import android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION +import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.internal.util.ScreenshotHelper +import com.android.internal.util.ScreenshotHelper.ScreenshotRequest +import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags.SCREENSHOT_REQUEST_PROCESSOR +import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_CHORD +import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_OVERVIEW +import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argThat +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.isNull +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyZeroInteractions +import org.mockito.Mockito.`when` as whenever + +private const val USER_ID = 1 +private const val TASK_ID = 1 + +@RunWith(AndroidTestingRunner::class) +class TakeScreenshotServiceTest : SysuiTestCase() { + + private val application = mock<Application>() + private val controller = mock<ScreenshotController>() + private val userManager = mock<UserManager>() + private val requestProcessor = mock<RequestProcessor>() + private val devicePolicyManager = mock<DevicePolicyManager>() + private val devicePolicyResourcesManager = mock<DevicePolicyResourcesManager>() + private val notificationsController = mock<ScreenshotNotificationsController>() + private val callback = mock<RequestCallback>() + + private val eventLogger = UiEventLoggerFake() + private val flags = FakeFeatureFlags() + private val topComponent = ComponentName(mContext, TakeScreenshotServiceTest::class.java) + + private val service = TakeScreenshotService( + controller, userManager, devicePolicyManager, eventLogger, + notificationsController, mContext, Runnable::run, flags, requestProcessor) + + @Before + fun setUp() { + whenever(devicePolicyManager.resources).thenReturn(devicePolicyResourcesManager) + whenever(devicePolicyManager.getScreenCaptureDisabled( + /* admin component (null: any admin) */ isNull(), eq(UserHandle.USER_ALL))) + .thenReturn(false) + whenever(userManager.isUserUnlocked).thenReturn(true) + + flags.set(SCREENSHOT_REQUEST_PROCESSOR, false) + + service.attach( + mContext, + /* thread = */ null, + /* className = */ null, + /* token = */ null, + application, + /* activityManager = */ null) + } + + @Test + fun testServiceLifecycle() { + service.onCreate() + service.onBind(null /* unused: Intent */) + + service.onUnbind(null /* unused: Intent */) + verify(controller).removeWindow() + + service.onDestroy() + verify(controller).onDestroy() + } + + @Test + fun takeScreenshotFullscreen() { + val request = ScreenshotRequest( + TAKE_SCREENSHOT_FULLSCREEN, + SCREENSHOT_KEY_CHORD, + topComponent) + + service.handleRequest(request, { /* onSaved */ }, callback) + + verify(controller).takeScreenshotFullscreen( + eq(topComponent), + /* onSavedListener = */ any(), + /* requestCallback = */ any()) + + assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1) + val logEvent = eventLogger.get(0) + + assertEquals("Expected SCREENSHOT_REQUESTED UiEvent", + logEvent.eventId, SCREENSHOT_REQUESTED_KEY_CHORD.id) + assertEquals("Expected supplied package name", + topComponent.packageName, eventLogger.get(0).packageName) + } + + @Test + fun takeScreenshotPartial() { + val request = ScreenshotRequest( + TAKE_SCREENSHOT_SELECTED_REGION, + SCREENSHOT_KEY_CHORD, + /* topComponent = */ null) + + service.handleRequest(request, { /* onSaved */ }, callback) + + verify(controller).takeScreenshotPartial( + /* topComponent = */ isNull(), + /* onSavedListener = */ any(), + /* requestCallback = */ any()) + + assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1) + val logEvent = eventLogger.get(0) + + assertEquals("Expected SCREENSHOT_REQUESTED UiEvent", + logEvent.eventId, SCREENSHOT_REQUESTED_KEY_CHORD.id) + assertEquals("Expected empty package name in UiEvent", "", eventLogger.get(0).packageName) + } + + @Test + fun takeScreenshotProvidedImage() { + val bounds = Rect(50, 50, 150, 150) + val bitmap = makeHardwareBitmap(100, 100) + val bitmapBundle = ScreenshotHelper.HardwareBitmapBundler.hardwareBitmapToBundle(bitmap) + + val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OVERVIEW, + bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, topComponent) + + service.handleRequest(request, { /* onSaved */ }, callback) + + verify(controller).handleImageAsScreenshot( + argThat { b -> b.equalsHardwareBitmap(bitmap) }, + eq(bounds), + eq(Insets.NONE), eq(TASK_ID), eq(USER_ID), eq(topComponent), + /* onSavedListener = */ any(), /* requestCallback = */ any()) + + assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1) + val logEvent = eventLogger.get(0) + + assertEquals("Expected SCREENSHOT_REQUESTED_* UiEvent", + logEvent.eventId, SCREENSHOT_REQUESTED_OVERVIEW.id) + assertEquals("Expected supplied package name", + topComponent.packageName, eventLogger.get(0).packageName) + } + + @Test + fun takeScreenshotFullscreen_userLocked() { + whenever(userManager.isUserUnlocked).thenReturn(false) + + val request = ScreenshotRequest( + TAKE_SCREENSHOT_FULLSCREEN, + SCREENSHOT_KEY_CHORD, + topComponent) + + service.handleRequest(request, { /* onSaved */ }, callback) + + verify(notificationsController).notifyScreenshotError(anyInt()) + verify(callback).reportError() + verifyZeroInteractions(controller) + } + + @Test + fun takeScreenshotFullscreen_screenCaptureDisabled_allUsers() { + whenever(devicePolicyManager.getScreenCaptureDisabled( + isNull(), eq(UserHandle.USER_ALL)) + ).thenReturn(true) + + whenever(devicePolicyResourcesManager.getString( + eq(SCREENSHOT_BLOCKED_BY_ADMIN), + /* Supplier<String> */ any(), + )).thenReturn("SCREENSHOT_BLOCKED_BY_ADMIN") + + val request = ScreenshotRequest( + TAKE_SCREENSHOT_FULLSCREEN, + SCREENSHOT_KEY_CHORD, + topComponent) + + service.handleRequest(request, { /* onSaved */ }, callback) + + // error shown: Toast.makeText(...).show(), untestable + verify(callback).reportError() + verifyZeroInteractions(controller) + } +} + +private fun Bitmap.equalsHardwareBitmap(other: Bitmap): Boolean { + return config == HARDWARE && + other.config == HARDWARE && + hardwareBuffer == other.hardwareBuffer && + colorSpace == other.colorSpace +} + +/** A hardware Bitmap is mandated by use of ScreenshotHelper.HardwareBitmapBundler */ +private fun makeHardwareBitmap(width: Int, height: Int): Bitmap { + val buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888, 1, + HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE) + return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!! +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt index ed1a13b36d6c..20c6d9adc300 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt @@ -91,6 +91,10 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() { @Mock private lateinit var statusBarIconController: StatusBarIconController @Mock + private lateinit var iconManagerFactory: StatusBarIconController.TintedIconManager.Factory + @Mock + private lateinit var iconManager: StatusBarIconController.TintedIconManager + @Mock private lateinit var qsCarrierGroupController: QSCarrierGroupController @Mock private lateinit var qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder @@ -169,6 +173,8 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() { } whenever(view.visibility).thenAnswer { _ -> viewVisibility } + whenever(iconManagerFactory.create(any())).thenReturn(iconManager) + whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(true) whenever(featureFlags.isEnabled(Flags.NEW_HEADER)).thenReturn(true) @@ -178,6 +184,7 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() { controller = LargeScreenShadeHeaderController( view, statusBarIconController, + iconManagerFactory, privacyIconsController, insetsProvider, configurationController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt index 02b26dbbc32d..eeb61bc8a0f8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt @@ -43,6 +43,8 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() { @Mock private lateinit var view: View @Mock private lateinit var statusIcons: StatusIconContainer @Mock private lateinit var statusBarIconController: StatusBarIconController + @Mock private lateinit var iconManagerFactory: StatusBarIconController.TintedIconManager.Factory + @Mock private lateinit var iconManager: StatusBarIconController.TintedIconManager @Mock private lateinit var qsCarrierGroupController: QSCarrierGroupController @Mock private lateinit var qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder @Mock private lateinit var featureFlags: FeatureFlags @@ -91,10 +93,12 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() { whenever(view.visibility).thenAnswer { _ -> viewVisibility } whenever(variableDateViewControllerFactory.create(any())) .thenReturn(variableDateViewController) + whenever(iconManagerFactory.create(any())).thenReturn(iconManager) whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(false) mLargeScreenShadeHeaderController = LargeScreenShadeHeaderController( view, statusBarIconController, + iconManagerFactory, privacyIconsController, insetsProvider, configurationController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index 30ab424b25d2..7d28871e340c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -47,7 +47,6 @@ import android.content.ContentResolver; import android.content.res.Configuration; import android.content.res.Resources; import android.database.ContentObserver; -import android.graphics.PointF; import android.os.Handler; import android.os.Looper; import android.os.PowerManager; @@ -238,6 +237,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Mock private DozeLog mDozeLog; @Mock + private ShadeLogger mShadeLog; + @Mock private CommandQueue mCommandQueue; @Mock private VibratorHelper mVibratorHelper; @@ -524,7 +525,9 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mNotificationShadeWindowController, mDozeLog, mDozeParameters, mCommandQueue, mVibratorHelper, mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor, - mMetricsLogger, mConfigurationController, + mMetricsLogger, + mShadeLog, + mConfigurationController, () -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager, mConversationNotificationManager, mMediaHiearchyManager, mStatusBarKeyguardViewManager, @@ -760,10 +763,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Test public void testSetDozing_notifiesNsslAndStateController() { - mNotificationPanelViewController.setDozing(true /* dozing */, false /* animate */, - null /* touch */); - verify(mNotificationStackScrollLayoutController) - .setDozing(eq(true), eq(false), eq(null)); + mNotificationPanelViewController.setDozing(true /* dozing */, false /* animate */); + verify(mNotificationStackScrollLayoutController).setDozing(eq(true), eq(false)); assertThat(mStatusBarStateController.getDozeAmount()).isEqualTo(1f); } @@ -1216,7 +1217,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); clearInvocations(mKeyguardStatusViewController); - mNotificationPanelViewController.setDozing(/* dozing= */ true, /* animate= */ false, null); + mNotificationPanelViewController.setDozing(/* dozing= */ true, /* animate= */ false); verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate= */ true); } @@ -1230,7 +1231,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); clearInvocations(mKeyguardStatusViewController); - mNotificationPanelViewController.setDozing(/* dozing= */ true, /* animate= */ false, null); + mNotificationPanelViewController.setDozing(/* dozing= */ true, /* animate= */ false); verify(mKeyguardStatusViewController).displayClock(SMALL, /* animate= */ true); } @@ -1244,7 +1245,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { when(mMediaDataManager.hasActiveMedia()).thenReturn(true); when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); - mNotificationPanelViewController.setDozing(true, false, null); + mNotificationPanelViewController.setDozing(true, false); verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ false); } @@ -1558,8 +1559,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { when(mDozeParameters.getAlwaysOn()).thenReturn(dozingAlwaysOn); mNotificationPanelViewController.setDozing( /* dozing= */ dozing, - /* animate= */ false, - /* wakeUpTouchLocation= */ new PointF() + /* animate= */ false ); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt new file mode 100644 index 000000000000..9393a4f4eead --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.shared.rotation + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import android.view.Display +import android.view.WindowInsetsController +import android.view.WindowManagerPolicyConstants +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidTestingRunner::class) +@SmallTest +@RunWithLooper +class RotationButtonControllerTest : SysuiTestCase() { + + private lateinit var mController: RotationButtonController + + @Before + fun setUp() { + mController = RotationButtonController( + mContext, + /* lightIconColor = */ 0, + /* darkIconColor = */ 0, + /* iconCcwStart0ResId = */ 0, + /* iconCcwStart90ResId = */ 0, + /* iconCwStart0ResId = */ 0, + /* iconCwStart90ResId = */ 0 + ) { 0 } + } + + @Test + fun ifGestural_showRotationSuggestion() { + mController.onNavigationBarWindowVisibilityChange( /* showing = */ false) + mController.onBehaviorChanged(Display.DEFAULT_DISPLAY, + WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE) + mController.onNavigationModeChanged(WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON) + mController.onTaskbarStateChange( /* visible = */ false, /* stashed = */ false) + assertThat(mController.canShowRotationButton()).isFalse() + + mController.onNavigationModeChanged(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL) + + assertThat(mController.canShowRotationButton()).isTrue() + } + + @Test + fun ifTaskbarVisible_showRotationSuggestion() { + mController.onNavigationBarWindowVisibilityChange( /* showing = */ false) + mController.onBehaviorChanged(Display.DEFAULT_DISPLAY, + WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE) + mController.onNavigationModeChanged(WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON) + mController.onTaskbarStateChange( /* visible = */ false, /* stashed = */ false) + assertThat(mController.canShowRotationButton()).isFalse() + + mController.onTaskbarStateChange( /* visible = */ true, /* stashed = */ false) + + assertThat(mController.canShowRotationButton()).isTrue() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt index be631afdd1a9..1d8e5dec5c50 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt @@ -38,8 +38,8 @@ import org.mockito.ArgumentMatchers.eq import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidTestingRunner::class) @@ -89,8 +89,8 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { val listener = mock(StatusBarStateController.StateListener::class.java) controller.addCallback(listener) - controller.setDozeAmount(0.5f, false /* animated */) - controller.setDozeAmount(0.5f, false /* animated */) + controller.setAndInstrumentDozeAmount(null, 0.5f, false /* animated */) + controller.setAndInstrumentDozeAmount(null, 0.5f, false /* animated */) verify(listener).onDozeAmountChanged(eq(0.5f), anyFloat()) } @@ -135,7 +135,7 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { @Test fun testSetDozeAmount_immediatelyChangesDozeAmount_lockscreenTransitionFromAod() { // Put controller in AOD state - controller.setDozeAmount(1f, false) + controller.setAndInstrumentDozeAmount(null, 1f, false) // When waking from doze, CentralSurfaces#updateDozingState will update the dozing state // before the doze amount changes diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt index 20747a05a360..790865bb496f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt @@ -56,6 +56,7 @@ import com.android.systemui.util.mockito.eq import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.time.FakeSystemClock import java.util.Optional +import java.util.concurrent.Executor import org.junit.Before import org.junit.Test import org.mockito.ArgumentCaptor @@ -105,6 +106,9 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { private lateinit var deviceProvisionedController: DeviceProvisionedController @Mock + private lateinit var bgExecutor: Executor + + @Mock private lateinit var handler: Handler @Mock @@ -203,6 +207,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { keyguardBypassController, execution, executor, + bgExecutor, handler, Optional.of(plugin) ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index f8b39e8cff71..137842ef314f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -28,7 +28,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -58,8 +57,8 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.AboveShelfChangedListener; import com.android.systemui.statusbar.notification.FeedbackIcon; -import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener; import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer; import org.junit.Assert; @@ -260,17 +259,6 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test - public void setNeedsRedactionFreesViewWhenFalse() throws Exception { - ExpandableNotificationRow row = mNotificationTestHelper.createRow(FLAG_CONTENT_VIEW_ALL); - row.setNeedsRedaction(true); - row.getPublicLayout().setVisibility(View.GONE); - - row.setNeedsRedaction(false); - TestableLooper.get(this).processAllMessages(); - assertNull(row.getPublicLayout().getContractedChild()); - } - - @Test public void testAboveShelfChangedListenerCalled() throws Exception { ExpandableNotificationRow row = mNotificationTestHelper.createRow(); AboveShelfChangedListener listener = mock(AboveShelfChangedListener.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java index 11e502fc79bf..6cf1a12d94f0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java @@ -47,7 +47,6 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.battery.BatteryMeterViewController; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.statusbar.SysuiStatusBarStateController; @@ -89,7 +88,9 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { @Mock private StatusBarIconController mStatusBarIconController; @Mock - private FeatureFlags mFeatureFlags; + private StatusBarIconController.TintedIconManager.Factory mIconManagerFactory; + @Mock + private StatusBarIconController.TintedIconManager mIconManager; @Mock private BatteryMeterViewController mBatteryMeterViewController; @Mock @@ -129,6 +130,8 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); + when(mIconManagerFactory.create(any())).thenReturn(mIconManager); + allowTestableLooperAsMainThread(); TestableLooper.get(this).runWithLooper(() -> { mKeyguardStatusBarView = @@ -148,7 +151,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { mBatteryController, mUserInfoController, mStatusBarIconController, - new StatusBarIconController.TintedIconManager.Factory(mFeatureFlags), + mIconManagerFactory, mBatteryMeterViewController, mNotificationPanelViewStateProvider, mKeyguardStateController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java index 0f1c40bacb7b..a6b7e5103c78 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java @@ -40,12 +40,16 @@ import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconMana import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState; +import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags; +import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel; import com.android.systemui.utils.leaks.LeakCheckedTest; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import javax.inject.Provider; + @RunWith(AndroidTestingRunner.class) @RunWithLooper @SmallTest @@ -67,7 +71,11 @@ public class StatusBarIconControllerTest extends LeakCheckedTest { @Test public void testSetCalledOnAdd_DarkIconManager() { LinearLayout layout = new LinearLayout(mContext); - TestDarkIconManager manager = new TestDarkIconManager(layout, mock(FeatureFlags.class)); + TestDarkIconManager manager = new TestDarkIconManager( + layout, + mock(FeatureFlags.class), + mock(StatusBarPipelineFlags.class), + () -> mock(WifiViewModel.class)); testCallOnAdd_forManager(manager); } @@ -104,8 +112,12 @@ public class StatusBarIconControllerTest extends LeakCheckedTest { private static class TestDarkIconManager extends DarkIconManager implements TestableIconManager { - TestDarkIconManager(LinearLayout group, FeatureFlags featureFlags) { - super(group, featureFlags); + TestDarkIconManager( + LinearLayout group, + FeatureFlags featureFlags, + StatusBarPipelineFlags statusBarPipelineFlags, + Provider<WifiViewModel> wifiViewModelProvider) { + super(group, featureFlags, statusBarPipelineFlags, wifiViewModelProvider); } @Override @@ -123,7 +135,7 @@ public class StatusBarIconControllerTest extends LeakCheckedTest { } @Override - protected StatusBarWifiView addSignalIcon(int index, String slot, WifiIconState state) { + protected StatusBarWifiView addWifiIcon(int index, String slot, WifiIconState state) { StatusBarWifiView mock = mock(StatusBarWifiView.class); mGroup.addView(mock, index); return mock; @@ -140,7 +152,10 @@ public class StatusBarIconControllerTest extends LeakCheckedTest { private static class TestIconManager extends IconManager implements TestableIconManager { TestIconManager(ViewGroup group) { - super(group, mock(FeatureFlags.class)); + super(group, + mock(FeatureFlags.class), + mock(StatusBarPipelineFlags.class), + () -> mock(WifiViewModel.class)); } @Override @@ -158,7 +173,7 @@ public class StatusBarIconControllerTest extends LeakCheckedTest { } @Override - protected StatusBarWifiView addSignalIcon(int index, String slot, WifiIconState state) { + protected StatusBarWifiView addWifiIcon(int index, String slot, WifiIconState state) { StatusBarWifiView mock = mock(StatusBarWifiView.class); mGroup.addView(mock, index); return mock; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index de43a1fabab6..2b805089a430 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java @@ -37,7 +37,6 @@ import android.app.KeyguardManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; -import android.content.Intent; import android.os.Handler; import android.os.RemoteException; import android.os.UserHandle; @@ -135,8 +134,6 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { @Mock private PendingIntent mContentIntent; @Mock - private Intent mContentIntentInner; - @Mock private OnUserInteractionCallback mOnUserInteractionCallback; @Mock private Runnable mFutureDismissalRunnable; @@ -159,7 +156,6 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); when(mContentIntent.isActivity()).thenReturn(true); when(mContentIntent.getCreatorUserHandle()).thenReturn(UserHandle.of(1)); - when(mContentIntent.getIntent()).thenReturn(mContentIntentInner); NotificationTestHelper notificationTestHelper = new NotificationTestHelper( mContext, @@ -374,7 +370,6 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { eq(entry.getKey()), any(NotificationVisibility.class)); // The content intent should NOT be sent on click. - verify(mContentIntent).getIntent(); verify(mContentIntent).isActivity(); verifyNoMoreInteractions(mContentIntent); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index 52a573f24afb..20bf50e41f49 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -113,6 +113,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Mock private NotificationPanelViewController mNotificationPanelViewController; @Mock + private StatusBarIconController.DarkIconManager.Factory mIconManagerFactory; + @Mock + private StatusBarIconController.DarkIconManager mIconManager; + @Mock private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; @Mock private DumpManager mDumpManager; @@ -463,6 +467,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { mOperatorNameViewControllerFactory = mock(OperatorNameViewController.Factory.class); when(mOperatorNameViewControllerFactory.create(any())) .thenReturn(mOperatorNameViewController); + when(mIconManagerFactory.create(any())).thenReturn(mIconManager); mSecureSettings = mock(SecureSettings.class); setUpNotificationIconAreaController(); @@ -475,6 +480,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { new PanelExpansionStateManager(), mock(FeatureFlags.class), mStatusBarIconController, + mIconManagerFactory, mStatusBarHideIconsForBouncerManager, mKeyguardStateController, mNotificationPanelViewController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt deleted file mode 100644 index 515a7c936f4d..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.pipeline - -import android.net.NetworkCapabilities -import android.testing.AndroidTestingRunner -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.statusbar.pipeline.repository.NetworkCapabilityInfo -import com.android.systemui.util.mockito.mock -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.CoroutineStart -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.InternalCoroutinesApi -import kotlinx.coroutines.cancel -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.yield -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mockito.`when` as whenever - -@OptIn(InternalCoroutinesApi::class) -@SmallTest -@RunWith(AndroidTestingRunner::class) -class ConnectivityInfoProcessorTest : SysuiTestCase() { - - private val statusBarPipelineFlags = mock<StatusBarPipelineFlags>() - - @Before - fun setUp() { - whenever(statusBarPipelineFlags.isNewPipelineEnabled()).thenReturn(true) - } - - @Test - fun collectorInfoUpdated_processedInfoAlsoUpdated() = runBlocking { - // GIVEN a processor hooked up to a collector - val scope = CoroutineScope(Dispatchers.Unconfined) - val collector = FakeConnectivityInfoCollector() - val processor = ConnectivityInfoProcessor( - collector, - context, - scope, - statusBarPipelineFlags, - ) - - var mostRecentValue: ProcessedConnectivityInfo? = null - val job = launch(start = CoroutineStart.UNDISPATCHED) { - processor.processedInfoFlow.collect { - mostRecentValue = it - } - } - - // WHEN the collector emits a value - val networkCapabilityInfo = mapOf( - 10 to NetworkCapabilityInfo(mock(), NetworkCapabilities.Builder().build()) - ) - collector.emitValue(RawConnectivityInfo(networkCapabilityInfo)) - // Because our job uses [CoroutineStart.UNDISPATCHED], it executes in the same thread as - // this test. So, our test needs to yield to let the job run. - // Note: Once we upgrade our Kotlin coroutines testing library, we won't need this. - yield() - - // THEN the processor receives it - assertThat(mostRecentValue?.networkCapabilityInfo).isEqualTo(networkCapabilityInfo) - - job.cancel() - scope.cancel() - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepoTest.kt deleted file mode 100644 index 40f8fbfd9af2..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepoTest.kt +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.pipeline.repository - -import android.net.ConnectivityManager -import android.net.ConnectivityManager.NetworkCallback -import android.net.Network -import android.net.NetworkCapabilities -import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED -import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED -import android.net.NetworkCapabilities.TRANSPORT_CELLULAR -import android.net.NetworkCapabilities.TRANSPORT_WIFI -import android.net.NetworkRequest -import android.test.suitebuilder.annotation.SmallTest -import android.testing.AndroidTestingRunner -import com.android.systemui.SysuiTestCase -import com.android.systemui.statusbar.pipeline.ConnectivityPipelineLogger -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.withArgCaptor -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.CoroutineStart -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.cancel -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.any -import org.mockito.Mock -import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever -import org.mockito.MockitoAnnotations - -// TODO(b/240619365): Update this test to use `runTest` when we update the testing library -@SmallTest -@RunWith(AndroidTestingRunner::class) -class NetworkCapabilitiesRepoTest : SysuiTestCase() { - @Mock private lateinit var connectivityManager: ConnectivityManager - @Mock private lateinit var logger: ConnectivityPipelineLogger - - @Before - fun setup() { - MockitoAnnotations.initMocks(this) - } - - @Test - fun testOnCapabilitiesChanged_oneNewNetwork_networkStored() = runBlocking { - // GIVEN a repo hooked up to [ConnectivityManager] - val scope = CoroutineScope(Dispatchers.Unconfined) - val repo = NetworkCapabilitiesRepo( - connectivityManager = connectivityManager, - scope = scope, - logger = logger, - ) - - val job = launch(start = CoroutineStart.UNDISPATCHED) { - repo.dataStream.collect { - } - } - - val callback: NetworkCallback = withArgCaptor { - verify(connectivityManager) - .registerNetworkCallback(any(NetworkRequest::class.java), capture()) - } - - // WHEN a new network is added - callback.onCapabilitiesChanged(NET_1, NET_1_CAPS) - - val currentMap = repo.dataStream.value - - // THEN it is emitted from the flow - assertThat(currentMap[NET_1_ID]?.network).isEqualTo(NET_1) - assertThat(currentMap[NET_1_ID]?.capabilities).isEqualTo(NET_1_CAPS) - - job.cancel() - scope.cancel() - } - - @Test - fun testOnCapabilitiesChanged_twoNewNetworks_bothStored() = runBlocking { - // GIVEN a repo hooked up to [ConnectivityManager] - val scope = CoroutineScope(Dispatchers.Unconfined) - val repo = NetworkCapabilitiesRepo( - connectivityManager = connectivityManager, - scope = scope, - logger = logger, - ) - - val job = launch(start = CoroutineStart.UNDISPATCHED) { - repo.dataStream.collect { - } - } - - val callback: NetworkCallback = withArgCaptor { - verify(connectivityManager) - .registerNetworkCallback(any(NetworkRequest::class.java), capture()) - } - - // WHEN two new networks are added - callback.onCapabilitiesChanged(NET_1, NET_1_CAPS) - callback.onCapabilitiesChanged(NET_2, NET_2_CAPS) - - val currentMap = repo.dataStream.value - - // THEN the current state of the flow reflects 2 networks - assertThat(currentMap[NET_1_ID]?.network).isEqualTo(NET_1) - assertThat(currentMap[NET_1_ID]?.capabilities).isEqualTo(NET_1_CAPS) - assertThat(currentMap[NET_2_ID]?.network).isEqualTo(NET_2) - assertThat(currentMap[NET_2_ID]?.capabilities).isEqualTo(NET_2_CAPS) - - job.cancel() - scope.cancel() - } - - @Test - fun testOnCapabilitesChanged_newCapabilitiesForExistingNetwork_areCaptured() = runBlocking { - // GIVEN a repo hooked up to [ConnectivityManager] - val scope = CoroutineScope(Dispatchers.Unconfined) - val repo = NetworkCapabilitiesRepo( - connectivityManager = connectivityManager, - scope = scope, - logger = logger, - ) - - val job = launch(start = CoroutineStart.UNDISPATCHED) { - repo.dataStream.collect { - } - } - - val callback: NetworkCallback = withArgCaptor { - verify(connectivityManager) - .registerNetworkCallback(any(NetworkRequest::class.java), capture()) - } - - // WHEN a network is added, and then its capabilities are changed - callback.onCapabilitiesChanged(NET_1, NET_1_CAPS) - callback.onCapabilitiesChanged(NET_1, NET_2_CAPS) - - val currentMap = repo.dataStream.value - - // THEN the current state of the flow reflects the new capabilities - assertThat(currentMap[NET_1_ID]?.capabilities).isEqualTo(NET_2_CAPS) - - job.cancel() - scope.cancel() - } - - @Test - fun testOnLost_networkIsRemoved() = runBlocking { - // GIVEN a repo hooked up to [ConnectivityManager] - val scope = CoroutineScope(Dispatchers.Unconfined) - val repo = NetworkCapabilitiesRepo( - connectivityManager = connectivityManager, - scope = scope, - logger = logger, - ) - - val job = launch(start = CoroutineStart.UNDISPATCHED) { - repo.dataStream.collect { - } - } - - val callback: NetworkCallback = withArgCaptor { - verify(connectivityManager) - .registerNetworkCallback(any(NetworkRequest::class.java), capture()) - } - - // WHEN two new networks are added, and one is removed - callback.onCapabilitiesChanged(NET_1, NET_1_CAPS) - callback.onCapabilitiesChanged(NET_2, NET_2_CAPS) - callback.onLost(NET_1) - - val currentMap = repo.dataStream.value - - // THEN the current state of the flow reflects only the remaining network - assertThat(currentMap[NET_1_ID]).isNull() - assertThat(currentMap[NET_2_ID]?.network).isEqualTo(NET_2) - assertThat(currentMap[NET_2_ID]?.capabilities).isEqualTo(NET_2_CAPS) - - job.cancel() - scope.cancel() - } - - @Test - fun testOnLost_noNetworks_doesNotCrash() = runBlocking { - // GIVEN a repo hooked up to [ConnectivityManager] - val scope = CoroutineScope(Dispatchers.Unconfined) - val repo = NetworkCapabilitiesRepo( - connectivityManager = connectivityManager, - scope = scope, - logger = logger, - ) - - val job = launch(start = CoroutineStart.UNDISPATCHED) { - repo.dataStream.collect { - } - } - - val callback: NetworkCallback = withArgCaptor { - verify(connectivityManager) - .registerNetworkCallback(any(NetworkRequest::class.java), capture()) - } - - // WHEN no networks are added, and one is removed - callback.onLost(NET_1) - - val currentMap = repo.dataStream.value - - // THEN the current state of the flow shows no networks - assertThat(currentMap).isEmpty() - - job.cancel() - scope.cancel() - } - - private val NET_1_ID = 100 - private val NET_1 = mock<Network>().also { - whenever(it.getNetId()).thenReturn(NET_1_ID) - } - private val NET_2_ID = 200 - private val NET_2 = mock<Network>().also { - whenever(it.getNetId()).thenReturn(NET_2_ID) - } - - private val NET_1_CAPS = NetworkCapabilities.Builder() - .addTransportType(TRANSPORT_CELLULAR) - .addCapability(NET_CAPABILITY_VALIDATED) - .build() - - private val NET_2_CAPS = NetworkCapabilities.Builder() - .addTransportType(TRANSPORT_WIFI) - .addCapability(NET_CAPABILITY_NOT_METERED) - .addCapability(NET_CAPABILITY_VALIDATED) - .build() -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt index 2915ae8dea88..36be1be309d6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.pipeline +package com.android.systemui.statusbar.pipeline.shared import android.net.Network import android.net.NetworkCapabilities diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt new file mode 100644 index 000000000000..6b8d4aa7c51f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.wifi.data.repository + +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow + +/** Fake implementation of [WifiRepository] exposing set methods for all the flows. */ +class FakeWifiRepository : WifiRepository { + private val _wifiNetwork: MutableStateFlow<WifiNetworkModel> = + MutableStateFlow(WifiNetworkModel.Inactive) + override val wifiNetwork: Flow<WifiNetworkModel> = _wifiNetwork + + private val _wifiActivity = MutableStateFlow(ACTIVITY_DEFAULT) + override val wifiActivity: Flow<WifiActivityModel> = _wifiActivity + + fun setWifiNetwork(wifiNetworkModel: WifiNetworkModel) { + _wifiNetwork.value = wifiNetworkModel + } + + fun setWifiActivity(activity: WifiActivityModel) { + _wifiActivity.value = activity + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt new file mode 100644 index 000000000000..d0a38084af76 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt @@ -0,0 +1,523 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.wifi.data.repository + +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkCapabilities.TRANSPORT_CELLULAR +import android.net.NetworkCapabilities.TRANSPORT_WIFI +import android.net.vcn.VcnTransportInfo +import android.net.wifi.WifiInfo +import android.net.wifi.WifiManager +import android.net.wifi.WifiManager.TrafficStateCallback +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import java.util.concurrent.Executor +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.runBlocking +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +class WifiRepositoryImplTest : SysuiTestCase() { + + private lateinit var underTest: WifiRepositoryImpl + + @Mock private lateinit var logger: ConnectivityPipelineLogger + @Mock private lateinit var connectivityManager: ConnectivityManager + @Mock private lateinit var wifiManager: WifiManager + private lateinit var executor: Executor + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + executor = FakeExecutor(FakeSystemClock()) + + underTest = WifiRepositoryImpl( + connectivityManager, + wifiManager, + executor, + logger, + ) + } + + @Test + fun wifiNetwork_initiallyGetsDefault() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + assertThat(latest).isEqualTo(WIFI_NETWORK_DEFAULT) + + job.cancel() + } + + @Test + fun wifiNetwork_primaryWifiNetworkAdded_flowHasNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + val wifiInfo = mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(SSID) + whenever(this.isPrimary).thenReturn(true) + } + val network = mock<Network>().apply { + whenever(this.getNetId()).thenReturn(NETWORK_ID) + } + + getNetworkCallback().onCapabilitiesChanged(network, createWifiNetworkCapabilities(wifiInfo)) + + assertThat(latest is WifiNetworkModel.Active).isTrue() + val latestActive = latest as WifiNetworkModel.Active + assertThat(latestActive.networkId).isEqualTo(NETWORK_ID) + assertThat(latestActive.ssid).isEqualTo(SSID) + + job.cancel() + } + + @Test + fun wifiNetwork_nonPrimaryWifiNetworkAdded_flowHasNoNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + val wifiInfo = mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(SSID) + whenever(this.isPrimary).thenReturn(false) + } + + getNetworkCallback().onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo)) + + assertThat(latest is WifiNetworkModel.Inactive).isTrue() + + job.cancel() + } + + @Test + fun wifiNetwork_cellularVcnNetworkAdded_flowHasNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + val capabilities = mock<NetworkCapabilities>().apply { + whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO)) + } + + getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + + assertThat(latest is WifiNetworkModel.Active).isTrue() + val latestActive = latest as WifiNetworkModel.Active + assertThat(latestActive.networkId).isEqualTo(NETWORK_ID) + assertThat(latestActive.ssid).isEqualTo(SSID) + + job.cancel() + } + + @Test + fun wifiNetwork_nonPrimaryCellularVcnNetworkAdded_flowHasNoNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + val wifiInfo = mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(SSID) + whenever(this.isPrimary).thenReturn(false) + } + val capabilities = mock<NetworkCapabilities>().apply { + whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(this.transportInfo).thenReturn(VcnTransportInfo(wifiInfo)) + } + + getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + + assertThat(latest is WifiNetworkModel.Inactive).isTrue() + + job.cancel() + } + + @Test + fun wifiNetwork_cellularNotVcnNetworkAdded_flowHasNoNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + val capabilities = mock<NetworkCapabilities>().apply { + whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(this.transportInfo).thenReturn(mock()) + } + + getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + + assertThat(latest is WifiNetworkModel.Inactive).isTrue() + + job.cancel() + } + + @Test + fun wifiNetwork_newPrimaryWifiNetwork_flowHasNewNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + // Start with the original network + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) + + // WHEN we update to a new primary network + val newNetworkId = 456 + val newNetwork = mock<Network>().apply { + whenever(this.getNetId()).thenReturn(newNetworkId) + } + val newSsid = "CD" + val newWifiInfo = mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(newSsid) + whenever(this.isPrimary).thenReturn(true) + } + + getNetworkCallback().onCapabilitiesChanged( + newNetwork, createWifiNetworkCapabilities(newWifiInfo) + ) + + // THEN we use the new network + assertThat(latest is WifiNetworkModel.Active).isTrue() + val latestActive = latest as WifiNetworkModel.Active + assertThat(latestActive.networkId).isEqualTo(newNetworkId) + assertThat(latestActive.ssid).isEqualTo(newSsid) + + job.cancel() + } + + @Test + fun wifiNetwork_newNonPrimaryWifiNetwork_flowHasOldNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + // Start with the original network + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) + + // WHEN we notify of a new but non-primary network + val newNetworkId = 456 + val newNetwork = mock<Network>().apply { + whenever(this.getNetId()).thenReturn(newNetworkId) + } + val newSsid = "EF" + val newWifiInfo = mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(newSsid) + whenever(this.isPrimary).thenReturn(false) + } + + getNetworkCallback().onCapabilitiesChanged( + newNetwork, createWifiNetworkCapabilities(newWifiInfo) + ) + + // THEN we still use the original network + assertThat(latest is WifiNetworkModel.Active).isTrue() + val latestActive = latest as WifiNetworkModel.Active + assertThat(latestActive.networkId).isEqualTo(NETWORK_ID) + assertThat(latestActive.ssid).isEqualTo(SSID) + + job.cancel() + } + + @Test + fun wifiNetwork_newNetworkCapabilities_flowHasNewData() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + val wifiInfo = mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(SSID) + whenever(this.isPrimary).thenReturn(true) + } + + // Start with the original network + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo)) + + // WHEN we keep the same network ID but change the SSID + val newSsid = "CD" + val newWifiInfo = mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(newSsid) + whenever(this.isPrimary).thenReturn(true) + } + + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(newWifiInfo)) + + // THEN we've updated to the new SSID + assertThat(latest is WifiNetworkModel.Active).isTrue() + val latestActive = latest as WifiNetworkModel.Active + assertThat(latestActive.networkId).isEqualTo(NETWORK_ID) + assertThat(latestActive.ssid).isEqualTo(newSsid) + + job.cancel() + } + + @Test + fun wifiNetwork_noCurrentNetwork_networkLost_flowHasNoNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + // WHEN we receive #onLost without any #onCapabilitiesChanged beforehand + getNetworkCallback().onLost(NETWORK) + + // THEN there's no crash and we still have no network + assertThat(latest is WifiNetworkModel.Inactive).isTrue() + + job.cancel() + } + + @Test + fun wifiNetwork_currentNetworkLost_flowHasNoNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) + assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID) + + // WHEN we lose our current network + getNetworkCallback().onLost(NETWORK) + + // THEN we update to no network + assertThat(latest is WifiNetworkModel.Inactive).isTrue() + + job.cancel() + } + + @Test + fun wifiNetwork_unknownNetworkLost_flowHasPreviousNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) + assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID) + + // WHEN we lose an unknown network + val unknownNetwork = mock<Network>().apply { + whenever(this.getNetId()).thenReturn(543) + } + getNetworkCallback().onLost(unknownNetwork) + + // THEN we still have our previous network + assertThat(latest is WifiNetworkModel.Active).isTrue() + val latestActive = latest as WifiNetworkModel.Active + assertThat(latestActive.networkId).isEqualTo(NETWORK_ID) + assertThat(latestActive.ssid).isEqualTo(SSID) + + job.cancel() + } + + @Test + fun wifiNetwork_notCurrentNetworkLost_flowHasCurrentNetwork() = runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest + .wifiNetwork + .onEach { latest = it } + .launchIn(this) + + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) + assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID) + + // WHEN we update to a new network... + val newNetworkId = 89 + val newNetwork = mock<Network>().apply { + whenever(this.getNetId()).thenReturn(newNetworkId) + } + getNetworkCallback().onCapabilitiesChanged( + newNetwork, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO) + ) + // ...and lose the old network + getNetworkCallback().onLost(NETWORK) + + // THEN we still have the new network + assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(newNetworkId) + + job.cancel() + } + + @Test + fun wifiActivity_nullWifiManager_receivesDefault() = runBlocking(IMMEDIATE) { + underTest = WifiRepositoryImpl( + connectivityManager, + wifiManager = null, + executor, + logger, + ) + + var latest: WifiActivityModel? = null + val job = underTest + .wifiActivity + .onEach { latest = it } + .launchIn(this) + + assertThat(latest).isEqualTo(ACTIVITY_DEFAULT) + + job.cancel() + } + + @Test + fun wifiActivity_callbackGivesNone_activityFlowHasNone() = runBlocking(IMMEDIATE) { + var latest: WifiActivityModel? = null + val job = underTest + .wifiActivity + .onEach { latest = it } + .launchIn(this) + + getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_NONE) + + assertThat(latest).isEqualTo( + WifiActivityModel(hasActivityIn = false, hasActivityOut = false) + ) + + job.cancel() + } + + @Test + fun wifiActivity_callbackGivesIn_activityFlowHasIn() = runBlocking(IMMEDIATE) { + var latest: WifiActivityModel? = null + val job = underTest + .wifiActivity + .onEach { latest = it } + .launchIn(this) + + getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_IN) + + assertThat(latest).isEqualTo( + WifiActivityModel(hasActivityIn = true, hasActivityOut = false) + ) + + job.cancel() + } + + @Test + fun wifiActivity_callbackGivesOut_activityFlowHasOut() = runBlocking(IMMEDIATE) { + var latest: WifiActivityModel? = null + val job = underTest + .wifiActivity + .onEach { latest = it } + .launchIn(this) + + getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_OUT) + + assertThat(latest).isEqualTo( + WifiActivityModel(hasActivityIn = false, hasActivityOut = true) + ) + + job.cancel() + } + + @Test + fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() = runBlocking(IMMEDIATE) { + var latest: WifiActivityModel? = null + val job = underTest + .wifiActivity + .onEach { latest = it } + .launchIn(this) + + getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_INOUT) + + assertThat(latest).isEqualTo(WifiActivityModel(hasActivityIn = true, hasActivityOut = true)) + + job.cancel() + } + + private fun getTrafficStateCallback(): TrafficStateCallback { + val callbackCaptor = argumentCaptor<TrafficStateCallback>() + verify(wifiManager).registerTrafficStateCallback(any(), callbackCaptor.capture()) + return callbackCaptor.value!! + } + + private fun getNetworkCallback(): ConnectivityManager.NetworkCallback { + val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>() + verify(connectivityManager).registerNetworkCallback(any(), callbackCaptor.capture()) + return callbackCaptor.value!! + } + + private fun createWifiNetworkCapabilities(wifiInfo: WifiInfo) = + mock<NetworkCapabilities>().apply { + whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(this.transportInfo).thenReturn(wifiInfo) + } + + private companion object { + const val NETWORK_ID = 45 + val NETWORK = mock<Network>().apply { + whenever(this.getNetId()).thenReturn(NETWORK_ID) + } + const val SSID = "AB" + val PRIMARY_WIFI_INFO: WifiInfo = mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(SSID) + whenever(this.isPrimary).thenReturn(true) + } + } +} + +private val IMMEDIATE = Dispatchers.Main.immediate diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt new file mode 100644 index 000000000000..5f1b1dbb19dc --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.wifi.domain.interactor + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.yield +import org.junit.Before +import org.junit.Test + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +class WifiInteractorTest : SysuiTestCase() { + + private lateinit var underTest: WifiInteractor + + private lateinit var repository: FakeWifiRepository + + @Before + fun setUp() { + repository = FakeWifiRepository() + underTest = WifiInteractor(repository) + } + + @Test + fun hasActivityIn_noInOrOut_outputsFalse() = runBlocking(IMMEDIATE) { + repository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL) + repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = false)) + + var latest: Boolean? = null + val job = underTest + .hasActivityIn + .onEach { latest = it } + .launchIn(this) + + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun hasActivityIn_onlyOut_outputsFalse() = runBlocking(IMMEDIATE) { + repository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL) + repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = true)) + + var latest: Boolean? = null + val job = underTest + .hasActivityIn + .onEach { latest = it } + .launchIn(this) + + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun hasActivityIn_onlyIn_outputsTrue() = runBlocking(IMMEDIATE) { + repository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL) + repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false)) + + var latest: Boolean? = null + val job = underTest + .hasActivityIn + .onEach { latest = it } + .launchIn(this) + + assertThat(latest).isTrue() + + job.cancel() + } + + @Test + fun hasActivityIn_inAndOut_outputsTrue() = runBlocking(IMMEDIATE) { + repository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL) + repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = true)) + + var latest: Boolean? = null + val job = underTest + .hasActivityIn + .onEach { latest = it } + .launchIn(this) + + assertThat(latest).isTrue() + + job.cancel() + } + + @Test + fun hasActivityIn_ssidNull_outputsFalse() = runBlocking(IMMEDIATE) { + repository.setWifiNetwork(WifiNetworkModel.Active(networkId = 1, ssid = null)) + repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = true)) + + var latest: Boolean? = null + val job = underTest + .hasActivityIn + .onEach { latest = it } + .launchIn(this) + + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun hasActivityIn_multipleChanges_multipleOutputChanges() = runBlocking(IMMEDIATE) { + repository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL) + + var latest: Boolean? = null + val job = underTest + .hasActivityIn + .onEach { latest = it } + .launchIn(this) + + // Conduct a series of changes and verify we catch each of them in succession + repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false)) + yield() + assertThat(latest).isTrue() + + repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = true)) + yield() + assertThat(latest).isFalse() + + repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = true)) + yield() + assertThat(latest).isTrue() + + repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false)) + yield() + assertThat(latest).isTrue() + + repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = false)) + yield() + assertThat(latest).isFalse() + + job.cancel() + } + + companion object { + val VALID_WIFI_NETWORK_MODEL = WifiNetworkModel.Active(networkId = 1, ssid = "AB") + } +} + +private val IMMEDIATE = Dispatchers.Main.immediate diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt new file mode 100644 index 000000000000..3c200a5da4fa --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.wifi.ui.view + +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.lifecycle.InstantTaskExecutorRule +import com.android.systemui.util.Assert +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@SmallTest +@RunWith(JUnit4::class) +@RunWithLooper +class ModernStatusBarWifiViewTest : SysuiTestCase() { + + @JvmField @Rule + val instantTaskExecutor = InstantTaskExecutorRule() + + @Before + fun setUp() { + Assert.setTestThread(Thread.currentThread()) + } + + @Test + fun constructAndBind_hasCorrectSlot() { + val view = ModernStatusBarWifiView.constructAndBind( + context, "slotName", mock() + ) + + assertThat(view.slot).isEqualTo("slotName") + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt new file mode 100644 index 000000000000..c79073409883 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags +import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel +import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository +import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor +import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.yield +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +class WifiViewModelTest : SysuiTestCase() { + + private lateinit var underTest: WifiViewModel + + @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags + @Mock private lateinit var logger: ConnectivityPipelineLogger + @Mock private lateinit var constants: WifiConstants + private lateinit var repository: FakeWifiRepository + private lateinit var interactor: WifiInteractor + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + repository = FakeWifiRepository() + interactor = WifiInteractor(repository) + + underTest = WifiViewModel( + statusBarPipelineFlags, + constants, + logger, + interactor + ) + + // Set up with a valid SSID + repository.setWifiNetwork(WifiNetworkModel.Active(networkId = 1, ssid = "AB")) + } + + @Test + fun activityInVisible_showActivityConfigFalse_receivesFalse() = runBlocking(IMMEDIATE) { + whenever(constants.shouldShowActivityConfig).thenReturn(false) + + var latest: Boolean? = null + val job = underTest + .isActivityInVisible + .onEach { latest = it } + .launchIn(this) + + // Verify that on launch, we receive a false. + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun activityInVisible_showActivityConfigFalse_noUpdatesReceived() = runBlocking(IMMEDIATE) { + whenever(constants.shouldShowActivityConfig).thenReturn(false) + + var latest: Boolean? = null + val job = underTest + .isActivityInVisible + .onEach { latest = it } + .launchIn(this) + + // Update the repo to have activityIn + repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false)) + yield() + + // Verify that we didn't update to activityIn=true (because our config is false) + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun activityInVisible_showActivityConfigTrue_receivesUpdate() = runBlocking(IMMEDIATE) { + whenever(constants.shouldShowActivityConfig).thenReturn(true) + + var latest: Boolean? = null + val job = underTest + .isActivityInVisible + .onEach { latest = it } + .launchIn(this) + + // Update the repo to have activityIn + repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false)) + yield() + + // Verify that we updated to activityIn=true + assertThat(latest).isTrue() + + job.cancel() + } +} + +private val IMMEDIATE = Dispatchers.Main.immediate diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java index b7f38f1433ff..50259b5246f5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java @@ -310,7 +310,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { } @Test - public void onWallpaperColorsChanged_ResetThemeWithNewHomeWallpapers() { + public void onWallpaperColorsChanged_resetThemeWithNewHomeWallpapers() { // Should ask for a new theme when wallpaper colors change WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED), Color.valueOf(Color.BLUE), null); @@ -345,6 +345,61 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { } @Test + public void onWallpaperColorsChanged_keepsThemeWhenSetFromLockScreen() { + // Should ask for a new theme when wallpaper colors change + WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED), + Color.valueOf(Color.BLUE), null); + String jsonString = + "{\"android.theme.customization.color_source\":\"lock_wallpaper\"," + + "\"android.theme.customization.system_palette\":\"A16B00\"," + + "\"android.theme.customization.accent_color\":\"A16B00\"," + + "\"android.theme.customization.color_index\":\"2\"}"; + when(mSecureSettings.getStringForUser( + eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt())) + .thenReturn(jsonString); + when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM)) + .thenReturn(20); + when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, USER_SYSTEM)) + .thenReturn(21); + mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM, + USER_SYSTEM); + verify(mSecureSettings, never()).putStringForUser( + eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), any(), anyInt()); + } + + @Test + public void onWallpaperColorsChanged_resetLockScreenThemeWhenBothSet() { + // Should ask for a new theme when wallpaper colors change + WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED), + Color.valueOf(Color.BLUE), null); + String jsonString = + "{\"android.theme.customization.color_source\":\"lock_wallpaper\"," + + "\"android.theme.customization.system_palette\":\"A16B00\"," + + "\"android.theme.customization.accent_color\":\"A16B00\"," + + "\"android.theme.customization.color_index\":\"2\"}"; + when(mSecureSettings.getStringForUser( + eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt())) + .thenReturn(jsonString); + when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM)) + .thenReturn(20); + when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, USER_SYSTEM)) + .thenReturn(21); + + mColorsListener.getValue().onColorsChanged(mainColors, + WallpaperManager.FLAG_SYSTEM | WallpaperManager.FLAG_LOCK, + USER_SYSTEM); + + ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class); + verify(mSecureSettings).putStringForUser( + eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture(), + anyInt()); + assertThat(updatedSetting.getValue().contains( + "android.theme.customization.color_both\":\"1")).isTrue(); + verify(mThemeOverlayApplier) + .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + } + + @Test public void onSettingChanged_honorThemeStyle() { when(mDeviceProvisionedController.isUserSetup(anyInt())).thenReturn(true); List<Style> validStyles = Arrays.asList(Style.EXPRESSIVE, Style.SPRITZ, Style.TONAL_SPOT, @@ -381,7 +436,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { } @Test - public void onWallpaperColorsChanged_ResetThemeWithNewHomeAndLockWallpaper() { + public void onWallpaperColorsChanged_resetThemeWithNewHomeAndLockWallpaper() { // Should ask for a new theme when wallpaper colors change WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED), Color.valueOf(Color.BLUE), null); @@ -450,7 +505,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED), Color.valueOf(Color.BLUE), null); String jsonString = - "{\"android.theme.customization.color_source\":\"lock_wallpaper\"," + "{\"android.theme.customization.color_source\":\"home_wallpaper\"," + "\"android.theme.customization.system_palette\":\"A16B00\"," + "\"android.theme.customization.accent_color\":\"A16B00\"," + "\"android.theme.customization.color_index\":\"2\"}"; @@ -476,7 +531,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { } @Test - public void onWallpaperColorsChanged_ResetThemeWhenFromLatestWallpaper() { + public void onWallpaperColorsChanged_resetThemeWhenFromLatestWallpaper() { // Should ask for a new theme when the colors of the last applied wallpaper change WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED), Color.valueOf(Color.BLUE), null); diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/SysuiLifecycleTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/SysuiLifecycleTest.java deleted file mode 100644 index 4f509eaaadde..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/util/SysuiLifecycleTest.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.util; - -import static androidx.lifecycle.Lifecycle.Event.ON_CREATE; -import static androidx.lifecycle.Lifecycle.Event.ON_DESTROY; -import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE; -import static androidx.lifecycle.Lifecycle.Event.ON_RESUME; -import static androidx.lifecycle.Lifecycle.Event.ON_START; -import static androidx.lifecycle.Lifecycle.Event.ON_STOP; - -import static com.android.systemui.util.SysuiLifecycle.viewAttachLifecycle; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; -import android.testing.TestableLooper.RunWithLooper; -import android.testing.ViewUtils; -import android.view.View; - -import androidx.lifecycle.Lifecycle; -import androidx.lifecycle.LifecycleEventObserver; -import androidx.lifecycle.LifecycleOwner; -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWithLooper(setAsMainLooper = true) -@RunWith(AndroidTestingRunner.class) -@SmallTest -public class SysuiLifecycleTest extends SysuiTestCase { - - private View mView; - - @Before - public void setUp() { - mView = new View(mContext); - } - - @After - public void tearDown() { - if (mView.isAttachedToWindow()) { - ViewUtils.detachView(mView); - TestableLooper.get(this).processAllMessages(); - } - } - - @Test - public void testAttach() { - LifecycleEventObserver observer = mock(LifecycleEventObserver.class); - LifecycleOwner lifecycle = viewAttachLifecycle(mView); - lifecycle.getLifecycle().addObserver(observer); - - ViewUtils.attachView(mView); - TestableLooper.get(this).processAllMessages(); - - verify(observer).onStateChanged(eq(lifecycle), eq(ON_CREATE)); - verify(observer).onStateChanged(eq(lifecycle), eq(ON_START)); - verify(observer).onStateChanged(eq(lifecycle), eq(ON_RESUME)); - } - - @Test - public void testDetach() { - LifecycleEventObserver observer = mock(LifecycleEventObserver.class); - LifecycleOwner lifecycle = viewAttachLifecycle(mView); - lifecycle.getLifecycle().addObserver(observer); - - ViewUtils.attachView(mView); - TestableLooper.get(this).processAllMessages(); - - ViewUtils.detachView(mView); - TestableLooper.get(this).processAllMessages(); - - verify(observer).onStateChanged(eq(lifecycle), eq(ON_PAUSE)); - verify(observer).onStateChanged(eq(lifecycle), eq(ON_STOP)); - verify(observer).onStateChanged(eq(lifecycle), eq(ON_DESTROY)); - } - - @Test - public void testStateBeforeAttach() { - // WHEN a lifecycle is obtained from a view - LifecycleOwner lifecycle = viewAttachLifecycle(mView); - // THEN the lifecycle state should be INITIAZED - assertThat(lifecycle.getLifecycle().getCurrentState()).isEqualTo( - Lifecycle.State.INITIALIZED); - } - - @Test - public void testStateAfterAttach() { - // WHEN a lifecycle is obtained from a view - LifecycleOwner lifecycle = viewAttachLifecycle(mView); - // AND the view is attached - ViewUtils.attachView(mView); - TestableLooper.get(this).processAllMessages(); - // THEN the lifecycle state should be RESUMED - assertThat(lifecycle.getLifecycle().getCurrentState()).isEqualTo(Lifecycle.State.RESUMED); - } - - @Test - public void testStateAfterDetach() { - // WHEN a lifecycle is obtained from a view - LifecycleOwner lifecycle = viewAttachLifecycle(mView); - // AND the view is detached - ViewUtils.attachView(mView); - TestableLooper.get(this).processAllMessages(); - ViewUtils.detachView(mView); - TestableLooper.get(this).processAllMessages(); - // THEN the lifecycle state should be DESTROYED - assertThat(lifecycle.getLifecycle().getCurrentState()).isEqualTo(Lifecycle.State.DESTROYED); - } - - @Test - public void testStateAfterReattach() { - // WHEN a lifecycle is obtained from a view - LifecycleOwner lifecycle = viewAttachLifecycle(mView); - // AND the view is re-attached - ViewUtils.attachView(mView); - TestableLooper.get(this).processAllMessages(); - ViewUtils.detachView(mView); - TestableLooper.get(this).processAllMessages(); - ViewUtils.attachView(mView); - TestableLooper.get(this).processAllMessages(); - // THEN the lifecycle state should still be DESTROYED, err RESUMED? - assertThat(lifecycle.getLifecycle().getCurrentState()).isEqualTo(Lifecycle.State.RESUMED); - } - - @Test - public void testStateWhenViewAlreadyAttached() { - // GIVEN that a view is already attached - ViewUtils.attachView(mView); - TestableLooper.get(this).processAllMessages(); - // WHEN a lifecycle is obtained from a view - LifecycleOwner lifecycle = viewAttachLifecycle(mView); - // THEN the lifecycle state should be RESUMED - assertThat(lifecycle.getLifecycle().getCurrentState()).isEqualTo(Lifecycle.State.RESUMED); - } - - @Test - public void testStateWhenViewAlreadyDetached() { - // GIVEN that a view is already detached - ViewUtils.attachView(mView); - TestableLooper.get(this).processAllMessages(); - ViewUtils.detachView(mView); - TestableLooper.get(this).processAllMessages(); - // WHEN a lifecycle is obtained from a view - LifecycleOwner lifecycle = viewAttachLifecycle(mView); - // THEN the lifecycle state should be INITIALIZED - assertThat(lifecycle.getLifecycle().getCurrentState()).isEqualTo( - Lifecycle.State.INITIALIZED); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt new file mode 100644 index 000000000000..15ba67205034 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.kotlin + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import java.util.concurrent.atomic.AtomicLong +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class IpcSerializerTest : SysuiTestCase() { + + private val serializer = IpcSerializer() + + @Test + fun serializeManyIncomingIpcs(): Unit = runBlocking(Dispatchers.Main.immediate) { + val processor = launch(start = CoroutineStart.LAZY) { serializer.process() } + withContext(Dispatchers.IO) { + val lastEvaluatedTime = AtomicLong(System.currentTimeMillis()) + // First, launch many serialization requests in parallel + repeat(100_000) { + launch(Dispatchers.Unconfined) { + val enqueuedTime = System.currentTimeMillis() + serializer.runSerialized { + val last = lastEvaluatedTime.getAndSet(enqueuedTime) + assertTrue( + "expected $last less than or equal to $enqueuedTime ", + last <= enqueuedTime, + ) + } + } + } + // Then, process them all in the order they came in. + processor.start() + } + // All done, stop processing + processor.cancel() + } + + @Test(timeout = 5000) + fun serializeOnOneThread_doesNotDeadlock() = runBlocking { + val job = launch { serializer.process() } + repeat(100) { + serializer.runSerializedBlocking { } + } + job.cancel() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 18acf3f6ce53..5d63632725c2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -63,6 +63,7 @@ import android.graphics.drawable.Icon; import android.hardware.display.AmbientDisplayConfiguration; import android.os.Handler; import android.os.PowerManager; +import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.service.dreams.IDreamManager; @@ -134,6 +135,7 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.onehanded.OneHandedController; +import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; @@ -204,6 +206,8 @@ public class BubblesTest extends SysuiTestCase { private ArgumentCaptor<IntentFilter> mFilterArgumentCaptor; @Captor private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverArgumentCaptor; + @Captor + private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallbackCaptor; private BubblesManager mBubblesManager; private TestableBubbleController mBubbleController; @@ -222,6 +226,8 @@ public class BubblesTest extends SysuiTestCase { @Mock private ShellInit mShellInit; @Mock + private ShellCommandHandler mShellCommandHandler; + @Mock private ShellController mShellController; @Mock private Bubbles.BubbleExpandListener mBubbleExpandListener; @@ -240,6 +246,8 @@ public class BubblesTest extends SysuiTestCase { @Mock private IStatusBarService mStatusBarService; @Mock + private IDreamManager mIDreamManager; + @Mock private NotificationVisibilityProvider mVisibilityProvider; @Mock private LauncherApps mLauncherApps; @@ -344,6 +352,7 @@ public class BubblesTest extends SysuiTestCase { mBubbleController = new TestableBubbleController( mContext, mShellInit, + mShellCommandHandler, mShellController, mBubbleData, mFloatingContentCoordinator, @@ -371,10 +380,11 @@ public class BubblesTest extends SysuiTestCase { mContext, mBubbleController.asBubbles(), mNotificationShadeWindowController, - mock(KeyguardStateController.class), + mKeyguardStateController, mShadeController, mStatusBarService, mock(INotificationManager.class), + mIDreamManager, mVisibilityProvider, interruptionStateProvider, mZenModeController, @@ -383,7 +393,6 @@ public class BubblesTest extends SysuiTestCase { mCommonNotifCollection, mNotifPipeline, mSysUiState, - mDumpManager, syncExecutor); mBubblesManager.addNotifCallback(mNotifCallback); @@ -391,6 +400,25 @@ public class BubblesTest extends SysuiTestCase { verify(mNotifPipeline, atLeastOnce()) .addCollectionListener(mNotifListenerCaptor.capture()); mEntryListener = mNotifListenerCaptor.getValue(); + + // Get a reference to KeyguardStateController.Callback + verify(mKeyguardStateController, atLeastOnce()) + .addCallback(mKeyguardStateControllerCallbackCaptor.capture()); + } + + @Test + public void dreamingHidesBubbles() throws RemoteException { + mBubbleController.updateBubble(mBubbleEntry); + assertTrue(mBubbleController.hasBubbles()); + assertThat(mBubbleController.getStackView().getVisibility()).isEqualTo(View.VISIBLE); + + when(mIDreamManager.isDreamingOrInPreview()).thenReturn(true); // dreaming is happening + when(mKeyguardStateController.isShowing()).thenReturn(false); // device is unlocked + KeyguardStateController.Callback callback = + mKeyguardStateControllerCallbackCaptor.getValue(); + callback.onKeyguardShowingChanged(); + + assertThat(mBubbleController.getStackView().getVisibility()).isEqualTo(View.INVISIBLE); } @Test @@ -1370,6 +1398,33 @@ public class BubblesTest extends SysuiTestCase { assertThat(stackView.getVisibility()).isEqualTo(View.VISIBLE); } + /** + * Test to verify behavior for following situation: + * <ul> + * <li>status bar shade state is set to <code>false</code></li> + * <li>there is a bubble pending to be expanded</li> + * </ul> + * Test that duplicate status bar state updates to <code>false</code> do not clear the + * pending bubble to be + * expanded. + */ + @Test + public void testOnStatusBarStateChanged_statusBarChangeDoesNotClearExpandingBubble() { + mBubbleController.updateBubble(mBubbleEntry); + mBubbleController.onStatusBarStateChanged(false); + // Set the bubble to expand once status bar state changes + mBubbleController.expandStackAndSelectBubble(mBubbleEntry); + // Check that stack is currently collapsed + assertStackCollapsed(); + // Post status bar state change update with the same value + mBubbleController.onStatusBarStateChanged(false); + // Stack should remain collapsedb + assertStackCollapsed(); + // Post status bar state change which should trigger bubble to expand + mBubbleController.onStatusBarStateChanged(true); + assertStackExpanded(); + } + @Test public void testSetShouldAutoExpand_notifiesFlagChanged() { mBubbleController.updateBubble(mBubbleEntry); diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java index 880ad187f910..6357a09eb196 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java @@ -38,6 +38,7 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.onehanded.OneHandedController; +import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; @@ -51,6 +52,7 @@ public class TestableBubbleController extends BubbleController { // Let's assume surfaces can be synchronized immediately. TestableBubbleController(Context context, ShellInit shellInit, + ShellCommandHandler shellCommandHandler, ShellController shellController, BubbleData data, FloatingContentCoordinator floatingContentCoordinator, @@ -71,12 +73,12 @@ public class TestableBubbleController extends BubbleController { Handler shellMainHandler, TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) { - super(context, shellInit, shellController, data, Runnable::run, floatingContentCoordinator, - dataRepository, statusBarService, windowManager, windowManagerShellWrapper, - userManager, launcherApps, bubbleLogger, taskStackListener, shellTaskOrganizer, - positioner, displayController, oneHandedOptional, dragAndDropController, - shellMainExecutor, shellMainHandler, new SyncExecutor(), taskViewTransitions, - syncQueue); + super(context, shellInit, shellCommandHandler, shellController, data, Runnable::run, + floatingContentCoordinator, dataRepository, statusBarService, windowManager, + windowManagerShellWrapper, userManager, launcherApps, bubbleLogger, + taskStackListener, shellTaskOrganizer, positioner, displayController, + oneHandedOptional, dragAndDropController, shellMainExecutor, shellMainHandler, + new SyncExecutor(), taskViewTransitions, syncQueue); setInflateSynchronously(true); onInit(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java index 9c2136675dfa..da33fa62a9ab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java @@ -28,10 +28,10 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.model.SysUiState; +import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.tracing.ProtoTracer; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.onehanded.OneHanded; @@ -72,7 +72,7 @@ public class WMShellTest extends SysuiTestCase { @Mock OneHanded mOneHanded; @Mock WakefulnessLifecycle mWakefulnessLifecycle; @Mock ProtoTracer mProtoTracer; - @Mock UserInfoController mUserInfoController; + @Mock UserTracker mUserTracker; @Mock ShellExecutor mSysUiMainExecutor; @Before @@ -83,7 +83,7 @@ public class WMShellTest extends SysuiTestCase { Optional.of(mSplitScreen), Optional.of(mOneHanded), mCommandQueue, mConfigurationController, mKeyguardStateController, mKeyguardUpdateMonitor, mScreenLifecycle, mSysUiState, mProtoTracer, mWakefulnessLifecycle, - mUserInfoController, mSysUiMainExecutor); + mUserTracker, mSysUiMainExecutor); } @Test diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt index 309acdf13a5d..f539dbdf76a0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt @@ -91,6 +91,8 @@ class KotlinArgumentCaptor<T> constructor(clazz: Class<T>) { fun capture(): T = wrapped.capture() val value: T get() = wrapped.value + val allValues: List<T> + get() = wrapped.allValues } /** diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto index 0a4ecb227548..5154a2ddd2cd 100644 --- a/proto/src/system_messages.proto +++ b/proto/src/system_messages.proto @@ -298,6 +298,10 @@ message SystemMessage { // Notify the user to setup their dock NOTE_SETUP_DOCK = 72; + // Inform the user of bluetooth apm state changes. + // Package: android + NOTE_BT_APM_NOTIFICATION = 74; + // ADD_NEW_IDS_ABOVE_THIS_LINE // Legacy IDs with arbitrary values appear below // Legacy IDs existed as stable non-conflicting constants prior to the O release diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java index 43e2b881927c..593a63c2f0c9 100644 --- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java +++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java @@ -77,6 +77,14 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController new ComponentName("android", BlockedAppStreamingActivity.class.getName()); /** + * For communicating when a secure window shows on the virtual display. + */ + public interface SecureWindowCallback { + /** Called when a secure window shows on the virtual display. */ + void onSecureWindowShown(int displayId, int uid); + } + + /** * If required, allow the secure activity to display on remote device since * {@link android.os.Build.VERSION_CODES#TIRAMISU}. */ @@ -108,6 +116,7 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController new ArraySet<>(); @Nullable private final @AssociationRequest.DeviceProfile String mDeviceProfile; + @Nullable private final SecureWindowCallback mSecureWindowCallback; /** * Creates a window policy controller that is generic to the different use cases of virtual @@ -131,6 +140,8 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController * @param activityListener Activity listener to listen for activity changes. * @param activityBlockedCallback Callback that is called when an activity is blocked from * launching. + * @param secureWindowCallback Callback that is called when a secure window shows on the + * virtual display. * @param deviceProfile The {@link AssociationRequest.DeviceProfile} of this virtual device. */ public GenericWindowPolicyController(int windowFlags, int systemWindowFlags, @@ -142,6 +153,7 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController @ActivityPolicy int defaultActivityPolicy, @NonNull ActivityListener activityListener, @NonNull ActivityBlockedCallback activityBlockedCallback, + @NonNull SecureWindowCallback secureWindowCallback, @AssociationRequest.DeviceProfile String deviceProfile) { super(); mAllowedUsers = allowedUsers; @@ -154,6 +166,7 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController setInterestedWindowFlags(windowFlags, systemWindowFlags); mActivityListener = activityListener; mDeviceProfile = deviceProfile; + mSecureWindowCallback = secureWindowCallback; } /** @@ -234,6 +247,12 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController @Override public boolean keepActivityOnWindowFlagsChanged(ActivityInfo activityInfo, int windowFlags, int systemWindowFlags) { + // The callback is fired only when windowFlags are changed. To let VirtualDevice owner + // aware that the virtual display has a secure window on top. + if ((windowFlags & FLAG_SECURE) != 0) { + mSecureWindowCallback.onSecureWindowShown(mDisplayId, activityInfo.applicationInfo.uid); + } + if (!canContainActivity(activityInfo, windowFlags, systemWindowFlags)) { mActivityBlockedCallback.onActivityBlocked(mDisplayId, activityInfo); return false; diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 5f337ab9bae3..cca3212703f0 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -52,6 +52,7 @@ import android.hardware.input.VirtualMouseScrollEvent; import android.hardware.input.VirtualTouchEvent; import android.os.Binder; import android.os.IBinder; +import android.os.Looper; import android.os.PowerManager; import android.os.RemoteException; import android.os.ResultReceiver; @@ -542,6 +543,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub mParams.getDefaultActivityPolicy(), createListenerAdapter(), this::onActivityBlocked, + this::onSecureWindowShown, mAssociationInfo.getDeviceProfile()); gwpc.registerRunningAppsChangedListener(/* listener= */ this); return gwpc; @@ -591,6 +593,21 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub mContext.getUser()); } + private void onSecureWindowShown(int displayId, int uid) { + if (!mVirtualDisplayIds.contains(displayId)) { + return; + } + + // If a virtual display isn't secure, the screen can't be captured. Show a warning toast + // if the secure window is shown on a non-secure virtual display. + DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); + Display display = displayManager.getDisplay(displayId); + if ((display.getFlags() & FLAG_SECURE) == 0) { + showToastWhereUidIsRunning(uid, com.android.internal.R.string.vdm_secure_window, + Toast.LENGTH_LONG, mContext.getMainLooper()); + } + } + private ArraySet<UserHandle> getAllowedUserHandles() { ArraySet<UserHandle> result = new ArraySet<>(); DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); @@ -650,14 +667,16 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub /** * Shows a toast on virtual displays owned by this device which have a given uid running. */ - void showToastWhereUidIsRunning(int uid, @StringRes int resId, @Toast.Duration int duration) { - showToastWhereUidIsRunning(uid, mContext.getString(resId), duration); + void showToastWhereUidIsRunning(int uid, @StringRes int resId, @Toast.Duration int duration, + Looper looper) { + showToastWhereUidIsRunning(uid, mContext.getString(resId), duration, looper); } /** * Shows a toast on virtual displays owned by this device which have a given uid running. */ - void showToastWhereUidIsRunning(int uid, String text, @Toast.Duration int duration) { + void showToastWhereUidIsRunning(int uid, String text, @Toast.Duration int duration, + Looper looper) { synchronized (mVirtualDeviceLock) { DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); final int size = mWindowPolicyControllers.size(); @@ -666,7 +685,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub int displayId = mWindowPolicyControllers.keyAt(i); Display display = displayManager.getDisplay(displayId); if (display != null && display.isValid()) { - Toast.makeText(mContext.createDisplayContext(display), text, + Toast.makeText(mContext.createDisplayContext(display), looper, text, duration).show(); } } diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index 35e9060b58d4..41b6fade4ac6 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -202,7 +202,7 @@ public class VirtualDeviceManagerService extends SystemService { getContext().getString( com.android.internal.R.string.vdm_camera_access_denied, deviceName), - Toast.LENGTH_LONG); + Toast.LENGTH_LONG, Looper.myLooper()); } } } diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 9a98f545d8d0..dcb7a300b6e9 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -19,12 +19,10 @@ package com.android.server; import static android.Manifest.permission.ACCESS_MTP; import static android.Manifest.permission.INSTALL_PACKAGES; import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE; -import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.OP_LEGACY_STORAGE; import static android.app.AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE; import static android.app.AppOpsManager.OP_REQUEST_INSTALL_PACKAGES; -import static android.app.AppOpsManager.OP_WRITE_EXTERNAL_STORAGE; import static android.app.PendingIntent.FLAG_CANCEL_CURRENT; import static android.app.PendingIntent.FLAG_IMMUTABLE; import static android.app.PendingIntent.FLAG_ONE_SHOT; @@ -4526,11 +4524,7 @@ class StorageManagerService extends IStorageManager.Stub } } - // Determine if caller is holding runtime permission - final boolean hasWrite = StorageManager.checkPermissionAndCheckOp(mContext, false, 0, - uid, packageName, WRITE_EXTERNAL_STORAGE, OP_WRITE_EXTERNAL_STORAGE); - - // We're only willing to give out installer access if they also hold + // We're only willing to give out installer access if they hold // runtime permission; this is a firm CDD requirement final boolean hasInstall = mIPackageManager.checkUidPermission(INSTALL_PACKAGES, uid) == PERMISSION_GRANTED; @@ -4546,7 +4540,7 @@ class StorageManagerService extends IStorageManager.Stub break; } } - if ((hasInstall || hasInstallOp) && hasWrite) { + if (hasInstall || hasInstallOp) { return StorageManager.MOUNT_MODE_EXTERNAL_INSTALLER; } return StorageManager.MOUNT_MODE_EXTERNAL_DEFAULT; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index b512cdfb6683..147b5507346c 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -452,6 +452,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.concurrent.CopyOnWriteArrayList; @@ -5494,7 +5495,7 @@ public class ActivityManagerService extends IActivityManager.Stub IIntentSender pendingResult, int matchFlags) { enforceCallingPermission(Manifest.permission.GET_INTENT_SENDER_INTENT, "queryIntentComponentsForIntentSender()"); - Preconditions.checkNotNull(pendingResult); + Objects.requireNonNull(pendingResult); final PendingIntentRecord res; try { res = (PendingIntentRecord) pendingResult; @@ -5506,17 +5507,19 @@ public class ActivityManagerService extends IActivityManager.Stub return null; } final int userId = res.key.userId; + final int uid = res.uid; + final String resolvedType = res.key.requestResolvedType; switch (res.key.type) { case ActivityManager.INTENT_SENDER_ACTIVITY: - return new ParceledListSlice<>(mContext.getPackageManager() - .queryIntentActivitiesAsUser(intent, matchFlags, userId)); + return new ParceledListSlice<>(mPackageManagerInt.queryIntentActivities( + intent, resolvedType, matchFlags, uid, userId)); case ActivityManager.INTENT_SENDER_SERVICE: case ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE: - return new ParceledListSlice<>(mContext.getPackageManager() - .queryIntentServicesAsUser(intent, matchFlags, userId)); + return new ParceledListSlice<>(mPackageManagerInt.queryIntentServices( + intent, matchFlags, uid, userId)); case ActivityManager.INTENT_SENDER_BROADCAST: - return new ParceledListSlice<>(mContext.getPackageManager() - .queryBroadcastReceiversAsUser(intent, matchFlags, userId)); + return new ParceledListSlice<>(mPackageManagerInt.queryIntentReceivers( + intent, resolvedType, matchFlags, uid, userId, false)); default: // ActivityManager.INTENT_SENDER_ACTIVITY_RESULT throw new IllegalStateException("Unsupported intent sender type: " + res.key.type); } diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java index 262436d693ec..eb1fd3aa49be 100644 --- a/services/core/java/com/android/server/am/ProcessStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessStateRecord.java @@ -497,6 +497,7 @@ final class ProcessStateRecord { @GuardedBy({"mService", "mProcLock"}) void setCurAdj(int curAdj) { mCurAdj = curAdj; + mApp.getWindowProcessController().setCurrentAdj(curAdj); } @GuardedBy(anyOf = {"mService", "mProcLock"}) diff --git a/services/core/java/com/android/server/am/UidObserverController.java b/services/core/java/com/android/server/am/UidObserverController.java index e8c1b545eb96..51cb9878c0b3 100644 --- a/services/core/java/com/android/server/am/UidObserverController.java +++ b/services/core/java/com/android/server/am/UidObserverController.java @@ -15,6 +15,7 @@ */ package com.android.server.am; +import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS; @@ -25,6 +26,7 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManagerProto; import android.app.IUidObserver; +import android.content.pm.PackageManager; import android.os.Handler; import android.os.RemoteCallbackList; import android.os.RemoteException; @@ -81,7 +83,9 @@ public class UidObserverController { @NonNull String callingPackage, int callingUid) { synchronized (mLock) { mUidObservers.register(observer, new UidObserverRegistration(callingUid, - callingPackage, which, cutpoint)); + callingPackage, which, cutpoint, + ActivityManager.checkUidPermission(INTERACT_ACROSS_USERS_FULL, callingUid) + == PackageManager.PERMISSION_GRANTED)); } } @@ -252,6 +256,11 @@ public class UidObserverController { final ChangeRecord item = mActiveUidChanges[j]; final long start = SystemClock.uptimeMillis(); final int change = item.change; + // Does the user have permission? Don't send a non user UID change otherwise + if (UserHandle.getUserId(item.uid) != UserHandle.getUserId(reg.mUid) + && !reg.mCanInteractAcrossUsers) { + continue; + } if (change == UidRecord.CHANGE_PROCSTATE && (reg.mWhich & ActivityManager.UID_OBSERVER_PROCSTATE) == 0) { // No-op common case: no significant change, the observer is not @@ -437,6 +446,7 @@ public class UidObserverController { private final String mPkg; private final int mWhich; private final int mCutpoint; + private final boolean mCanInteractAcrossUsers; /** * Total # of callback calls that took more than {@link #SLOW_UID_OBSERVER_THRESHOLD_MS}. @@ -467,11 +477,13 @@ public class UidObserverController { ActivityManagerProto.UID_OBSERVER_FLAG_PROC_OOM_ADJ, }; - UidObserverRegistration(int uid, @NonNull String pkg, int which, int cutpoint) { + UidObserverRegistration(int uid, @NonNull String pkg, int which, int cutpoint, + boolean canInteractAcrossUsers) { this.mUid = uid; this.mPkg = pkg; this.mWhich = which; this.mCutpoint = cutpoint; + this.mCanInteractAcrossUsers = canInteractAcrossUsers; mLastProcStates = cutpoint >= ActivityManager.MIN_PROCESS_STATE ? new SparseIntArray() : null; } diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java index d16fe1240d0c..d4ef638d0818 100644 --- a/services/core/java/com/android/server/attention/AttentionManagerService.java +++ b/services/core/java/com/android/server/attention/AttentionManagerService.java @@ -343,6 +343,9 @@ public class AttentionManagerService extends SystemService { * * Calling this multiple times for duplicate requests will be no-ops, returning true. * + * TODO(b/239130847): Maintain the proximity state in AttentionManagerService and change this + * to a polling API. + * * @return {@code true} if the framework was able to dispatch the request */ @VisibleForTesting @@ -853,9 +856,6 @@ public class AttentionManagerService extends SystemService { @GuardedBy("mLock") private void cancelAndUnbindLocked() { synchronized (mLock) { - if (mCurrentAttentionCheck == null && mCurrentProximityUpdate == null) { - return; - } if (mCurrentAttentionCheck != null) { cancel(); } @@ -937,7 +937,7 @@ public class AttentionManagerService extends SystemService { } } - class TestableProximityUpdateCallbackInternal extends ProximityUpdateCallbackInternal { + class TestableProximityUpdateCallbackInternal implements ProximityUpdateCallbackInternal { private double mLastCallbackCode = PROXIMITY_UNKNOWN; @Override @@ -1069,6 +1069,7 @@ public class AttentionManagerService extends SystemService { private void resetStates() { synchronized (mLock) { mCurrentProximityUpdate = null; + cancelAndUnbindLocked(); } mComponentName = resolveAttentionService(mContext); } diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 82fe6c6654da..2b8d6a33bb1b 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -1770,8 +1770,8 @@ import java.util.concurrent.atomic.AtomicBoolean; return; } Log.w(TAG, "Communication client died"); - removeCommunicationRouteClient(client.getBinder(), true); - onUpdateCommunicationRouteClient("onCommunicationRouteClientDied"); + setCommunicationRouteForClient(client.getBinder(), client.getPid(), null, + BtHelper.SCO_MODE_UNDEFINED, "onCommunicationRouteClientDied"); } /** diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index c00c29815891..0a081bfbee96 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -1614,6 +1614,7 @@ public class AudioService extends IAudioService.Stub } synchronized (mAudioPolicies) { + ArrayList<AudioPolicyProxy> invalidProxies = new ArrayList<>(); for (AudioPolicyProxy policy : mAudioPolicies.values()) { final int status = policy.connectMixes(); if (status != AudioSystem.SUCCESS) { @@ -1621,7 +1622,7 @@ public class AudioService extends IAudioService.Stub Log.e(TAG, "onAudioServerDied: error " + AudioSystem.audioSystemErrorToString(status) + " when connecting mixes for policy " + policy.toLogFriendlyString()); - policy.release(); + invalidProxies.add(policy); } else { final int deviceAffinitiesStatus = policy.setupDeviceAffinities(); if (deviceAffinitiesStatus != AudioSystem.SUCCESS) { @@ -1629,10 +1630,12 @@ public class AudioService extends IAudioService.Stub + AudioSystem.audioSystemErrorToString(deviceAffinitiesStatus) + " when connecting device affinities for policy " + policy.toLogFriendlyString()); - policy.release(); + invalidProxies.add(policy); } } } + invalidProxies.forEach((policy) -> policy.release()); + } // Restore capture policies diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index 8356134bc63b..b7e817e15452 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -571,7 +571,9 @@ public class SpatializerHelper { // There may be different devices with the same device type (aliasing). // We always send the full device state info on each change. private void logDeviceState(SADeviceState deviceState, String event) { - final String deviceName = AudioSystem.getDeviceName(deviceState.mDeviceType); + final int deviceType = AudioDeviceInfo.convertDeviceTypeToInternalDevice( + deviceState.mDeviceType); + final String deviceName = AudioSystem.getDeviceName(deviceType); new MediaMetrics.Item(METRICS_DEVICE_PREFIX + deviceName) .set(MediaMetrics.Property.ADDRESS, deviceState.mDeviceAddress) .set(MediaMetrics.Property.ENABLED, deviceState.mEnabled ? "true" : "false") @@ -727,8 +729,11 @@ public class SpatializerHelper { } private boolean isDeviceCompatibleWithSpatializationModes(@NonNull AudioDeviceAttributes ada) { + // modeForDevice will be neither transaural or binaural for devices that do not support + // spatial audio. For instance mono devices like earpiece, speaker safe or sco must + // not be included. final byte modeForDevice = (byte) SPAT_MODE_FOR_DEVICE_TYPE.get(ada.getType(), - /*default when type not found*/ SpatializationMode.SPATIALIZER_BINAURAL); + /*default when type not found*/ -1); if ((modeForDevice == SpatializationMode.SPATIALIZER_BINAURAL && mBinauralSupported) || (modeForDevice == SpatializationMode.SPATIALIZER_TRANSAURAL && mTransauralSupported)) { @@ -1536,8 +1541,8 @@ public class SpatializerHelper { @Override public String toString() { - return "type:" + mDeviceType + " addr:" + mDeviceAddress + " enabled:" + mEnabled - + " HT:" + mHasHeadTracker + " HTenabled:" + mHeadTrackerEnabled; + return "type: " + mDeviceType + " addr: " + mDeviceAddress + " enabled: " + mEnabled + + " HT: " + mHasHeadTracker + " HTenabled: " + mHeadTrackerEnabled; } String toPersistableString() { diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 9691b20066c7..98238ccd93c3 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -4040,6 +4040,7 @@ public class Vpn { mConfig.proxyInfo = profile.proxy; mConfig.requiresInternetValidation = profile.requiresInternetValidation; mConfig.excludeLocalRoutes = profile.excludeLocalRoutes; + mConfig.allowBypass = profile.isBypassable; switch (profile.type) { case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS: diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index db646df9b071..31562c735f3a 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -148,6 +148,12 @@ class AutomaticBrightnessController { // The currently accepted nominal ambient light level. private float mAmbientLux; + // The last calculated ambient light level (long time window). + private float mSlowAmbientLux; + + // The last calculated ambient light level (short time window). + private float mFastAmbientLux; + // The last ambient lux value prior to passing the darkening or brightening threshold. private float mPreThresholdLux; @@ -439,6 +445,14 @@ class AutomaticBrightnessController { return mAmbientLux; } + float getSlowAmbientLux() { + return mSlowAmbientLux; + } + + float getFastAmbientLux() { + return mFastAmbientLux; + } + private boolean setDisplayPolicy(int policy) { if (mDisplayPolicy == policy) { return false; @@ -811,20 +825,20 @@ class AutomaticBrightnessController { // proposed ambient light value since the slow value might be sufficiently far enough away // from the fast value to cause a recalculation while its actually just converging on // the fast value still. - float slowAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonLong); - float fastAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonShort); + mSlowAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonLong); + mFastAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonShort); - if ((slowAmbientLux >= mAmbientBrighteningThreshold - && fastAmbientLux >= mAmbientBrighteningThreshold + if ((mSlowAmbientLux >= mAmbientBrighteningThreshold + && mFastAmbientLux >= mAmbientBrighteningThreshold && nextBrightenTransition <= time) - || (slowAmbientLux <= mAmbientDarkeningThreshold - && fastAmbientLux <= mAmbientDarkeningThreshold + || (mSlowAmbientLux <= mAmbientDarkeningThreshold + && mFastAmbientLux <= mAmbientDarkeningThreshold && nextDarkenTransition <= time)) { mPreThresholdLux = mAmbientLux; - setAmbientLux(fastAmbientLux); + setAmbientLux(mFastAmbientLux); if (mLoggingEnabled) { Slog.d(TAG, "updateAmbientLux: " - + ((fastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": " + + ((mFastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": " + "mBrighteningLuxThreshold=" + mAmbientBrighteningThreshold + ", " + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", " + "mAmbientLux=" + mAmbientLux); diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 9c86076a8999..2deb0565f018 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -1629,7 +1629,14 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mTempBrightnessEvent.reason.set(mBrightnessReason); mTempBrightnessEvent.hbmMax = mHbmController.getCurrentBrightnessMax(); mTempBrightnessEvent.hbmMode = mHbmController.getHighBrightnessMode(); - mTempBrightnessEvent.flags |= (mIsRbcActive ? BrightnessEvent.FLAG_RBC : 0); + mTempBrightnessEvent.flags = (mTempBrightnessEvent.flags + | (mIsRbcActive ? BrightnessEvent.FLAG_RBC : 0) + | (mPowerRequest.lowPowerMode ? BrightnessEvent.FLAG_LOW_POWER_MODE : 0)); + mTempBrightnessEvent.physicalDisplayId = mUniqueDisplayId; + mTempBrightnessEvent.rbcStrength = mCdsi != null + ? mCdsi.getReduceBrightColorsStrength() : -1; + mTempBrightnessEvent.powerFactor = mPowerRequest.screenLowPowerBrightnessFactor; + // Temporary is what we use during slider interactions. We avoid logging those so that // we don't spam logcat when the slider is being used. boolean tempToTempTransition = @@ -1637,6 +1644,15 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call && mLastBrightnessEvent.reason.reason == BrightnessReason.REASON_TEMPORARY; if ((!mTempBrightnessEvent.equalsMainData(mLastBrightnessEvent) && !tempToTempTransition) || brightnessAdjustmentFlags != 0) { + float lastBrightness = mLastBrightnessEvent.brightness; + mTempBrightnessEvent.initialBrightness = lastBrightness; + mTempBrightnessEvent.fastAmbientLux = + mAutomaticBrightnessController == null + ? -1f : mAutomaticBrightnessController.getFastAmbientLux(); + mTempBrightnessEvent.slowAmbientLux = + mAutomaticBrightnessController == null + ? -1f : mAutomaticBrightnessController.getSlowAmbientLux(); + mTempBrightnessEvent.automaticBrightnessEnabled = mPowerRequest.useAutoBrightness; mLastBrightnessEvent.copyFrom(mTempBrightnessEvent); BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent); @@ -1646,6 +1662,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call newEvent.flags |= (userSetBrightnessChanged ? BrightnessEvent.FLAG_USER_SET : 0); Slog.i(TAG, newEvent.toString(/* includeTime= */ false)); + if (userSetBrightnessChanged) { + logManualBrightnessEvent(newEvent); + } if (mBrightnessEventRingBuffer != null) { mBrightnessEventRingBuffer.append(newEvent); } @@ -2736,27 +2755,63 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } } + private void logManualBrightnessEvent(BrightnessEvent event) { + float appliedHbmMaxNits = + event.hbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF + ? -1f : convertToNits(event.hbmMax); + + // thermalCapNits set to -1 if not currently capping max brightness + float appliedThermalCapNits = + event.thermalMax == PowerManager.BRIGHTNESS_MAX + ? -1f : convertToNits(event.thermalMax); + + int appliedRbcStrength = event.isRbcEnabled() ? event.rbcStrength : -1; + + float appliedPowerFactor = event.isLowPowerModeSet() ? event.powerFactor : -1f; + + FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED, + convertToNits(event.initialBrightness), + convertToNits(event.brightness), + event.slowAmbientLux, + event.physicalDisplayId, + event.isShortTermModelActive(), + appliedPowerFactor, + appliedRbcStrength, + appliedHbmMaxNits, + appliedThermalCapNits, + event.automaticBrightnessEnabled, + FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__REASON__REASON_MANUAL); + } + class BrightnessEvent { static final int FLAG_RBC = 0x1; static final int FLAG_INVALID_LUX = 0x2; static final int FLAG_DOZE_SCALE = 0x4; static final int FLAG_USER_SET = 0x8; - static final int FLAG_IDLE_CURVE = 0x16; + static final int FLAG_IDLE_CURVE = 0x10; + static final int FLAG_LOW_POWER_MODE = 0x20; public final BrightnessReason reason = new BrightnessReason(); public int displayId; + public String physicalDisplayId; public float lux; + public float fastAmbientLux; + public float slowAmbientLux; public float preThresholdLux; public long time; public float brightness; + public float initialBrightness; public float recommendedBrightness; public float preThresholdBrightness; public float hbmMax; + public int rbcStrength; public float thermalMax; + public float powerFactor; public int hbmMode; public int flags; public int adjustmentFlags; + public boolean automaticBrightnessEnabled; BrightnessEvent(BrightnessEvent that) { copyFrom(that); @@ -2769,71 +2824,115 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call void copyFrom(BrightnessEvent that) { displayId = that.displayId; + physicalDisplayId = that.physicalDisplayId; time = that.time; lux = that.lux; + fastAmbientLux = that.fastAmbientLux; + slowAmbientLux = that.slowAmbientLux; preThresholdLux = that.preThresholdLux; brightness = that.brightness; + initialBrightness = that.initialBrightness; recommendedBrightness = that.recommendedBrightness; preThresholdBrightness = that.preThresholdBrightness; hbmMax = that.hbmMax; + rbcStrength = that.rbcStrength; thermalMax = that.thermalMax; + powerFactor = that.powerFactor; flags = that.flags; hbmMode = that.hbmMode; reason.set(that.reason); adjustmentFlags = that.adjustmentFlags; + automaticBrightnessEnabled = that.automaticBrightnessEnabled; } void reset() { time = SystemClock.uptimeMillis(); + physicalDisplayId = ""; brightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; + initialBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; recommendedBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; - lux = 0; - preThresholdLux = 0; + lux = 0f; + fastAmbientLux = 0f; + slowAmbientLux = 0f; + preThresholdLux = 0f; preThresholdBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; hbmMax = PowerManager.BRIGHTNESS_MAX; + rbcStrength = 0; + powerFactor = 1f; thermalMax = PowerManager.BRIGHTNESS_MAX; flags = 0; hbmMode = BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF; reason.set(null); adjustmentFlags = 0; + automaticBrightnessEnabled = true; + } + + boolean isRbcEnabled() { + return (flags & FLAG_RBC) != 0; + } + + public boolean isShortTermModelActive() { + return (flags & FLAG_USER_SET) != 0; + } + + public boolean isLowPowerModeSet() { + return (flags & FLAG_LOW_POWER_MODE) != 0; } boolean equalsMainData(BrightnessEvent that) { // This equals comparison purposefully ignores time since it is regularly changing and // we don't want to log a brightness event just because the time changed. return displayId == that.displayId + && physicalDisplayId.equals(that.physicalDisplayId) && Float.floatToRawIntBits(brightness) == Float.floatToRawIntBits(that.brightness) + && Float.floatToRawIntBits(initialBrightness) + == Float.floatToRawIntBits(that.initialBrightness) && Float.floatToRawIntBits(recommendedBrightness) == Float.floatToRawIntBits(that.recommendedBrightness) && Float.floatToRawIntBits(preThresholdBrightness) == Float.floatToRawIntBits(that.preThresholdBrightness) && Float.floatToRawIntBits(lux) == Float.floatToRawIntBits(that.lux) + && Float.floatToRawIntBits(fastAmbientLux) + == Float.floatToRawIntBits(that.fastAmbientLux) + && Float.floatToRawIntBits(slowAmbientLux) + == Float.floatToRawIntBits(that.slowAmbientLux) && Float.floatToRawIntBits(preThresholdLux) == Float.floatToRawIntBits(that.preThresholdLux) + && rbcStrength == that.rbcStrength && Float.floatToRawIntBits(hbmMax) == Float.floatToRawIntBits(that.hbmMax) && hbmMode == that.hbmMode && Float.floatToRawIntBits(thermalMax) == Float.floatToRawIntBits(that.thermalMax) + && Float.floatToRawIntBits(powerFactor) + == Float.floatToRawIntBits(that.powerFactor) && flags == that.flags && adjustmentFlags == that.adjustmentFlags - && reason.equals(that.reason); + && reason.equals(that.reason) + && automaticBrightnessEnabled == that.automaticBrightnessEnabled; } public String toString(boolean includeTime) { return (includeTime ? TimeUtils.formatForLogging(time) + " - " : "") + "BrightnessEvent: " + "disp=" + displayId + + ", physDisp=" + physicalDisplayId + ", brt=" + brightness + ((flags & FLAG_USER_SET) != 0 ? "(user_set)" : "") + + ", initBrt=" + initialBrightness + ", rcmdBrt=" + recommendedBrightness + ", preBrt=" + preThresholdBrightness + ", lux=" + lux + + ", fastAmbientLux=" + fastAmbientLux + + ", slowAmbientLux=" + slowAmbientLux + ", preLux=" + preThresholdLux + ", hbmMax=" + hbmMax + ", hbmMode=" + BrightnessInfo.hbmToString(hbmMode) + + ", rbcStrength=" + rbcStrength + + ", powerFactor=" + powerFactor + ", thrmMax=" + thermalMax + ", flags=" + flagsToString() - + ", reason=" + reason.toString(adjustmentFlags); + + ", reason=" + reason.toString(adjustmentFlags) + + ", autoBrightness=" + automaticBrightnessEnabled; } @Override @@ -2846,7 +2945,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call + ((flags & FLAG_RBC) != 0 ? "rbc " : "") + ((flags & FLAG_INVALID_LUX) != 0 ? "invalid_lux " : "") + ((flags & FLAG_DOZE_SCALE) != 0 ? "doze_scale " : "") - + ((flags & FLAG_DOZE_SCALE) != 0 ? "idle_curve " : ""); + + ((flags & FLAG_IDLE_CURVE) != 0 ? "idle_curve " : "") + + ((flags & FLAG_LOW_POWER_MODE) != 0 ? "low_power_mode " : ""); } } diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java index 223b8c181fea..c5a8fc2f3bc5 100644 --- a/services/core/java/com/android/server/display/color/ColorDisplayService.java +++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java @@ -1515,6 +1515,10 @@ public final class ColorDisplayService extends SystemService { return mReduceBrightColorsTintController.isActivated(); } + public int getReduceBrightColorsStrength() { + return mReduceBrightColorsTintController.getStrength(); + } + /** * Gets the computed brightness, in nits, when the reduce bright colors feature is applied * at the current strength. diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java index 625f1936e28e..885789227a12 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java @@ -322,6 +322,17 @@ public class ContextHubClientBroker extends IContextHubClient.Stub } else { mPendingIntentRequest = new PendingIntentRequest(pendingIntent, nanoAppId); } + + if (packageName == null) { + String[] packages = mContext.getPackageManager().getPackagesForUid( + Binder.getCallingUid()); + if (packages != null && packages.length > 0) { + packageName = packages[0]; + } + Log.e(TAG, "createClient: Provided package name null. Using first package name " + + packageName); + } + mPackage = packageName; mAttributionTag = attributionTag; mTransactionManager = transactionManager; diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java index 5b2188ac078e..17638ccbe6cc 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -1024,6 +1024,9 @@ public class ContextHubService extends IContextHubService.Stub { } /* package */ void denyClientAuthState(int contextHubId, String packageName, long nanoAppId) { + Log.i(TAG, "Denying " + packageName + " access to " + Long.toHexString(nanoAppId) + + " on context hub # " + contextHubId); + mClientManager.forEachClientOfHub(contextHubId, client -> { if (client.getPackageName().equals(packageName)) { client.updateNanoAppAuthState( diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 057afe84c03d..2f34ccdb5c03 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -6685,6 +6685,20 @@ public class NotificationManagerService extends SystemService { } } + // Ensure only allowed packages have a substitute app name + if (notification.extras.containsKey(Notification.EXTRA_SUBSTITUTE_APP_NAME)) { + int hasSubstituteAppNamePermission = mPackageManager.checkPermission( + permission.SUBSTITUTE_NOTIFICATION_APP_NAME, pkg, userId); + if (hasSubstituteAppNamePermission != PERMISSION_GRANTED) { + notification.extras.remove(Notification.EXTRA_SUBSTITUTE_APP_NAME); + if (DBG) { + Slog.w(TAG, "warning: pkg " + pkg + " attempting to substitute app name" + + " without holding perm " + + Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME); + } + } + } + // Remote views? Are they too big? checkRemoteViews(pkg, tag, id, notification); } @@ -7834,7 +7848,8 @@ public class NotificationManagerService extends SystemService { && (record.getSuppressedVisualEffects() & SUPPRESSED_EFFECT_STATUS_BAR) != 0; if (!record.isUpdate && record.getImportance() > IMPORTANCE_MIN - && !suppressedByDnd) { + && !suppressedByDnd + && isNotificationForCurrentUser(record)) { sendAccessibilityEvent(record); sentAccessibilityEvent = true; } diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java index bdc571103ffd..5e0a18039152 100644 --- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java +++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java @@ -131,6 +131,17 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { } } + // For tests: just do the setting of various local variables without actually doing work + @VisibleForTesting + protected void initForTests(Context context, NotificationUsageStats usageStats, + LruCache peopleCache) { + mUserToContextMap = new ArrayMap<>(); + mBaseContext = context; + mUsageStats = usageStats; + mPeopleCache = peopleCache; + mEnabled = true; + } + public RankingReconsideration process(NotificationRecord record) { if (!mEnabled) { if (VERBOSE) Slog.i(TAG, "disabled"); @@ -179,7 +190,7 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { return NONE; } final PeopleRankingReconsideration prr = - validatePeople(context, key, extras, null, affinityOut); + validatePeople(context, key, extras, null, affinityOut, null); float affinity = affinityOut[0]; if (prr != null) { @@ -224,15 +235,21 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { return context; } - private RankingReconsideration validatePeople(Context context, + @VisibleForTesting + protected RankingReconsideration validatePeople(Context context, final NotificationRecord record) { final String key = record.getKey(); final Bundle extras = record.getNotification().extras; final float[] affinityOut = new float[1]; + ArraySet<String> phoneNumbersOut = new ArraySet<>(); final PeopleRankingReconsideration rr = - validatePeople(context, key, extras, record.getPeopleOverride(), affinityOut); + validatePeople(context, key, extras, record.getPeopleOverride(), affinityOut, + phoneNumbersOut); final float affinity = affinityOut[0]; record.setContactAffinity(affinity); + if (phoneNumbersOut.size() > 0) { + record.mergePhoneNumbers(phoneNumbersOut); + } if (rr == null) { mUsageStats.registerPeopleAffinity(record, affinity > NONE, affinity == STARRED_CONTACT, true /* cached */); @@ -243,7 +260,7 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { } private PeopleRankingReconsideration validatePeople(Context context, String key, Bundle extras, - List<String> peopleOverride, float[] affinityOut) { + List<String> peopleOverride, float[] affinityOut, ArraySet<String> phoneNumbersOut) { float affinity = NONE; if (extras == null) { return null; @@ -270,6 +287,15 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { } if (lookupResult != null) { affinity = Math.max(affinity, lookupResult.getAffinity()); + + // add all phone numbers associated with this lookup result, if they exist + // and if requested + if (phoneNumbersOut != null) { + ArraySet<String> phoneNumbers = lookupResult.getPhoneNumbers(); + if (phoneNumbers != null && phoneNumbers.size() > 0) { + phoneNumbersOut.addAll(phoneNumbers); + } + } } } if (++personIdx == MAX_PEOPLE) { @@ -289,7 +315,8 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { return new PeopleRankingReconsideration(context, key, pendingLookups); } - private String getCacheKey(int userId, String handle) { + @VisibleForTesting + protected static String getCacheKey(int userId, String handle) { return Integer.toString(userId) + ":" + handle; } @@ -485,7 +512,8 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { } } - private static class LookupResult { + @VisibleForTesting + protected static class LookupResult { private static final long CONTACT_REFRESH_MILLIS = 60 * 60 * 1000; // 1hr private final long mExpireMillis; @@ -574,7 +602,8 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { return mPhoneNumbers; } - private boolean isExpired() { + @VisibleForTesting + protected boolean isExpired() { return mExpireMillis < System.currentTimeMillis(); } diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java index 7e36aed81d4a..db0ce2ef6fe2 100644 --- a/services/core/java/com/android/server/notification/ZenModeFiltering.java +++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java @@ -149,8 +149,13 @@ public class ZenModeFiltering { */ public boolean shouldIntercept(int zen, NotificationManager.Policy policy, NotificationRecord record) { - // Zen mode is ignored for critical notifications. - if (zen == ZEN_MODE_OFF || isCritical(record)) { + if (zen == ZEN_MODE_OFF) { + return false; + } + + if (isCritical(record)) { + // Zen mode is ignored for critical notifications. + ZenLog.traceNotIntercepted(record, "criticalNotification"); return false; } // Make an exception to policy for the notification saying that policy has changed @@ -168,6 +173,7 @@ public class ZenModeFiltering { case Global.ZEN_MODE_ALARMS: if (isAlarm(record)) { // Alarms only + ZenLog.traceNotIntercepted(record, "alarm"); return false; } ZenLog.traceIntercepted(record, "alarmsOnly"); @@ -184,6 +190,7 @@ public class ZenModeFiltering { ZenLog.traceIntercepted(record, "!allowAlarms"); return true; } + ZenLog.traceNotIntercepted(record, "allowedAlarm"); return false; } if (isEvent(record)) { @@ -191,6 +198,7 @@ public class ZenModeFiltering { ZenLog.traceIntercepted(record, "!allowEvents"); return true; } + ZenLog.traceNotIntercepted(record, "allowedEvent"); return false; } if (isReminder(record)) { @@ -198,6 +206,7 @@ public class ZenModeFiltering { ZenLog.traceIntercepted(record, "!allowReminders"); return true; } + ZenLog.traceNotIntercepted(record, "allowedReminder"); return false; } if (isMedia(record)) { @@ -205,6 +214,7 @@ public class ZenModeFiltering { ZenLog.traceIntercepted(record, "!allowMedia"); return true; } + ZenLog.traceNotIntercepted(record, "allowedMedia"); return false; } if (isSystem(record)) { @@ -212,6 +222,7 @@ public class ZenModeFiltering { ZenLog.traceIntercepted(record, "!allowSystem"); return true; } + ZenLog.traceNotIntercepted(record, "allowedSystem"); return false; } if (isConversation(record)) { @@ -253,6 +264,7 @@ public class ZenModeFiltering { ZenLog.traceIntercepted(record, "!priority"); return true; default: + ZenLog.traceNotIntercepted(record, "unknownZenMode"); return false; } } @@ -271,10 +283,12 @@ public class ZenModeFiltering { } private static boolean shouldInterceptAudience(int source, NotificationRecord record) { - if (!audienceMatches(source, record.getContactAffinity())) { - ZenLog.traceIntercepted(record, "!audienceMatches"); + float affinity = record.getContactAffinity(); + if (!audienceMatches(source, affinity)) { + ZenLog.traceIntercepted(record, "!audienceMatches,affinity=" + affinity); return true; } + ZenLog.traceNotIntercepted(record, "affinity=" + affinity); return false; } diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java index eb635500580a..3d9e89aa1846 100644 --- a/services/core/java/com/android/server/pm/Computer.java +++ b/services/core/java/com/android/server/pm/Computer.java @@ -113,6 +113,8 @@ public interface Computer extends PackageDataSnapshot { @PackageManagerInternal.PrivateResolveFlags long privateResolveFlags, int filterCallingUid, int userId, boolean resolveForStart, boolean allowDynamicSplits); @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, String resolvedType, + long flags, int filterCallingUid, int userId); + @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, String resolvedType, long flags, int userId); @NonNull List<ResolveInfo> queryIntentServicesInternal(Intent intent, String resolvedType, long flags, int userId, int callingUid, boolean includeInstantApps); diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 259ca655d2b9..4a640ce6274c 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -599,6 +599,15 @@ public class ComputerEngine implements Computer { resolveForStart, userId, intent); } + @NonNull + @Override + public final List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, String resolvedType, + @PackageManager.ResolveInfoFlagsBits long flags, int filterCallingUid, int userId) { + return queryIntentActivitiesInternal( + intent, resolvedType, flags, 0 /*privateResolveFlags*/, filterCallingUid, + userId, false /*resolveForStart*/, true /*allowDynamicSplits*/); + } + public final @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId) { return queryIntentActivitiesInternal( diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index 339d5d4fe021..c3b479219853 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -43,6 +43,7 @@ import android.content.pm.PackageChangeEvent; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.SharedLibraryInfo; +import android.content.pm.UserInfo; import android.content.pm.VersionedPackage; import android.net.Uri; import android.os.Binder; @@ -163,6 +164,18 @@ final class DeletePackageHelper { return PackageManager.DELETE_FAILED_INTERNAL_ERROR; } + if (PackageManagerServiceUtils.isSystemApp(uninstalledPs) + && ((deleteFlags & PackageManager.DELETE_SYSTEM_APP) == 0)) { + UserInfo userInfo = mUserManagerInternal.getUserInfo(userId); + if (userInfo == null || (!userInfo.isAdmin() && !mUserManagerInternal.getUserInfo( + mUserManagerInternal.getProfileParentId(userId)).isAdmin())) { + Slog.w(TAG, "Not removing package " + packageName + + " as only admin user (or their profile) may downgrade system apps"); + EventLog.writeEvent(0x534e4554, "170646036", -1, packageName); + return PackageManager.DELETE_FAILED_USER_RESTRICTED; + } + } + disabledSystemPs = mPm.mSettings.getDisabledSystemPkgLPr(packageName); // Static shared libs can be declared by any package, so let us not // allow removing a package if it provides a lib others depend on. diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java index 652847ad1647..96f37424ea4a 100644 --- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java +++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java @@ -308,7 +308,8 @@ abstract class PackageManagerInternalBase extends PackageManagerInternal { public final List<ResolveInfo> queryIntentActivities( Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int filterCallingUid, int userId) { - return snapshot().queryIntentActivitiesInternal(intent, resolvedType, flags, userId); + return snapshot().queryIntentActivitiesInternal(intent, resolvedType, flags, + filterCallingUid, userId); } @Override diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 701ac73c55d1..00fb0651adc4 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -18,6 +18,7 @@ package com.android.server.pm; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.os.UserManager.DISALLOW_USER_SWITCH; import android.Manifest; import android.accounts.Account; @@ -1760,6 +1761,19 @@ public class UserManagerService extends IUserManager.Stub { } @Override + public boolean isUserSwitcherEnabled(@UserIdInt int mUserId) { + boolean multiUserSettingOn = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.USER_SWITCHER_ENABLED, + Resources.getSystem().getBoolean(com.android.internal + .R.bool.config_showUserSwitcherByDefault) ? 1 : 0) != 0; + + return UserManager.supportsMultipleUsers() + && !hasUserRestriction(DISALLOW_USER_SWITCH, mUserId) + && !UserManager.isDeviceInDemoMode(mContext) + && multiUserSettingOn; + } + + @Override public boolean isRestricted(@UserIdInt int userId) { if (userId != UserHandle.getCallingUserId()) { checkCreateUsersPermission("query isRestricted for user " + userId); @@ -2049,7 +2063,6 @@ public class UserManagerService extends IUserManager.Stub { originatingUserId, local); localChanged = updateLocalRestrictionsForTargetUsersLR(originatingUserId, local, updatedLocalTargetUserIds); - if (isDeviceOwner) { // Remember the global restriction owner userId to be able to make a distinction // in getUserRestrictionSource on who set local policies. @@ -4432,44 +4445,59 @@ public class UserManagerService extends IUserManager.Stub { null, // use default PullAtomMetadata values BackgroundThread.getExecutor(), this::onPullAtom); + statsManager.setPullAtomCallback( + FrameworkStatsLog.MULTI_USER_INFO, + null, // use default PullAtomMetadata values + BackgroundThread.getExecutor(), + this::onPullAtom); } /** Writes a UserInfo pulled atom for each user on the device. */ private int onPullAtom(int atomTag, List<StatsEvent> data) { - if (atomTag != FrameworkStatsLog.USER_INFO) { - Slogf.e(LOG_TAG, "Unexpected atom tag: %d", atomTag); - return android.app.StatsManager.PULL_SKIP; - } - final List<UserInfo> users = getUsersInternal(true, true, true); - final int size = users.size(); - for (int idx = 0; idx < size; idx++) { - final UserInfo user = users.get(idx); - if (user.id == UserHandle.USER_SYSTEM) { - // Skip user 0. It's not interesting. We already know it exists, is running, and (if - // we know the device configuration) its userType. - continue; + if (atomTag == FrameworkStatsLog.USER_INFO) { + final List<UserInfo> users = getUsersInternal(true, true, true); + final int size = users.size(); + if (size > 1) { + for (int idx = 0; idx < size; idx++) { + final UserInfo user = users.get(idx); + final int userTypeStandard = UserManager.getUserTypeForStatsd(user.userType); + final String userTypeCustom = (userTypeStandard == FrameworkStatsLog + .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN) + ? + user.userType : null; + + boolean isUserRunningUnlocked; + synchronized (mUserStates) { + isUserRunningUnlocked = + mUserStates.get(user.id, -1) == UserState.STATE_RUNNING_UNLOCKED; + } + + data.add(FrameworkStatsLog.buildStatsEvent(FrameworkStatsLog.USER_INFO, + user.id, + userTypeStandard, + userTypeCustom, + user.flags, + user.creationTime, + user.lastLoggedInTime, + isUserRunningUnlocked + )); + } } + } else if (atomTag == FrameworkStatsLog.MULTI_USER_INFO) { + if (UserManager.getMaxSupportedUsers() > 1) { + int deviceOwnerUserId = UserHandle.USER_NULL; - final int userTypeStandard = UserManager.getUserTypeForStatsd(user.userType); - final String userTypeCustom = (userTypeStandard == - FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN) ? - user.userType : null; + synchronized (mRestrictionsLock) { + deviceOwnerUserId = mDeviceOwnerUserId; + } - boolean isUserRunningUnlocked; - synchronized (mUserStates) { - isUserRunningUnlocked = - mUserStates.get(user.id, -1) == UserState.STATE_RUNNING_UNLOCKED; - } - - data.add(FrameworkStatsLog.buildStatsEvent(FrameworkStatsLog.USER_INFO, - user.id, - userTypeStandard, - userTypeCustom, - user.flags, - user.creationTime, - user.lastLoggedInTime, - isUserRunningUnlocked - )); + data.add(FrameworkStatsLog.buildStatsEvent(FrameworkStatsLog.MULTI_USER_INFO, + UserManager.getMaxSupportedUsers(), + isUserSwitcherEnabled(deviceOwnerUserId))); + } + } else { + Slogf.e(LOG_TAG, "Unexpected atom tag: %d", atomTag); + return android.app.StatsManager.PULL_SKIP; } return android.app.StatsManager.PULL_SUCCESS; } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index d645bb211bc3..05cb42973a00 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -3245,8 +3245,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override public void onKeyguardOccludedChangedLw(boolean occluded) { - if (mKeyguardDelegate != null && mKeyguardDelegate.isShowing() - && !WindowManagerService.sEnableShellTransitions) { + if (mKeyguardDelegate != null && mKeyguardDelegate.isShowing()) { mPendingKeyguardOccluded = occluded; mKeyguardOccludedChanged = true; } else { @@ -5107,18 +5106,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { /** {@inheritDoc} */ @Override - public void userActivity() { - // *************************************** - // NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE - // *************************************** - // THIS IS CALLED FROM DEEP IN THE POWER MANAGER - // WITH ITS LOCKS HELD. - // - // This code must be VERY careful about the locks - // it acquires. - // In fact, the current code acquires way too many, - // and probably has lurking deadlocks. - + public void userActivity(int displayGroupId, int event) { + if (displayGroupId == DEFAULT_DISPLAY && event == PowerManager.USER_ACTIVITY_EVENT_TOUCH) { + mDefaultDisplayPolicy.onUserActivityEventTouch(); + } synchronized (mScreenLockTimeout) { if (mLockScreenTimerActive) { // reset the timer diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index e8a3dcd5635f..4f00992c713e 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -1006,7 +1006,7 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { * Called when userActivity is signalled in the power manager. * This is safe to call from any thread, with any window manager locks held or not. */ - public void userActivity(); + void userActivity(int displayGroupId, int event); /** * Called when we have finished booting and can now display the home diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java index 685b744c8062..dad9584c6722 100644 --- a/services/core/java/com/android/server/power/Notifier.java +++ b/services/core/java/com/android/server/power/Notifier.java @@ -571,7 +571,8 @@ public class Notifier { /** * Called when there has been user activity. */ - public void onUserActivity(int displayGroupId, int event, int uid) { + public void onUserActivity(int displayGroupId, @PowerManager.UserActivityEvent int event, + int uid) { if (DEBUG) { Slog.d(TAG, "onUserActivity: event=" + event + ", uid=" + uid); } @@ -712,7 +713,7 @@ public class Notifier { } TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); tm.notifyUserActivity(); - mPolicy.userActivity(); + mPolicy.userActivity(displayGroupId, event); mFaceDownDetector.userActivity(event); mScreenUndimDetector.userActivity(displayGroupId); } diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java index fec61ac8f2cf..9fe53fbfaf25 100644 --- a/services/core/java/com/android/server/power/PowerGroup.java +++ b/services/core/java/com/android/server/power/PowerGroup.java @@ -74,6 +74,8 @@ public class PowerGroup { private long mLastPowerOnTime; private long mLastUserActivityTime; private long mLastUserActivityTimeNoChangeLights; + @PowerManager.UserActivityEvent + private int mLastUserActivityEvent; /** Timestamp (milliseconds since boot) of the last time the power group was awoken.*/ private long mLastWakeTime; /** Timestamp (milliseconds since boot) of the last time the power group was put to sleep. */ @@ -244,7 +246,7 @@ public class PowerGroup { return true; } - boolean dozeLocked(long eventTime, int uid, int reason) { + boolean dozeLocked(long eventTime, int uid, @PowerManager.GoToSleepReason int reason) { if (eventTime < getLastWakeTimeLocked() || !isInteractive(mWakefulness)) { return false; } @@ -253,9 +255,14 @@ public class PowerGroup { try { reason = Math.min(PowerManager.GO_TO_SLEEP_REASON_MAX, Math.max(reason, PowerManager.GO_TO_SLEEP_REASON_MIN)); + long millisSinceLastUserActivity = eventTime - Math.max( + mLastUserActivityTimeNoChangeLights, mLastUserActivityTime); Slog.i(TAG, "Powering off display group due to " - + PowerManager.sleepReasonToString(reason) + " (groupId= " + getGroupId() - + ", uid= " + uid + ")..."); + + PowerManager.sleepReasonToString(reason) + + " (groupId= " + getGroupId() + ", uid= " + uid + + ", millisSinceLastUserActivity=" + millisSinceLastUserActivity + + ", lastUserActivityEvent=" + PowerManager.userActivityEventToString( + mLastUserActivityEvent) + ")..."); setSandmanSummonedLocked(/* isSandmanSummoned= */ true); setWakefulnessLocked(WAKEFULNESS_DOZING, eventTime, uid, reason, /* opUid= */ 0, @@ -266,14 +273,16 @@ public class PowerGroup { return true; } - boolean sleepLocked(long eventTime, int uid, int reason) { + boolean sleepLocked(long eventTime, int uid, @PowerManager.GoToSleepReason int reason) { if (eventTime < mLastWakeTime || getWakefulnessLocked() == WAKEFULNESS_ASLEEP) { return false; } Trace.traceBegin(Trace.TRACE_TAG_POWER, "sleepPowerGroup"); try { - Slog.i(TAG, "Sleeping power group (groupId=" + getGroupId() + ", uid=" + uid + ")..."); + Slog.i(TAG, + "Sleeping power group (groupId=" + getGroupId() + ", uid=" + uid + ", reason=" + + PowerManager.sleepReasonToString(reason) + ")..."); setSandmanSummonedLocked(/* isSandmanSummoned= */ true); setWakefulnessLocked(WAKEFULNESS_ASLEEP, eventTime, uid, reason, /* opUid= */0, /* opPackageName= */ null, /* details= */ null); @@ -287,16 +296,20 @@ public class PowerGroup { return mLastUserActivityTime; } - void setLastUserActivityTimeLocked(long lastUserActivityTime) { + void setLastUserActivityTimeLocked(long lastUserActivityTime, + @PowerManager.UserActivityEvent int event) { mLastUserActivityTime = lastUserActivityTime; + mLastUserActivityEvent = event; } public long getLastUserActivityTimeNoChangeLightsLocked() { return mLastUserActivityTimeNoChangeLights; } - public void setLastUserActivityTimeNoChangeLightsLocked(long time) { + public void setLastUserActivityTimeNoChangeLightsLocked(long time, + @PowerManager.UserActivityEvent int event) { mLastUserActivityTimeNoChangeLights = time; + mLastUserActivityEvent = event; } public int getUserActivitySummaryLocked() { diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index dbf05f1cd7c7..bc93cb30e449 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -1169,6 +1169,7 @@ public final class PowerManagerService extends SystemService return; } + Slog.i(TAG, "onFlip(): Face " + (isFaceDown ? "down." : "up.")); mIsFaceDown = isFaceDown; if (isFaceDown) { final long currentTime = mClock.uptimeMillis(); @@ -1888,12 +1889,13 @@ public final class PowerManagerService extends SystemService // Called from native code. @SuppressWarnings("unused") - private void userActivityFromNative(long eventTime, int event, int displayId, int flags) { + private void userActivityFromNative(long eventTime, @PowerManager.UserActivityEvent int event, + int displayId, int flags) { userActivityInternal(displayId, eventTime, event, flags, Process.SYSTEM_UID); } - private void userActivityInternal(int displayId, long eventTime, int event, int flags, - int uid) { + private void userActivityInternal(int displayId, long eventTime, + @PowerManager.UserActivityEvent int event, int flags, int uid) { synchronized (mLock) { if (displayId == Display.INVALID_DISPLAY) { if (userActivityNoUpdateLocked(eventTime, event, flags, uid)) { @@ -1944,11 +1946,12 @@ public final class PowerManagerService extends SystemService @GuardedBy("mLock") private boolean userActivityNoUpdateLocked(final PowerGroup powerGroup, long eventTime, - int event, int flags, int uid) { + @PowerManager.UserActivityEvent int event, int flags, int uid) { final int groupId = powerGroup.getGroupId(); if (DEBUG_SPEW) { Slog.d(TAG, "userActivityNoUpdateLocked: groupId=" + groupId - + ", eventTime=" + eventTime + ", event=" + event + + ", eventTime=" + eventTime + + ", event=" + PowerManager.userActivityEventToString(event) + ", flags=0x" + Integer.toHexString(flags) + ", uid=" + uid); } @@ -1983,7 +1986,7 @@ public final class PowerManagerService extends SystemService if ((flags & PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS) != 0) { if (eventTime > powerGroup.getLastUserActivityTimeNoChangeLightsLocked() && eventTime > powerGroup.getLastUserActivityTimeLocked()) { - powerGroup.setLastUserActivityTimeNoChangeLightsLocked(eventTime); + powerGroup.setLastUserActivityTimeNoChangeLightsLocked(eventTime, event); mDirty |= DIRTY_USER_ACTIVITY; if (event == PowerManager.USER_ACTIVITY_EVENT_BUTTON) { mDirty |= DIRTY_QUIESCENT; @@ -1993,7 +1996,7 @@ public final class PowerManagerService extends SystemService } } else { if (eventTime > powerGroup.getLastUserActivityTimeLocked()) { - powerGroup.setLastUserActivityTimeLocked(eventTime); + powerGroup.setLastUserActivityTimeLocked(eventTime, event); mDirty |= DIRTY_USER_ACTIVITY; if (event == PowerManager.USER_ACTIVITY_EVENT_BUTTON) { mDirty |= DIRTY_QUIESCENT; @@ -2020,7 +2023,8 @@ public final class PowerManagerService extends SystemService @WakeReason int reason, String details, int uid, String opPackageName, int opUid) { if (DEBUG_SPEW) { Slog.d(TAG, "wakePowerGroupLocked: eventTime=" + eventTime - + ", groupId=" + powerGroup.getGroupId() + ", uid=" + uid); + + ", groupId=" + powerGroup.getGroupId() + + ", reason=" + PowerManager.wakeReasonToString(reason) + ", uid=" + uid); } if (mForceSuspendActive || !mSystemReady) { return; @@ -2043,11 +2047,11 @@ public final class PowerManagerService extends SystemService @GuardedBy("mLock") private boolean dozePowerGroupLocked(final PowerGroup powerGroup, long eventTime, - int reason, int uid) { + @GoToSleepReason int reason, int uid) { if (DEBUG_SPEW) { Slog.d(TAG, "dozePowerGroup: eventTime=" + eventTime - + ", groupId=" + powerGroup.getGroupId() + ", reason=" + reason - + ", uid=" + uid); + + ", groupId=" + powerGroup.getGroupId() + + ", reason=" + PowerManager.sleepReasonToString(reason) + ", uid=" + uid); } if (!mSystemReady || !mBootCompleted) { @@ -2058,10 +2062,12 @@ public final class PowerManagerService extends SystemService } @GuardedBy("mLock") - private boolean sleepPowerGroupLocked(final PowerGroup powerGroup, long eventTime, int reason, - int uid) { + private boolean sleepPowerGroupLocked(final PowerGroup powerGroup, long eventTime, + @GoToSleepReason int reason, int uid) { if (DEBUG_SPEW) { - Slog.d(TAG, "sleepPowerGroup: eventTime=" + eventTime + ", uid=" + uid); + Slog.d(TAG, "sleepPowerGroup: eventTime=" + eventTime + + ", groupId=" + powerGroup.getGroupId() + + ", reason=" + PowerManager.sleepReasonToString(reason) + ", uid=" + uid); } if (!mBootCompleted || !mSystemReady) { return false; @@ -2122,7 +2128,10 @@ public final class PowerManagerService extends SystemService case WAKEFULNESS_DOZING: traceMethodName = "goToSleep"; Slog.i(TAG, "Going to sleep due to " + PowerManager.sleepReasonToString(reason) - + " (uid " + uid + ")..."); + + " (uid " + uid + ", screenOffTimeout=" + mScreenOffTimeoutSetting + + ", activityTimeoutWM=" + mUserActivityTimeoutOverrideFromWindowManager + + ", maxDimRatio=" + mMaximumScreenDimRatioConfig + + ", maxDimDur=" + mMaximumScreenDimDurationConfig + ")..."); mLastGlobalSleepTime = eventTime; mLastGlobalSleepReason = reason; @@ -4207,7 +4216,7 @@ public final class PowerManagerService extends SystemService void onUserActivity() { synchronized (mLock) { mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP).setLastUserActivityTimeLocked( - mClock.uptimeMillis()); + mClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_OTHER); } } @@ -5590,7 +5599,8 @@ public final class PowerManagerService extends SystemService } @Override // Binder call - public void userActivity(int displayId, long eventTime, int event, int flags) { + public void userActivity(int displayId, long eventTime, + @PowerManager.UserActivityEvent int event, int flags) { final long now = mClock.uptimeMillis(); if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER) != PackageManager.PERMISSION_GRANTED diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java index e7c4ba5b9efe..d2413f015003 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java @@ -70,6 +70,7 @@ import static com.android.internal.util.FrameworkStatsLog.CAMERA_COMPAT_CONTROL_ import static com.android.internal.util.FrameworkStatsLog.CAMERA_COMPAT_CONTROL_EVENT_REPORTED__EVENT__CLICKED_REVERT_TREATMENT; import static com.android.server.am.MemoryStatUtil.MemoryStat; import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem; +import static com.android.server.am.ProcessList.INVALID_ADJ; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_METRICS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -277,6 +278,8 @@ class ActivityMetricsLogger { final boolean mProcessSwitch; /** The process state of the launching activity prior to the launch */ final int mProcessState; + /** The oom adj score of the launching activity prior to the launch */ + final int mProcessOomAdj; /** Whether the last launched activity has reported drawn. */ boolean mIsDrawn; /** The latest activity to have been launched. */ @@ -312,7 +315,7 @@ class ActivityMetricsLogger { @Nullable static TransitionInfo create(@NonNull ActivityRecord r, @NonNull LaunchingState launchingState, @Nullable ActivityOptions options, - boolean processRunning, boolean processSwitch, int processState, + boolean processRunning, boolean processSwitch, int processState, int processOomAdj, boolean newActivityCreated, int startResult) { if (startResult != START_SUCCESS && startResult != START_TASK_TO_FRONT) { return null; @@ -328,19 +331,20 @@ class ActivityMetricsLogger { transitionType = TYPE_TRANSITION_COLD_LAUNCH; } return new TransitionInfo(r, launchingState, options, transitionType, processRunning, - processSwitch, processState); + processSwitch, processState, processOomAdj); } /** Use {@link TransitionInfo#create} instead to ensure the transition type is valid. */ private TransitionInfo(ActivityRecord r, LaunchingState launchingState, ActivityOptions options, int transitionType, boolean processRunning, - boolean processSwitch, int processState) { + boolean processSwitch, int processState, int processOomAdj) { mLaunchingState = launchingState; mTransitionStartTimeNs = launchingState.mCurrentTransitionStartTimeNs; mTransitionType = transitionType; mProcessRunning = processRunning; mProcessSwitch = processSwitch; mProcessState = processState; + mProcessOomAdj = processOomAdj; mTransitionDeviceUptimeMs = launchingState.mCurrentUpTimeMs; setLatestLaunchedActivity(r); // The launching state can be reused by consecutive launch. Its original association @@ -644,9 +648,15 @@ class ActivityMetricsLogger { // interesting. final boolean processSwitch = !processRunning || !processRecord.hasStartedActivity(launchedActivity); - final int processState = processRunning - ? processRecord.getCurrentProcState() - : PROCESS_STATE_NONEXISTENT; + final int processState; + final int processOomAdj; + if (processRunning) { + processState = processRecord.getCurrentProcState(); + processOomAdj = processRecord.getCurrentAdj(); + } else { + processState = PROCESS_STATE_NONEXISTENT; + processOomAdj = INVALID_ADJ; + } final TransitionInfo info = launchingState.mAssociatedTransitionInfo; if (DEBUG_METRICS) { @@ -654,6 +664,7 @@ class ActivityMetricsLogger { + " launchedActivity=" + launchedActivity + " processRunning=" + processRunning + " processSwitch=" + processSwitch + " processState=" + processState + + " processOomAdj=" + processOomAdj + " newActivityCreated=" + newActivityCreated + " info=" + info); } @@ -689,8 +700,8 @@ class ActivityMetricsLogger { } final TransitionInfo newInfo = TransitionInfo.create(launchedActivity, launchingState, - options, processRunning, processSwitch, processState, newActivityCreated, - resultCode); + options, processRunning, processSwitch, processState, processOomAdj, + newActivityCreated, resultCode); if (newInfo == null) { abort(launchingState, "unrecognized launch"); return; @@ -1005,8 +1016,10 @@ class ActivityMetricsLogger { final long uptime = info.mTransitionDeviceUptimeMs; final int transitionDelay = info.mCurrentTransitionDelayMs; final int processState = info.mProcessState; + final int processOomAdj = info.mProcessOomAdj; mLoggerHandler.post(() -> logAppTransition( - timestamp, uptime, transitionDelay, infoSnapshot, isHibernating, processState)); + timestamp, uptime, transitionDelay, infoSnapshot, isHibernating, + processState, processOomAdj)); } mLoggerHandler.post(() -> logAppDisplayed(infoSnapshot)); if (info.mPendingFullyDrawn != null) { @@ -1019,7 +1032,7 @@ class ActivityMetricsLogger { // This gets called on another thread without holding the activity manager lock. private void logAppTransition(long transitionStartTimeNs, long transitionDeviceUptimeMs, int currentTransitionDelayMs, TransitionInfoSnapshot info, boolean isHibernating, - int processState) { + int processState, int processOomAdj) { final LogMaker builder = new LogMaker(APP_TRANSITION); builder.setPackageName(info.packageName); builder.setType(info.type); @@ -1086,7 +1099,8 @@ class ActivityMetricsLogger { isLoading, info.launchedActivityName.hashCode(), TimeUnit.NANOSECONDS.toMillis(transitionStartTimeNs), - processState); + processState, + processOomAdj); if (DEBUG_METRICS) { Slog.i(TAG, String.format("APP_START_OCCURRED(%s, %s, %s, %s, %s)", diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index bd88c41b5268..a174c54eae98 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -313,7 +313,6 @@ import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; import android.util.proto.ProtoOutputStream; import android.view.AppTransitionAnimationSpec; -import android.view.DisplayCutout; import android.view.DisplayInfo; import android.view.IAppTransitionAnimationSpecsFuture; import android.view.InputApplicationHandle; @@ -357,6 +356,7 @@ import com.android.server.wm.ActivityMetricsLogger.TransitionInfoSnapshot; import com.android.server.wm.SurfaceAnimator.AnimationType; import com.android.server.wm.WindowManagerService.H; import com.android.server.wm.utils.InsetUtils; +import com.android.server.wm.utils.WmDisplayCutout; import dalvik.annotation.optimization.NeverCompile; @@ -660,6 +660,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A boolean mUseTransferredAnimation; + /** Whether we need to setup the animation to animate only within the letterbox. */ + private boolean mNeedsLetterboxedAnimation; + /** * @see #currentLaunchCanTurnScreenOn() */ @@ -1762,8 +1765,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mLetterboxUiController.layoutLetterbox(winHint); } - boolean hasWallpaperBackgroudForLetterbox() { - return mLetterboxUiController.hasWallpaperBackgroudForLetterbox(); + boolean hasWallpaperBackgroundForLetterbox() { + return mLetterboxUiController.hasWallpaperBackgroundForLetterbox(); + } + + void updateLetterboxSurface(WindowState winHint, Transaction t) { + mLetterboxUiController.updateLetterboxSurface(winHint, t); } void updateLetterboxSurface(WindowState winHint) { @@ -3247,7 +3254,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A rootTask.moveToFront(reason, task); // Report top activity change to tracking services and WM if (mRootWindowContainer.getTopResumedActivity() == this) { - mAtmService.setResumedActivityUncheckLocked(this, reason); + mAtmService.setLastResumedActivityUncheckLocked(this, reason); } return true; } @@ -5334,6 +5341,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A commitVisibility(visible, performLayout, false /* fromTransition */); } + void setNeedsLetterboxedAnimation(boolean needsLetterboxedAnimation) { + mNeedsLetterboxedAnimation = needsLetterboxedAnimation; + } + + boolean isNeedsLetterboxedAnimation() { + return mNeedsLetterboxedAnimation; + } + + boolean isInLetterboxAnimation() { + return mNeedsLetterboxedAnimation && isAnimating(); + } + /** * Post process after applying an app transition animation. * @@ -5877,7 +5896,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A try { mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token, PauseActivityItem.obtain(finishing, false /* userLeaving */, - configChangeFlags, false /* dontReport */)); + configChangeFlags, false /* dontReport */, + false /* autoEnteringPip */)); } catch (Exception e) { Slog.w(TAG, "Exception thrown sending pause: " + intent.getComponent(), e); } @@ -7261,6 +7281,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A .setParent(getAnimationLeashParent()) .setName(getSurfaceControl() + " - animation-bounds") .setCallsite("ActivityRecord.createAnimationBoundsLayer"); + if (mNeedsLetterboxedAnimation) { + // Needs to be an effect layer to support rounded corners + builder.setEffectLayer(); + } final SurfaceControl boundsLayer = builder.build(); t.show(boundsLayer); return boundsLayer; @@ -7298,6 +7322,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mAnimatingActivityRegistry.notifyStarting(this); } + if (mNeedsLetterboxedAnimation) { + updateLetterboxSurface(findMainWindow(), t); + mNeedsAnimationBoundsLayer = true; + } + // If the animation needs to be cropped then an animation bounds layer is created as a // child of the root pinned task or animation layer. The leash is then reparented to this // new layer. @@ -7320,6 +7349,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A t.setLayer(leash, 0); t.setLayer(mAnimationBoundsLayer, getLastLayer()); + if (mNeedsLetterboxedAnimation) { + final int cornerRadius = mLetterboxUiController + .getRoundedCornersRadius(findMainWindow()); + + final Rect letterboxInnerBounds = new Rect(); + getLetterboxInnerBounds(letterboxInnerBounds); + + t.setCornerRadius(mAnimationBoundsLayer, cornerRadius) + .setCrop(mAnimationBoundsLayer, letterboxInnerBounds); + } + // Reparent leash to animation bounds layer. t.reparent(leash, mAnimationBoundsLayer); } @@ -7433,6 +7473,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mAnimationBoundsLayer = null; } + mNeedsAnimationBoundsLayer = false; + if (mNeedsLetterboxedAnimation) { + mNeedsLetterboxedAnimation = false; + updateLetterboxSurface(findMainWindow(), t); + } + if (mAnimatingActivityRegistry != null) { mAnimatingActivityRegistry.notifyFinished(this); } @@ -7445,7 +7491,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AR#onAnimationFinished"); mTransit = TRANSIT_OLD_UNSET; mTransitFlags = 0; - mNeedsAnimationBoundsLayer = false; setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM | FINISH_LAYOUT_REDO_WALLPAPER, "ActivityRecord"); @@ -9649,9 +9694,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); final int dw = rotated ? display.mBaseDisplayHeight : display.mBaseDisplayWidth; final int dh = rotated ? display.mBaseDisplayWidth : display.mBaseDisplayHeight; - final DisplayCutout cutout = display.calculateDisplayCutoutForRotation(rotation) - .getDisplayCutout(); - policy.getNonDecorInsetsLw(rotation, cutout, mNonDecorInsets[rotation]); + final WmDisplayCutout cutout = display.calculateDisplayCutoutForRotation(rotation); + policy.getNonDecorInsetsLw(rotation, dw, dh, cutout, mNonDecorInsets[rotation]); mStableInsets[rotation].set(mNonDecorInsets[rotation]); policy.convertNonDecorInsetsToStableInsets(mStableInsets[rotation], rotation); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 73237189da16..31f87416db67 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -226,6 +226,7 @@ import android.view.IWindowFocusObserver; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationDefinition; import android.view.WindowManager; +import android.window.BackAnimationAdaptor; import android.window.BackNavigationInfo; import android.window.IWindowOrganizerController; import android.window.SplashScreenView.SplashScreenViewParcelable; @@ -311,7 +312,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { /** * The duration to keep a process in animating state (top scheduling group) when the - * wakefulness is changing from awake to doze or sleep. + * wakefulness is dozing (unlocking) or changing from awake to doze or sleep (locking). */ private static final long DOZE_ANIMATING_STATE_RETAIN_TIME_MS = 2000; @@ -457,7 +458,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { private final ClientLifecycleManager mLifecycleManager; @Nullable - private final BackNavigationController mBackNavigationController; + final BackNavigationController mBackNavigationController; private TaskChangeNotificationController mTaskChangeNotificationController; /** The controller for all operations related to locktask. */ @@ -1486,6 +1487,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { a.colorMode = ActivityInfo.COLOR_MODE_DEFAULT; a.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS; a.resizeMode = RESIZE_MODE_UNRESIZEABLE; + a.configChanges = ActivityInfo.CONFIG_ORIENTATION; final ActivityOptions options = ActivityOptions.makeBasic(); options.setLaunchActivityType(ACTIVITY_TYPE_DREAM); @@ -1835,13 +1837,14 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Override public BackNavigationInfo startBackNavigation(boolean requestAnimation, - IWindowFocusObserver observer) { + IWindowFocusObserver observer, BackAnimationAdaptor backAnimationAdaptor) { mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS, "startBackNavigation()"); if (mBackNavigationController == null) { return null; } - return mBackNavigationController.startBackNavigation(requestAnimation, observer); + return mBackNavigationController.startBackNavigation( + requestAnimation, observer, backAnimationAdaptor); } /** @@ -2927,12 +2930,14 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mDemoteTopAppReasons &= ~DEMOTE_TOP_REASON_DURING_UNLOCKING; final WindowState notificationShade = mRootWindowContainer.getDefaultDisplay() .getDisplayPolicy().getNotificationShade(); - proc = notificationShade != null - ? mProcessMap.getProcess(notificationShade.mSession.mPid) : null; - } - if (proc == null) { - return; + proc = notificationShade != null ? notificationShade.getProcess() : null; } + setProcessAnimatingWhileDozing(proc); + } + + // The caller MUST NOT hold the global lock because it calls AM method directly. + void setProcessAnimatingWhileDozing(WindowProcessController proc) { + if (proc == null) return; // Set to activity manager directly to make sure the state can be seen by the subsequent // update of scheduling group. proc.setRunningAnimationUnsafe(); @@ -3560,7 +3565,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { // Continue the pausing process after entering pip. if (r.isState(PAUSING)) { r.getTask().schedulePauseActivity(r, false /* userLeaving */, - false /* pauseImmediately */, "auto-pip"); + false /* pauseImmediately */, true /* autoEnteringPip */, "auto-pip"); } } }; @@ -4621,7 +4626,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } /** Update AMS states when an activity is resumed. */ - void setResumedActivityUncheckLocked(ActivityRecord r, String reason) { + void setLastResumedActivityUncheckLocked(ActivityRecord r, String reason) { final Task task = r.getTask(); if (task.isActivityTypeStandard()) { if (mCurAppTimeTracker != r.appTimeTracker) { diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 208b001dfd0e..a870b8afe2f9 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -2083,7 +2083,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { * activity releases the top state and reports back, message about acquiring top state will be * sent to the new top resumed activity. */ - void updateTopResumedActivityIfNeeded() { + void updateTopResumedActivityIfNeeded(String reason) { final ActivityRecord prevTopActivity = mTopResumedActivity; final Task topRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask(); if (topRootTask == null || topRootTask.getTopResumedActivity() == prevTopActivity) { @@ -2119,6 +2119,12 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } mService.updateOomAdj(); } + // Update the last resumed activity and focused app when the top resumed activity changed + // because the new top resumed activity might be already resumed and thus won't have + // activity state change to update the records to AMS. + if (mTopResumedActivity != null) { + mService.setLastResumedActivityUncheckLocked(mTopResumedActivity, reason); + } scheduleTopResumedActivityStateIfNeeded(); mService.updateTopApp(mTopResumedActivity); diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index fb9d7e602210..5ac5f2e4bdaf 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -81,9 +81,11 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.graphics.Rect; import android.os.Trace; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Pair; import android.util.Slog; import android.view.Display; import android.view.RemoteAnimationAdapter; @@ -1028,6 +1030,32 @@ public class AppTransitionController { return; } + if (AppTransition.isActivityTransitOld(transit)) { + final ArrayList<Pair<ActivityRecord, Rect>> closingLetterboxes = new ArrayList(); + for (int i = 0; i < closingApps.size(); ++i) { + ActivityRecord closingApp = closingApps.valueAt(i); + if (closingApp.areBoundsLetterboxed()) { + final Rect insets = closingApp.getLetterboxInsets(); + closingLetterboxes.add(new Pair(closingApp, insets)); + } + } + + for (int i = 0; i < openingApps.size(); ++i) { + ActivityRecord openingApp = openingApps.valueAt(i); + if (openingApp.areBoundsLetterboxed()) { + final Rect openingInsets = openingApp.getLetterboxInsets(); + for (Pair<ActivityRecord, Rect> closingLetterbox : closingLetterboxes) { + final Rect closingInsets = closingLetterbox.second; + if (openingInsets.equals(closingInsets)) { + ActivityRecord closingApp = closingLetterbox.first; + openingApp.setNeedsLetterboxedAnimation(true); + closingApp.setNeedsLetterboxedAnimation(true); + } + } + } + } + } + final ArraySet<WindowContainer> openingWcs = getAnimationTargets( openingApps, closingApps, true /* visible */); final ArraySet<WindowContainer> closingWcs = getAnimationTargets( diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java index 9a94a4f54b61..d3452277a29f 100644 --- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java +++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java @@ -22,6 +22,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE; import static com.android.server.wm.WindowState.BLAST_TIMEOUT_DURATION; import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Trace; import android.util.ArraySet; import android.util.Slog; @@ -63,6 +64,15 @@ import java.util.ArrayList; class BLASTSyncEngine { private static final String TAG = "BLASTSyncEngine"; + /** No specific method. Used by override specifiers. */ + public static final int METHOD_UNDEFINED = -1; + + /** No sync method. Apps will draw/present internally and just report. */ + public static final int METHOD_NONE = 0; + + /** Sync with BLAST. Apps will draw and then send the buffer to be applied in sync. */ + public static final int METHOD_BLAST = 1; + interface TransactionReadyListener { void onTransactionReady(int mSyncId, SurfaceControl.Transaction transaction); } @@ -85,6 +95,7 @@ class BLASTSyncEngine { */ class SyncGroup { final int mSyncId; + final int mSyncMethod; final TransactionReadyListener mListener; final Runnable mOnTimeout; boolean mReady = false; @@ -92,8 +103,9 @@ class BLASTSyncEngine { private SurfaceControl.Transaction mOrphanTransaction = null; private String mTraceName; - private SyncGroup(TransactionReadyListener listener, int id, String name) { + private SyncGroup(TransactionReadyListener listener, int id, String name, int method) { mSyncId = id; + mSyncMethod = method; mListener = listener; mOnTimeout = () -> { Slog.w(TAG, "Sync group " + mSyncId + " timeout"); @@ -271,16 +283,13 @@ class BLASTSyncEngine { * Prepares a {@link SyncGroup} that is not active yet. Caller must call {@link #startSyncSet} * before calling {@link #addToSyncSet(int, WindowContainer)} on any {@link WindowContainer}. */ - SyncGroup prepareSyncSet(TransactionReadyListener listener, String name) { - return new SyncGroup(listener, mNextSyncId++, name); + SyncGroup prepareSyncSet(TransactionReadyListener listener, String name, int method) { + return new SyncGroup(listener, mNextSyncId++, name, method); } - int startSyncSet(TransactionReadyListener listener) { - return startSyncSet(listener, BLAST_TIMEOUT_DURATION, ""); - } - - int startSyncSet(TransactionReadyListener listener, long timeoutMs, String name) { - final SyncGroup s = prepareSyncSet(listener, name); + int startSyncSet(TransactionReadyListener listener, long timeoutMs, String name, + int method) { + final SyncGroup s = prepareSyncSet(listener, name, method); startSyncSet(s, timeoutMs); return s.mSyncId; } @@ -302,6 +311,11 @@ class BLASTSyncEngine { scheduleTimeout(s, timeoutMs); } + @Nullable + SyncGroup getSyncSet(int id) { + return mActiveSyncs.get(id); + } + boolean hasActiveSync() { return mActiveSyncs.size() != 0; } diff --git a/services/core/java/com/android/server/wm/BackNaviAnimationController.java b/services/core/java/com/android/server/wm/BackNaviAnimationController.java new file mode 100644 index 000000000000..ecc7534ad386 --- /dev/null +++ b/services/core/java/com/android/server/wm/BackNaviAnimationController.java @@ -0,0 +1,418 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.app.ActivityTaskManager.INVALID_TASK_ID; +import static android.view.RemoteAnimationTarget.MODE_CLOSING; +import static android.view.RemoteAnimationTarget.MODE_OPENING; +import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; + +import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS; + +import android.annotation.NonNull; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.SystemClock; +import android.util.Slog; +import android.util.proto.ProtoOutputStream; +import android.view.RemoteAnimationTarget; +import android.view.SurfaceControl; +import android.view.WindowInsets; +import android.window.BackNavigationInfo; +import android.window.IBackAnimationRunner; +import android.window.IBackNaviAnimationController; + +import com.android.server.wm.utils.InsetUtils; + +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * Controls the back navigation animation. + * This is throw-away code and should only be used for Android T, most code is duplicated from + * RecentsAnimationController which should be stable to handle animation leash resources/flicker/ + * fixed rotation, etc. Remove this class at U and migrate to shell transition. + */ +public class BackNaviAnimationController implements IBinder.DeathRecipient { + private static final String TAG = BackNavigationController.TAG; + // Constant for a yet-to-be-calculated {@link RemoteAnimationTarget#Mode} state + private static final int MODE_UNKNOWN = -1; + + // The activity which host this animation + private ActivityRecord mTargetActivityRecord; + // The original top activity + private ActivityRecord mTopActivity; + + private final DisplayContent mDisplayContent; + private final WindowManagerService mWindowManagerService; + private final BackNavigationController mBackNavigationController; + + // We start the BackAnimationController in a pending-start state since we need to wait for + // the wallpaper/activity to draw before we can give control to the handler to start animating + // the visible task surfaces + private boolean mPendingStart; + private IBackAnimationRunner mRunner; + final IBackNaviAnimationController mRemoteController; + private boolean mLinkedToDeathOfRunner; + + private final ArrayList<TaskAnimationAdapter> mPendingAnimations = new ArrayList<>(); + + BackNaviAnimationController(IBackAnimationRunner runner, + BackNavigationController backNavigationController, int displayId) { + mRunner = runner; + mBackNavigationController = backNavigationController; + mWindowManagerService = mBackNavigationController.mWindowManagerService; + mDisplayContent = mWindowManagerService.mRoot.getDisplayContent(displayId); + + mRemoteController = new IBackNaviAnimationController.Stub() { + @Override + public void finish(boolean triggerBack) { + synchronized (mWindowManagerService.getWindowManagerLock()) { + final long token = Binder.clearCallingIdentity(); + try { + mWindowManagerService.inSurfaceTransaction(() -> { + mWindowManagerService.mAtmService.deferWindowLayout(); + try { + if (triggerBack) { + mDisplayContent.mFixedRotationTransitionListener + .notifyRecentsWillBeTop(); + if (mTopActivity != null) { + mWindowManagerService.mTaskSnapshotController + .recordTaskSnapshot(mTopActivity.getTask(), false); + // TODO consume moveTaskToBack? + mTopActivity.commitVisibility(false, false, true); + } + } else { + mTargetActivityRecord.mTaskSupervisor + .scheduleLaunchTaskBehindComplete( + mTargetActivityRecord.token); + } + cleanupAnimation(); + } finally { + mWindowManagerService.mAtmService.continueWindowLayout(); + } + }); + } finally { + Binder.restoreCallingIdentity(token); + } + } + } + }; + } + + /** + * @param targetActivity The home or opening activity which should host the wallpaper + * @param topActivity The current top activity before animation start. + */ + void initialize(ActivityRecord targetActivity, ActivityRecord topActivity) { + mTargetActivityRecord = targetActivity; + mTopActivity = topActivity; + final Task topTask = mTopActivity.getTask(); + + createAnimationAdapter(topTask, (type, anim) -> topTask.forAllWindows( + win -> { + win.onAnimationFinished(type, anim); + }, true)); + final Task homeTask = mTargetActivityRecord.getRootTask(); + createAnimationAdapter(homeTask, (type, anim) -> homeTask.forAllWindows( + win -> { + win.onAnimationFinished(type, anim); + }, true)); + try { + linkToDeathOfRunner(); + } catch (RemoteException e) { + cancelAnimation(); + return; + } + + if (targetActivity.windowsCanBeWallpaperTarget()) { + mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; + mDisplayContent.setLayoutNeeded(); + } + + mWindowManagerService.mWindowPlacerLocked.performSurfacePlacement(); + + mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(targetActivity); + mPendingStart = true; + } + + void cleanupAnimation() { + for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { + final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i); + + removeAnimationAdapter(taskAdapter); + taskAdapter.onCleanup(); + } + mTargetActivityRecord.mLaunchTaskBehind = false; + // Clear references to the runner + unlinkToDeathOfRunner(); + mRunner = null; + + // Update the input windows after the animation is complete + final InputMonitor inputMonitor = mDisplayContent.getInputMonitor(); + inputMonitor.updateInputWindowsLw(true /*force*/); + + mDisplayContent.mFixedRotationTransitionListener.onFinishRecentsAnimation(); + mBackNavigationController.finishAnimation(); + } + + void removeAnimationAdapter(TaskAnimationAdapter taskAdapter) { + taskAdapter.onRemove(); + mPendingAnimations.remove(taskAdapter); + } + + void checkAnimationReady(WallpaperController wallpaperController) { + if (mPendingStart) { + final boolean wallpaperReady = !isTargetOverWallpaper() + || (wallpaperController.getWallpaperTarget() != null + && wallpaperController.wallpaperTransitionReady()); + if (wallpaperReady) { + startAnimation(); + } + } + } + + boolean isWallpaperVisible(WindowState w) { + return w != null && w.mAttrs.type == TYPE_BASE_APPLICATION + && ((w.mActivityRecord != null && mTargetActivityRecord == w.mActivityRecord) + || isAnimatingTask(w.getTask())) + && isTargetOverWallpaper() && w.isOnScreen(); + } + + boolean isAnimatingTask(Task task) { + for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { + if (task == mPendingAnimations.get(i).mTask) { + return true; + } + } + return false; + } + + void linkFixedRotationTransformIfNeeded(@NonNull WindowToken wallpaper) { + if (mTargetActivityRecord == null) { + return; + } + wallpaper.linkFixedRotationTransform(mTargetActivityRecord); + } + + private void linkToDeathOfRunner() throws RemoteException { + if (!mLinkedToDeathOfRunner) { + mRunner.asBinder().linkToDeath(this, 0); + mLinkedToDeathOfRunner = true; + } + } + + private void unlinkToDeathOfRunner() { + if (mLinkedToDeathOfRunner) { + mRunner.asBinder().unlinkToDeath(this, 0); + mLinkedToDeathOfRunner = false; + } + } + + void startAnimation() { + if (!mPendingStart) { + // Skip starting if we've already started or canceled the animation + return; + } + // Create the app targets + final RemoteAnimationTarget[] appTargets = createAppAnimations(); + + // Skip the animation if there is nothing to animate + if (appTargets.length == 0) { + cancelAnimation(); + return; + } + + mPendingStart = false; + + try { + mRunner.onAnimationStart(mRemoteController, BackNavigationInfo.TYPE_RETURN_TO_HOME, + appTargets, null /* wallpapers */, null /*nonApps*/); + } catch (RemoteException e) { + cancelAnimation(); + } + } + + @Override + public void binderDied() { + cancelAnimation(); + } + + TaskAnimationAdapter createAnimationAdapter(Task task, + SurfaceAnimator.OnAnimationFinishedCallback finishedCallback) { + final TaskAnimationAdapter taskAdapter = new TaskAnimationAdapter(task, + mTargetActivityRecord, this::cancelAnimation); + // borrow from recents since we cannot start back animation if recents is playing + task.startAnimation(task.getPendingTransaction(), taskAdapter, false /* hidden */, + ANIMATION_TYPE_RECENTS, finishedCallback); + task.commitPendingTransaction(); + mPendingAnimations.add(taskAdapter); + return taskAdapter; + } + + private RemoteAnimationTarget[] createAppAnimations() { + final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>(); + for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { + final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i); + final RemoteAnimationTarget target = + taskAdapter.createRemoteAnimationTarget(INVALID_TASK_ID, MODE_UNKNOWN); + if (target != null) { + targets.add(target); + } else { + removeAnimationAdapter(taskAdapter); + } + } + return targets.toArray(new RemoteAnimationTarget[targets.size()]); + } + + private void cancelAnimation() { + synchronized (mWindowManagerService.getWindowManagerLock()) { + // Notify the runner and clean up the animation immediately + // Note: In the fallback case, this can trigger multiple onAnimationCancel() calls + // to the runner if we this actually triggers cancel twice on the caller + try { + mRunner.onAnimationCancelled(); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to cancel recents animation", e); + } + cleanupAnimation(); + } + } + + private boolean isTargetOverWallpaper() { + if (mTargetActivityRecord == null) { + return false; + } + return mTargetActivityRecord.windowsCanBeWallpaperTarget(); + } + + private static class TaskAnimationAdapter implements AnimationAdapter { + private final Task mTask; + private SurfaceControl mCapturedLeash; + private SurfaceAnimator.OnAnimationFinishedCallback mCapturedFinishCallback; + @SurfaceAnimator.AnimationType private int mLastAnimationType; + private RemoteAnimationTarget mTarget; + private final ActivityRecord mTargetActivityRecord; + private final Runnable mCancelCallback; + + private final Rect mBounds = new Rect(); + // The bounds of the target relative to its parent. + private final Rect mLocalBounds = new Rect(); + + TaskAnimationAdapter(Task task, ActivityRecord target, Runnable cancelCallback) { + mTask = task; + mBounds.set(mTask.getBounds()); + + mLocalBounds.set(mBounds); + Point tmpPos = new Point(); + mTask.getRelativePosition(tmpPos); + mLocalBounds.offsetTo(tmpPos.x, tmpPos.y); + mTargetActivityRecord = target; + mCancelCallback = cancelCallback; + } + + // Keep overrideTaskId and overrideMode now, if we need to add other type of back animation + // on legacy transition system then they can be useful. + RemoteAnimationTarget createRemoteAnimationTarget(int overrideTaskId, int overrideMode) { + ActivityRecord topApp = mTask.getTopRealVisibleActivity(); + if (topApp == null) { + topApp = mTask.getTopVisibleActivity(); + } + final WindowState mainWindow = topApp != null + ? topApp.findMainWindow() + : null; + if (mainWindow == null) { + return null; + } + final Rect insets = + mainWindow.getInsetsStateWithVisibilityOverride().calculateInsets( + mBounds, WindowInsets.Type.systemBars(), + false /* ignoreVisibility */).toRect(); + InsetUtils.addInsets(insets, mainWindow.mActivityRecord.getLetterboxInsets()); + final int mode = overrideMode != MODE_UNKNOWN + ? overrideMode + : topApp.getActivityType() == mTargetActivityRecord.getActivityType() + ? MODE_OPENING + : MODE_CLOSING; + if (overrideTaskId < 0) { + overrideTaskId = mTask.mTaskId; + } + mTarget = new RemoteAnimationTarget(overrideTaskId, mode, mCapturedLeash, + !topApp.fillsParent(), new Rect(), + insets, mTask.getPrefixOrderIndex(), new Point(mBounds.left, mBounds.top), + mLocalBounds, mBounds, mTask.getWindowConfiguration(), + true /* isNotInRecents */, null, null, mTask.getTaskInfo(), + topApp.checkEnterPictureInPictureAppOpsState()); + return mTarget; + } + @Override + public boolean getShowWallpaper() { + return false; + } + @Override + public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t, + @SurfaceAnimator.AnimationType int type, + @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) { + t.setPosition(animationLeash, mLocalBounds.left, mLocalBounds.top); + final Rect tmpRect = new Rect(); + tmpRect.set(mLocalBounds); + tmpRect.offsetTo(0, 0); + t.setWindowCrop(animationLeash, tmpRect); + mCapturedLeash = animationLeash; + mCapturedFinishCallback = finishCallback; + mLastAnimationType = type; + } + + @Override + public void onAnimationCancelled(SurfaceControl animationLeash) { + mCancelCallback.run(); + } + + void onRemove() { + mCapturedFinishCallback.onAnimationFinished(mLastAnimationType, this); + } + + void onCleanup() { + final SurfaceControl.Transaction pendingTransaction = mTask.getPendingTransaction(); + if (!mTask.isAttached()) { + // Apply the task's pending transaction in case it is detached and its transaction + // is not reachable. + pendingTransaction.apply(); + } + } + + @Override + public long getDurationHint() { + return 0; + } + + @Override + public long getStatusBarTransitionsStartTime() { + return SystemClock.uptimeMillis(); + } + + @Override + public void dump(PrintWriter pw, String prefix) { } + + @Override + public void dumpDebug(ProtoOutputStream proto) { } + } +} diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index d9ab971c9a78..35a39c048e57 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -34,6 +34,7 @@ import android.util.Slog; import android.view.IWindowFocusObserver; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; +import android.window.BackAnimationAdaptor; import android.window.BackNavigationInfo; import android.window.OnBackInvokedCallbackInfo; import android.window.TaskSnapshot; @@ -46,9 +47,15 @@ import com.android.server.LocalServices; * Controller to handle actions related to the back gesture on the server side. */ class BackNavigationController { - private static final String TAG = "BackNavigationController"; - private WindowManagerService mWindowManagerService; + static final String TAG = "BackNavigationController"; + WindowManagerService mWindowManagerService; private IWindowFocusObserver mFocusObserver; + // TODO (b/241808055) Find a appropriate time to remove during refactor + // Execute back animation with legacy transition system. Temporary flag for easier debugging. + static final boolean USE_TRANSITION = + SystemProperties.getInt("persist.wm.debug.predictive_back_ani_trans", 1) != 0; + + BackNaviAnimationController mBackNaviAnimationController; /** * Returns true if the back predictability feature is enabled @@ -72,7 +79,7 @@ class BackNavigationController { @VisibleForTesting @Nullable BackNavigationInfo startBackNavigation(boolean requestAnimation, - IWindowFocusObserver observer) { + IWindowFocusObserver observer, BackAnimationAdaptor backAnimationAdaptor) { final WindowManagerService wmService = mWindowManagerService; final SurfaceControl.Transaction tx = wmService.mTransactionFactory.get(); mFocusObserver = observer; @@ -259,6 +266,8 @@ class BackNavigationController { && requestAnimation // Only create a new leash if no leash has been created. // Otherwise return null for animation target to avoid conflict. + // TODO isAnimating, recents can cancel app transition animation, can't back + // cancel like recents? && !removedWindowContainer.hasCommittedReparentToAnimationLeash(); if (prepareAnimation) { @@ -266,19 +275,21 @@ class BackNavigationController { currentTask.getTaskInfo().configuration.windowConfiguration; infoBuilder.setTaskWindowConfiguration(taskWindowConfiguration); - // Prepare a leash to animate the current top window - // TODO(b/220934562): Use surface animator to better manage animation conflicts. - SurfaceControl animLeash = removedWindowContainer.makeAnimationLeash() - .setName("BackPreview Leash for " + removedWindowContainer) - .setHidden(false) - .setBLASTLayer() - .build(); - removedWindowContainer.reparentSurfaceControl(tx, animLeash); - animationLeashParent = removedWindowContainer.getAnimationLeashParent(); - topAppTarget = createRemoteAnimationTargetLocked(removedWindowContainer, - currentActivity, - currentTask, animLeash); - infoBuilder.setDepartingAnimationTarget(topAppTarget); + if (!USE_TRANSITION) { + // Prepare a leash to animate the current top window + // TODO(b/220934562): Use surface animator to better manage animation conflicts. + SurfaceControl animLeash = removedWindowContainer.makeAnimationLeash() + .setName("BackPreview Leash for " + removedWindowContainer) + .setHidden(false) + .setBLASTLayer() + .build(); + removedWindowContainer.reparentSurfaceControl(tx, animLeash); + animationLeashParent = removedWindowContainer.getAnimationLeashParent(); + topAppTarget = createRemoteAnimationTargetLocked(removedWindowContainer, + currentActivity, + currentTask, animLeash); + infoBuilder.setDepartingAnimationTarget(topAppTarget); + } } //TODO(207481538) Remove once the infrastructure to support per-activity screenshot is @@ -293,21 +304,32 @@ class BackNavigationController { // Special handling for back to home animation if (backType == BackNavigationInfo.TYPE_RETURN_TO_HOME && prepareAnimation && prevTask != null) { - currentTask.mBackGestureStarted = true; - // Make launcher show from behind by marking its top activity as visible and - // launch-behind to bump its visibility for the duration of the back gesture. - prevActivity = prevTask.getTopNonFinishingActivity(); - if (prevActivity != null) { - if (!prevActivity.mVisibleRequested) { - prevActivity.setVisibility(true); + if (USE_TRANSITION && mBackNaviAnimationController == null) { + if (backAnimationAdaptor != null + && backAnimationAdaptor.getSupportType() == backType) { + mBackNaviAnimationController = new BackNaviAnimationController( + backAnimationAdaptor.getRunner(), this, + currentActivity.getDisplayId()); + prepareBackToHomeTransition(currentTask, prevTask); + infoBuilder.setPrepareAnimation(true); + } + } else { + currentTask.mBackGestureStarted = true; + // Make launcher show from behind by marking its top activity as visible and + // launch-behind to bump its visibility for the duration of the back gesture. + prevActivity = prevTask.getTopNonFinishingActivity(); + if (prevActivity != null) { + if (!prevActivity.mVisibleRequested) { + prevActivity.setVisibility(true); + } + prevActivity.mLaunchTaskBehind = true; + ProtoLog.d(WM_DEBUG_BACK_PREVIEW, + "Setting Activity.mLauncherTaskBehind to true. Activity=%s", + prevActivity); + prevActivity.mRootWindowContainer.ensureActivitiesVisible( + null /* starting */, 0 /* configChanges */, + false /* preserveWindows */); } - prevActivity.mLaunchTaskBehind = true; - ProtoLog.d(WM_DEBUG_BACK_PREVIEW, - "Setting Activity.mLauncherTaskBehind to true. Activity=%s", - prevActivity); - prevActivity.mRootWindowContainer.ensureActivitiesVisible( - null /* starting */, 0 /* configChanges */, - false /* preserveWindows */); } } } // Release wm Lock @@ -388,29 +410,30 @@ class BackNavigationController { BackNavigationInfo.KEY_TRIGGER_BACK); ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "onBackNavigationDone backType=%s, " + "task=%s, prevActivity=%s", backType, task, prevActivity); - - if (backType == BackNavigationInfo.TYPE_RETURN_TO_HOME && prepareAnimation) { - if (triggerBack) { - if (surfaceControl != null && surfaceControl.isValid()) { - // When going back to home, hide the task surface before it is re-parented to - // avoid flicker. - SurfaceControl.Transaction t = windowContainer.getSyncTransaction(); - t.hide(surfaceControl); - t.apply(); + if (!USE_TRANSITION) { + if (backType == BackNavigationInfo.TYPE_RETURN_TO_HOME && prepareAnimation) { + if (triggerBack) { + if (surfaceControl != null && surfaceControl.isValid()) { + // When going back to home, hide the task surface before it is re-parented + // to avoid flicker. + SurfaceControl.Transaction t = windowContainer.getSyncTransaction(); + t.hide(surfaceControl); + t.apply(); + } } + if (prevActivity != null && !triggerBack) { + // Restore the launch-behind state. + task.mTaskSupervisor.scheduleLaunchTaskBehindComplete(prevActivity.token); + prevActivity.mLaunchTaskBehind = false; + ProtoLog.d(WM_DEBUG_BACK_PREVIEW, + "Setting Activity.mLauncherTaskBehind to false. Activity=%s", + prevActivity); + } + } else { + task.mBackGestureStarted = false; } - if (prevActivity != null && !triggerBack) { - // Restore the launch-behind state. - task.mTaskSupervisor.scheduleLaunchTaskBehindComplete(prevActivity.token); - prevActivity.mLaunchTaskBehind = false; - ProtoLog.d(WM_DEBUG_BACK_PREVIEW, - "Setting Activity.mLauncherTaskBehind to false. Activity=%s", - prevActivity); - } - } else if (task != null) { - task.mBackGestureStarted = false; + resetSurfaces(windowContainer); } - resetSurfaces(windowContainer); if (mFocusObserver != null) { focusedWindow.unregisterFocusObserver(mFocusObserver); @@ -465,4 +488,21 @@ class BackNavigationController { void setWindowManager(WindowManagerService wm) { mWindowManagerService = wm; } + + private void prepareBackToHomeTransition(Task currentTask, Task homeTask) { + final DisplayContent dc = currentTask.getDisplayContent(); + final ActivityRecord homeActivity = homeTask.getTopNonFinishingActivity(); + if (!homeActivity.mVisibleRequested) { + homeActivity.setVisibility(true); + } + homeActivity.mLaunchTaskBehind = true; + dc.ensureActivitiesVisible( + null /* starting */, 0 /* configChanges */, + false /* preserveWindows */, true); + mBackNaviAnimationController.initialize(homeActivity, currentTask.getTopMostActivity()); + } + + void finishAnimation() { + mBackNaviAnimationController = null; + } } diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java index 04229063732f..b033dca465f4 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -504,6 +504,7 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { @Override public void onConfigurationChanged(Configuration newParentConfig) { + mTransitionController.collectForDisplayAreaChange(this); mTmpConfiguration.setTo(getConfiguration()); super.onConfigurationChanged(newParentConfig); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index ecb9fe3a00cc..0376974900e3 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -438,7 +438,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ final DisplayMetrics mRealDisplayMetrics = new DisplayMetrics(); - /** @see #computeCompatSmallestWidth(boolean, int, int, int) */ + /** @see #computeCompatSmallestWidth(boolean, int, int) */ private final DisplayMetrics mTmpDisplayMetrics = new DisplayMetrics(); /** @@ -946,7 +946,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final int preferredModeId = getDisplayPolicy().getRefreshRatePolicy() .getPreferredModeId(w); - if (mTmpApplySurfaceChangesTransactionState.preferredModeId == 0 + if (w.isFocused() && mTmpApplySurfaceChangesTransactionState.preferredModeId == 0 && preferredModeId != 0) { mTmpApplySurfaceChangesTransactionState.preferredModeId = preferredModeId; } @@ -2014,7 +2014,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // the top of the method, the caller is obligated to call computeNewConfigurationLocked(). // By updating the Display info here it will be available to // #computeScreenConfiguration() later. - updateDisplayAndOrientation(getConfiguration().uiMode, null /* outConfig */); + updateDisplayAndOrientation(null /* outConfig */); // NOTE: We disable the rotation in the emulator because // it doesn't support hardware OpenGL emulation yet. @@ -2064,7 +2064,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * changed. * Do not call if {@link WindowManagerService#mDisplayReady} == false. */ - private DisplayInfo updateDisplayAndOrientation(int uiMode, Configuration outConfig) { + private DisplayInfo updateDisplayAndOrientation(Configuration outConfig) { // Use the effective "visual" dimensions based on current rotation final int rotation = getRotation(); final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); @@ -2076,18 +2076,16 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final DisplayCutout displayCutout = wmDisplayCutout.getDisplayCutout(); final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation); - final int appWidth = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode, - displayCutout); - final int appHeight = mDisplayPolicy.getNonDecorDisplayHeight(dh, rotation, - displayCutout); + final Rect appFrame = mDisplayPolicy.getNonDecorDisplayFrame(dw, dh, rotation, + wmDisplayCutout); mDisplayInfo.rotation = rotation; mDisplayInfo.logicalWidth = dw; mDisplayInfo.logicalHeight = dh; mDisplayInfo.logicalDensityDpi = mBaseDisplayDensity; mDisplayInfo.physicalXDpi = mBaseDisplayPhysicalXDpi; mDisplayInfo.physicalYDpi = mBaseDisplayPhysicalYDpi; - mDisplayInfo.appWidth = appWidth; - mDisplayInfo.appHeight = appHeight; + mDisplayInfo.appWidth = appFrame.width(); + mDisplayInfo.appHeight = appFrame.height(); if (isDefaultDisplay) { mDisplayInfo.getLogicalMetrics(mRealDisplayMetrics, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); @@ -2101,7 +2099,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mDisplayInfo.flags &= ~Display.FLAG_SCALING_DISABLED; } - computeSizeRangesAndScreenLayout(mDisplayInfo, rotated, uiMode, dw, dh, + computeSizeRangesAndScreenLayout(mDisplayInfo, rotated, dw, dh, mDisplayMetrics.density, outConfig); mWmService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(mDisplayId, @@ -2191,10 +2189,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp outConfig.windowConfiguration.setMaxBounds(0, 0, dw, dh); outConfig.windowConfiguration.setBounds(outConfig.windowConfiguration.getMaxBounds()); - final int uiMode = getConfiguration().uiMode; - final DisplayCutout displayCutout = - calculateDisplayCutoutForRotation(rotation).getDisplayCutout(); - computeScreenAppConfiguration(outConfig, dw, dh, rotation, uiMode, displayCutout); + final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation); + computeScreenAppConfiguration(outConfig, dw, dh, rotation, wmDisplayCutout); final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo); displayInfo.rotation = rotation; @@ -2203,38 +2199,35 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final Rect appBounds = outConfig.windowConfiguration.getAppBounds(); displayInfo.appWidth = appBounds.width(); displayInfo.appHeight = appBounds.height(); + final DisplayCutout displayCutout = wmDisplayCutout.getDisplayCutout(); displayInfo.displayCutout = displayCutout.isEmpty() ? null : displayCutout; - computeSizeRangesAndScreenLayout(displayInfo, rotated, uiMode, dw, dh, + computeSizeRangesAndScreenLayout(displayInfo, rotated, dw, dh, mDisplayMetrics.density, outConfig); return displayInfo; } /** Compute configuration related to application without changing current display. */ private void computeScreenAppConfiguration(Configuration outConfig, int dw, int dh, - int rotation, int uiMode, DisplayCutout displayCutout) { - final int appWidth = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode, - displayCutout); - final int appHeight = mDisplayPolicy.getNonDecorDisplayHeight(dh, rotation, - displayCutout); - mDisplayPolicy.getNonDecorInsetsLw(rotation, displayCutout, mTmpRect); - final int leftInset = mTmpRect.left; - final int topInset = mTmpRect.top; + int rotation, WmDisplayCutout wmDisplayCutout) { + final DisplayFrames displayFrames = + mDisplayPolicy.getSimulatedDisplayFrames(rotation, dw, dh, wmDisplayCutout); + final Rect appFrame = + mDisplayPolicy.getNonDecorDisplayFrameWithSimulatedFrame(displayFrames); // AppBounds at the root level should mirror the app screen size. - outConfig.windowConfiguration.setAppBounds(leftInset /* left */, topInset /* top */, - leftInset + appWidth /* right */, topInset + appHeight /* bottom */); + outConfig.windowConfiguration.setAppBounds(appFrame); outConfig.windowConfiguration.setRotation(rotation); outConfig.orientation = (dw <= dh) ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; final float density = mDisplayMetrics.density; - outConfig.screenWidthDp = (int) (mDisplayPolicy.getConfigDisplayWidth(dw, dh, rotation, - uiMode, displayCutout) / density + 0.5f); - outConfig.screenHeightDp = (int) (mDisplayPolicy.getConfigDisplayHeight(dw, dh, rotation, - uiMode, displayCutout) / density + 0.5f); + final Point configSize = + mDisplayPolicy.getConfigDisplaySizeWithSimulatedFrame(displayFrames); + outConfig.screenWidthDp = (int) (configSize.x / density + 0.5f); + outConfig.screenHeightDp = (int) (configSize.y / density + 0.5f); outConfig.compatScreenWidthDp = (int) (outConfig.screenWidthDp / mCompatibleScreenScale); outConfig.compatScreenHeightDp = (int) (outConfig.screenHeightDp / mCompatibleScreenScale); final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); - outConfig.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, uiMode, dw, dh); + outConfig.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, dw, dh); outConfig.windowConfiguration.setDisplayRotation(rotation); } @@ -2243,7 +2236,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * Do not call if mDisplayReady == false. */ void computeScreenConfiguration(Configuration config) { - final DisplayInfo displayInfo = updateDisplayAndOrientation(config.uiMode, config); + final DisplayInfo displayInfo = updateDisplayAndOrientation(config); final int dw = displayInfo.logicalWidth; final int dh = displayInfo.logicalHeight; mTmpRect.set(0, 0, dw, dh); @@ -2252,8 +2245,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp config.windowConfiguration.setWindowingMode(getWindowingMode()); config.windowConfiguration.setDisplayWindowingMode(getWindowingMode()); - computeScreenAppConfiguration(config, dw, dh, displayInfo.rotation, config.uiMode, - displayInfo.displayCutout); + computeScreenAppConfiguration(config, dw, dh, displayInfo.rotation, + calculateDisplayCutoutForRotation(getRotation())); config.screenLayout = (config.screenLayout & ~Configuration.SCREENLAYOUT_ROUND_MASK) | ((displayInfo.flags & Display.FLAG_ROUND) != 0 @@ -2342,7 +2335,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mWmService.mPolicy.adjustConfigurationLw(config, keyboardPresence, navigationPresence); } - private int computeCompatSmallestWidth(boolean rotated, int uiMode, int dw, int dh) { + private int computeCompatSmallestWidth(boolean rotated, int dw, int dh) { mTmpDisplayMetrics.setTo(mDisplayMetrics); final DisplayMetrics tmpDm = mTmpDisplayMetrics; final int unrotDw, unrotDh; @@ -2353,25 +2346,20 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp unrotDw = dw; unrotDh = dh; } - int sw = reduceCompatConfigWidthSize(0, Surface.ROTATION_0, uiMode, tmpDm, unrotDw, - unrotDh); - sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_90, uiMode, tmpDm, unrotDh, - unrotDw); - sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_180, uiMode, tmpDm, unrotDw, - unrotDh); - sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_270, uiMode, tmpDm, unrotDh, - unrotDw); + int sw = reduceCompatConfigWidthSize(0, Surface.ROTATION_0, tmpDm, unrotDw, unrotDh); + sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_90, tmpDm, unrotDh, unrotDw); + sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_180, tmpDm, unrotDw, unrotDh); + sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_270, tmpDm, unrotDh, unrotDw); return sw; } - private int reduceCompatConfigWidthSize(int curSize, int rotation, int uiMode, + private int reduceCompatConfigWidthSize(int curSize, int rotation, DisplayMetrics dm, int dw, int dh) { - final DisplayCutout displayCutout = calculateDisplayCutoutForRotation( - rotation).getDisplayCutout(); - dm.noncompatWidthPixels = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode, - displayCutout); - dm.noncompatHeightPixels = mDisplayPolicy.getNonDecorDisplayHeight(dh, rotation, - displayCutout); + final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation); + final Rect nonDecorSize = mDisplayPolicy.getNonDecorDisplayFrame(dw, dh, rotation, + wmDisplayCutout); + dm.noncompatWidthPixels = nonDecorSize.width(); + dm.noncompatHeightPixels = nonDecorSize.height(); float scale = CompatibilityInfo.computeCompatibleScaling(dm, null); int size = (int)(((dm.noncompatWidthPixels / scale) / dm.density) + .5f); if (curSize == 0 || size < curSize) { @@ -2381,7 +2369,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } private void computeSizeRangesAndScreenLayout(DisplayInfo displayInfo, boolean rotated, - int uiMode, int dw, int dh, float density, Configuration outConfig) { + int dw, int dh, float density, Configuration outConfig) { // We need to determine the smallest width that will occur under normal // operation. To this, start with the base screen size and compute the @@ -2399,37 +2387,34 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp displayInfo.smallestNominalAppHeight = 1<<30; displayInfo.largestNominalAppWidth = 0; displayInfo.largestNominalAppHeight = 0; - adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_0, uiMode, unrotDw, unrotDh); - adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_90, uiMode, unrotDh, unrotDw); - adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_180, uiMode, unrotDw, unrotDh); - adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_270, uiMode, unrotDh, unrotDw); + adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_0, unrotDw, unrotDh); + adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_90, unrotDh, unrotDw); + adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_180, unrotDw, unrotDh); + adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_270, unrotDh, unrotDw); if (outConfig == null) { return; } int sl = Configuration.resetScreenLayout(outConfig.screenLayout); - sl = reduceConfigLayout(sl, Surface.ROTATION_0, density, unrotDw, unrotDh, uiMode); - sl = reduceConfigLayout(sl, Surface.ROTATION_90, density, unrotDh, unrotDw, uiMode); - sl = reduceConfigLayout(sl, Surface.ROTATION_180, density, unrotDw, unrotDh, uiMode); - sl = reduceConfigLayout(sl, Surface.ROTATION_270, density, unrotDh, unrotDw, uiMode); + sl = reduceConfigLayout(sl, Surface.ROTATION_0, density, unrotDw, unrotDh); + sl = reduceConfigLayout(sl, Surface.ROTATION_90, density, unrotDh, unrotDw); + sl = reduceConfigLayout(sl, Surface.ROTATION_180, density, unrotDw, unrotDh); + sl = reduceConfigLayout(sl, Surface.ROTATION_270, density, unrotDh, unrotDw); outConfig.smallestScreenWidthDp = (int) (displayInfo.smallestNominalAppWidth / density + 0.5f); outConfig.screenLayout = sl; } - private int reduceConfigLayout(int curLayout, int rotation, float density, int dw, int dh, - int uiMode) { + private int reduceConfigLayout(int curLayout, int rotation, float density, int dw, int dh) { // Get the display cutout at this rotation. - final DisplayCutout displayCutout = calculateDisplayCutoutForRotation( - rotation).getDisplayCutout(); + final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation); // Get the app screen size at this rotation. - int w = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode, displayCutout); - int h = mDisplayPolicy.getNonDecorDisplayHeight(dh, rotation, displayCutout); + final Rect size = mDisplayPolicy.getNonDecorDisplayFrame(dw, dh, rotation, wmDisplayCutout); // Compute the screen layout size class for this rotation. - int longSize = w; - int shortSize = h; + int longSize = size.width(); + int shortSize = size.height(); if (longSize < shortSize) { int tmp = longSize; longSize = shortSize; @@ -2440,25 +2425,20 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return Configuration.reduceScreenLayout(curLayout, longSize, shortSize); } - private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int rotation, - int uiMode, int dw, int dh) { - final DisplayCutout displayCutout = calculateDisplayCutoutForRotation( - rotation).getDisplayCutout(); - final int width = mDisplayPolicy.getConfigDisplayWidth(dw, dh, rotation, uiMode, - displayCutout); - if (width < displayInfo.smallestNominalAppWidth) { - displayInfo.smallestNominalAppWidth = width; + private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int rotation, int dw, int dh) { + final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation); + final Point size = mDisplayPolicy.getConfigDisplaySize(dw, dh, rotation, wmDisplayCutout); + if (size.x < displayInfo.smallestNominalAppWidth) { + displayInfo.smallestNominalAppWidth = size.x; } - if (width > displayInfo.largestNominalAppWidth) { - displayInfo.largestNominalAppWidth = width; + if (size.x > displayInfo.largestNominalAppWidth) { + displayInfo.largestNominalAppWidth = size.x; } - final int height = mDisplayPolicy.getConfigDisplayHeight(dw, dh, rotation, uiMode, - displayCutout); - if (height < displayInfo.smallestNominalAppHeight) { - displayInfo.smallestNominalAppHeight = height; + if (size.y < displayInfo.smallestNominalAppHeight) { + displayInfo.smallestNominalAppHeight = size.y; } - if (height > displayInfo.largestNominalAppHeight) { - displayInfo.largestNominalAppHeight = height; + if (size.y > displayInfo.largestNominalAppHeight) { + displayInfo.largestNominalAppHeight = size.y; } } @@ -3304,6 +3284,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mAsyncRotationController.keepAppearanceInPreviousRotation(); } } else if (isRotationChanging()) { + if (displayChange != null) { + final boolean seamless = mDisplayRotation.shouldRotateSeamlessly( + displayChange.getStartRotation(), displayChange.getEndRotation(), + false /* forceUpdate */); + if (seamless) { + t.onSeamlessRotating(this); + } + } mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN); controller.mTransitionMetricsReporter.associate(t, startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN)); @@ -4400,13 +4388,20 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ @VisibleForTesting InsetsControlTarget computeImeControlTarget() { + if (mImeInputTarget == null) { + // A special case that if there is no IME input target while the IME is being killed, + // in case seeing unexpected IME surface visibility change when delivering the IME leash + // to the remote insets target during the IME restarting, but the focus window is not in + // multi-windowing mode, return null target until the next input target updated. + return null; + } + + final WindowState imeInputTarget = mImeInputTarget.getWindowState(); if (!isImeControlledByApp() && mRemoteInsetsControlTarget != null - || (mImeInputTarget != null - && getImeHostOrFallback(mImeInputTarget.getWindowState()) - == mRemoteInsetsControlTarget)) { + || getImeHostOrFallback(imeInputTarget) == mRemoteInsetsControlTarget) { return mRemoteInsetsControlTarget; } else { - return mImeInputTarget != null ? mImeInputTarget.getWindowState() : null; + return imeInputTarget; } } @@ -5935,6 +5930,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp if (changes != 0) { Slog.i(TAG, "Override config changes=" + Integer.toHexString(changes) + " " + mTempConfig + " for displayId=" + mDisplayId); + if (isReady() && mTransitionController.isShellTransitionsEnabled()) { + requestChangeTransitionIfNeeded(changes, null /* displayChange */); + } onRequestedOverrideConfigurationChanged(mTempConfig); final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0; @@ -5951,9 +5949,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } mWmService.mDisplayNotificationController.dispatchDisplayChanged( this, getConfiguration()); - if (isReady() && mTransitionController.isShellTransitionsEnabled()) { - requestChangeTransitionIfNeeded(changes, null /* displayChange */); - } } return changes; } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 096ebe2fd080..8e06a810ead1 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -19,15 +19,19 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.view.Display.TYPE_INTERNAL; +import static android.view.InsetsState.ITYPE_BOTTOM_DISPLAY_CUTOUT; import static android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES; import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT; import static android.view.InsetsState.ITYPE_CAPTION_BAR; import static android.view.InsetsState.ITYPE_CLIMATE_BAR; import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR; +import static android.view.InsetsState.ITYPE_LEFT_DISPLAY_CUTOUT; import static android.view.InsetsState.ITYPE_LEFT_GESTURES; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; +import static android.view.InsetsState.ITYPE_RIGHT_DISPLAY_CUTOUT; import static android.view.InsetsState.ITYPE_RIGHT_GESTURES; import static android.view.InsetsState.ITYPE_STATUS_BAR; +import static android.view.InsetsState.ITYPE_TOP_DISPLAY_CUTOUT; import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES; import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; @@ -103,6 +107,7 @@ import android.content.Intent; import android.content.res.Resources; import android.graphics.Insets; import android.graphics.PixelFormat; +import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; import android.gui.DropInputMode; @@ -127,6 +132,8 @@ import android.view.InsetsSource; import android.view.InsetsState; import android.view.InsetsState.InternalInsetsType; import android.view.InsetsVisibilities; +import android.view.PrivacyIndicatorBounds; +import android.view.RoundedCorners; import android.view.Surface; import android.view.View; import android.view.ViewDebug; @@ -136,7 +143,6 @@ import android.view.WindowLayout; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import android.view.WindowManagerGlobal; -import android.view.WindowManagerPolicyConstants; import android.view.accessibility.AccessibilityManager; import android.window.ClientWindowFrames; @@ -160,6 +166,7 @@ import com.android.server.policy.WindowManagerPolicy.ScreenOnListener; import com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.wallpaper.WallpaperManagerInternal; +import com.android.server.wm.utils.WmDisplayCutout; import java.io.PrintWriter; import java.util.ArrayList; @@ -258,7 +265,7 @@ public class DisplayPolicy { private volatile boolean mWindowManagerDrawComplete; private WindowState mStatusBar = null; - private WindowState mNotificationShade = null; + private volatile WindowState mNotificationShade; private final int[] mStatusBarHeightForRotation = new int[4]; private WindowState mNavigationBar = null; @NavigationBarPosition @@ -383,6 +390,16 @@ public class DisplayPolicy { private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS = 0; private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION = 1; + // TODO (b/235842600): Use public type once we can treat task bar as navigation bar. + private static final int[] STABLE_TYPES = new int[]{ + ITYPE_TOP_DISPLAY_CUTOUT, ITYPE_RIGHT_DISPLAY_CUTOUT, ITYPE_BOTTOM_DISPLAY_CUTOUT, + ITYPE_LEFT_DISPLAY_CUTOUT, ITYPE_NAVIGATION_BAR, ITYPE_STATUS_BAR, ITYPE_CLIMATE_BAR + }; + private static final int[] NON_DECOR_TYPES = new int[]{ + ITYPE_TOP_DISPLAY_CUTOUT, ITYPE_RIGHT_DISPLAY_CUTOUT, ITYPE_BOTTOM_DISPLAY_CUTOUT, + ITYPE_LEFT_DISPLAY_CUTOUT, ITYPE_NAVIGATION_BAR + }; + private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver; private final WindowManagerInternal.AppTransitionListener mAppTransitionListener; @@ -794,11 +811,11 @@ public class DisplayPolicy { } public void setAwake(boolean awake) { - if (awake == mAwake) { - return; - } - mAwake = awake; - synchronized (mService.mGlobalLock) { + synchronized (mLock) { + if (awake == mAwake) { + return; + } + mAwake = awake; if (!mDisplayContent.isDefaultDisplay) { return; } @@ -2007,35 +2024,6 @@ public class DisplayPolicy { return mUiContext; } - private int getNavigationBarWidth(int rotation, int uiMode, int position) { - if (mNavigationBar == null) { - return 0; - } - LayoutParams lp = mNavigationBar.mAttrs; - if (lp.paramsForRotation != null - && lp.paramsForRotation.length == 4 - && lp.paramsForRotation[rotation] != null) { - lp = lp.paramsForRotation[rotation]; - } - Insets providedInsetsSize = null; - if (lp.providedInsets != null) { - for (InsetsFrameProvider provider : lp.providedInsets) { - if (provider.type != ITYPE_NAVIGATION_BAR) { - continue; - } - providedInsetsSize = provider.insetsSize; - } - } - if (providedInsetsSize != null) { - if (position == NAV_BAR_LEFT) { - return providedInsetsSize.left; - } else if (position == NAV_BAR_RIGHT) { - return providedInsetsSize.right; - } - } - return lp.width; - } - @VisibleForTesting void setCanSystemBarsBeShownByUser(boolean canBeShown) { mCanSystemBarsBeShownByUser = canBeShown; @@ -2057,45 +2045,24 @@ public class DisplayPolicy { } /** - * Return the display width available after excluding any screen - * decorations that could never be removed in Honeycomb. That is, system bar or - * button bar. + * Return the display frame available after excluding any screen decorations that could never be + * removed in Honeycomb. That is, system bar or button bar. + * + * @return display frame excluding all non-decor insets. */ - public int getNonDecorDisplayWidth(int fullWidth, int fullHeight, int rotation, int uiMode, - DisplayCutout displayCutout) { - int width = fullWidth; - if (hasNavigationBar()) { - final int navBarPosition = navigationBarPosition(rotation); - if (navBarPosition == NAV_BAR_LEFT || navBarPosition == NAV_BAR_RIGHT) { - width -= getNavigationBarWidth(rotation, uiMode, navBarPosition); - } - } - if (displayCutout != null) { - width -= displayCutout.getSafeInsetLeft() + displayCutout.getSafeInsetRight(); - } - return width; + Rect getNonDecorDisplayFrame(int fullWidth, int fullHeight, int rotation, + WmDisplayCutout cutout) { + final DisplayFrames displayFrames = + getSimulatedDisplayFrames(rotation, fullWidth, fullHeight, cutout); + return getNonDecorDisplayFrameWithSimulatedFrame(displayFrames); } - @VisibleForTesting - int getNavigationBarHeight(int rotation) { - if (mNavigationBar == null) { - return 0; - } - LayoutParams lp = mNavigationBar.mAttrs.forRotation(rotation); - Insets providedInsetsSize = null; - if (lp.providedInsets != null) { - for (InsetsFrameProvider provider : lp.providedInsets) { - if (provider.type != ITYPE_NAVIGATION_BAR) { - continue; - } - providedInsetsSize = provider.insetsSize; - if (providedInsetsSize != null) { - return providedInsetsSize.bottom; - } - break; - } - } - return lp.height; + Rect getNonDecorDisplayFrameWithSimulatedFrame(DisplayFrames displayFrames) { + final Rect nonDecorInsets = + getInsetsWithInternalTypes(displayFrames, NON_DECOR_TYPES).toRect(); + final Rect displayFrame = new Rect(displayFrames.mInsetsState.getDisplayFrame()); + displayFrame.inset(nonDecorInsets); + return displayFrame; } /** @@ -2117,53 +2084,23 @@ public class DisplayPolicy { } /** - * Return the display height available after excluding any screen - * decorations that could never be removed in Honeycomb. That is, system bar or - * button bar. - */ - public int getNonDecorDisplayHeight(int fullHeight, int rotation, DisplayCutout displayCutout) { - int height = fullHeight; - final int navBarPosition = navigationBarPosition(rotation); - if (navBarPosition == NAV_BAR_BOTTOM) { - height -= getNavigationBarHeight(rotation); - } - if (displayCutout != null) { - height -= displayCutout.getSafeInsetTop() + displayCutout.getSafeInsetBottom(); - } - return height; - } - - /** - * Return the available screen width that we should report for the + * Return the available screen size that we should report for the * configuration. This must be no larger than - * {@link #getNonDecorDisplayWidth(int, int, int, int, DisplayCutout)}; it may be smaller + * {@link #getNonDecorDisplayFrame(int, int, int, DisplayCutout)}; it may be smaller * than that to account for more transient decoration like a status bar. */ - public int getConfigDisplayWidth(int fullWidth, int fullHeight, int rotation, int uiMode, - DisplayCutout displayCutout) { - return getNonDecorDisplayWidth(fullWidth, fullHeight, rotation, uiMode, displayCutout); + public Point getConfigDisplaySize(int fullWidth, int fullHeight, int rotation, + WmDisplayCutout wmDisplayCutout) { + final DisplayFrames displayFrames = getSimulatedDisplayFrames(rotation, fullWidth, + fullHeight, wmDisplayCutout); + return getConfigDisplaySizeWithSimulatedFrame(displayFrames); } - /** - * Return the available screen height that we should report for the - * configuration. This must be no larger than - * {@link #getNonDecorDisplayHeight(int, int, DisplayCutout)}; it may be smaller - * than that to account for more transient decoration like a status bar. - */ - public int getConfigDisplayHeight(int fullWidth, int fullHeight, int rotation, int uiMode, - DisplayCutout displayCutout) { - // There is a separate status bar at the top of the display. We don't count that as part - // of the fixed decor, since it can hide; however, for purposes of configurations, - // we do want to exclude it since applications can't generally use that part - // of the screen. - int statusBarHeight = mStatusBarHeightForRotation[rotation]; - if (displayCutout != null) { - // If there is a cutout, it may already have accounted for some part of the status - // bar height. - statusBarHeight = Math.max(0, statusBarHeight - displayCutout.getSafeInsetTop()); - } - return getNonDecorDisplayHeight(fullHeight, rotation, displayCutout) - - statusBarHeight; + Point getConfigDisplaySizeWithSimulatedFrame(DisplayFrames displayFrames) { + final Insets insets = getInsetsWithInternalTypes(displayFrames, STABLE_TYPES); + Rect configFrame = new Rect(displayFrames.mInsetsState.getDisplayFrame()); + configFrame.inset(insets); + return new Point(configFrame.width(), configFrame.height()); } /** @@ -2195,48 +2132,75 @@ public class DisplayPolicy { * Calculates the stable insets without running a layout. * * @param displayRotation the current display rotation + * @param displayWidth full display width + * @param displayHeight full display height * @param displayCutout the current display cutout * @param outInsets the insets to return */ - public void getStableInsetsLw(int displayRotation, DisplayCutout displayCutout, - Rect outInsets) { - outInsets.setEmpty(); + public void getStableInsetsLw(int displayRotation, int displayWidth, int displayHeight, + WmDisplayCutout displayCutout, Rect outInsets) { + final DisplayFrames displayFrames = getSimulatedDisplayFrames(displayRotation, + displayWidth, displayHeight, displayCutout); + getStableInsetsWithSimulatedFrame(displayFrames, outInsets); + } - // Navigation bar and status bar. - getNonDecorInsetsLw(displayRotation, displayCutout, outInsets); - convertNonDecorInsetsToStableInsets(outInsets, displayRotation); + void getStableInsetsWithSimulatedFrame(DisplayFrames displayFrames, Rect outInsets) { + // Navigation bar, status bar, and cutout. + outInsets.set(getInsetsWithInternalTypes(displayFrames, STABLE_TYPES).toRect()); } /** * Calculates the insets for the areas that could never be removed in Honeycomb, i.e. system - * bar or button bar. See {@link #getNonDecorDisplayWidth}. - * @param displayRotation the current display rotation - * @param displayCutout the current display cutout + * bar or button bar. See {@link #getNonDecorDisplayFrame}. + * + * @param displayRotation the current display rotation + * @param fullWidth the width of the display, including all insets + * @param fullHeight the height of the display, including all insets + * @param cutout the current display cutout * @param outInsets the insets to return */ - public void getNonDecorInsetsLw(int displayRotation, DisplayCutout displayCutout, - Rect outInsets) { - outInsets.setEmpty(); - - // Only navigation bar - if (hasNavigationBar()) { - final int uiMode = mService.mPolicy.getUiMode(); - int position = navigationBarPosition(displayRotation); - if (position == NAV_BAR_BOTTOM) { - outInsets.bottom = getNavigationBarHeight(displayRotation); - } else if (position == NAV_BAR_RIGHT) { - outInsets.right = getNavigationBarWidth(displayRotation, uiMode, position); - } else if (position == NAV_BAR_LEFT) { - outInsets.left = getNavigationBarWidth(displayRotation, uiMode, position); - } - } + public void getNonDecorInsetsLw(int displayRotation, int fullWidth, int fullHeight, + WmDisplayCutout cutout, Rect outInsets) { + final DisplayFrames displayFrames = + getSimulatedDisplayFrames(displayRotation, fullWidth, fullHeight, cutout); + getNonDecorInsetsWithSimulatedFrame(displayFrames, outInsets); + } + + void getNonDecorInsetsWithSimulatedFrame(DisplayFrames displayFrames, Rect outInsets) { + outInsets.set(getInsetsWithInternalTypes(displayFrames, NON_DECOR_TYPES).toRect()); + } + + DisplayFrames getSimulatedDisplayFrames(int displayRotation, int fullWidth, + int fullHeight, WmDisplayCutout cutout) { + final DisplayInfo info = new DisplayInfo(mDisplayContent.getDisplayInfo()); + info.rotation = displayRotation; + info.logicalWidth = fullWidth; + info.logicalHeight = fullHeight; + info.displayCutout = cutout.getDisplayCutout(); + final RoundedCorners roundedCorners = + mDisplayContent.calculateRoundedCornersForRotation(displayRotation); + final PrivacyIndicatorBounds indicatorBounds = + mDisplayContent.calculatePrivacyIndicatorBoundsForRotation(displayRotation); + final DisplayFrames displayFrames = new DisplayFrames(getDisplayId(), new InsetsState(), + info, cutout, roundedCorners, indicatorBounds); + simulateLayoutDisplay(displayFrames); + return displayFrames; + } - if (displayCutout != null) { - outInsets.left += displayCutout.getSafeInsetLeft(); - outInsets.top += displayCutout.getSafeInsetTop(); - outInsets.right += displayCutout.getSafeInsetRight(); - outInsets.bottom += displayCutout.getSafeInsetBottom(); - } + @VisibleForTesting + Insets getInsets(DisplayFrames displayFrames, @InsetsType int type) { + final InsetsState state = displayFrames.mInsetsState; + final Insets insets = state.calculateInsets(state.getDisplayFrame(), type, + true /* ignoreVisibility */); + return insets; + } + + Insets getInsetsWithInternalTypes(DisplayFrames displayFrames, + @InternalInsetsType int[] types) { + final InsetsState state = displayFrames.mInsetsState; + final Insets insets = state.calculateInsetsWithInternalTypes(state.getDisplayFrame(), types, + true /* ignoreVisibility */); + return insets; } @NavigationBarPosition @@ -2256,17 +2220,6 @@ public class DisplayPolicy { } /** - * @return The side of the screen where navigation bar is positioned. - * @see WindowManagerPolicyConstants#NAV_BAR_LEFT - * @see WindowManagerPolicyConstants#NAV_BAR_RIGHT - * @see WindowManagerPolicyConstants#NAV_BAR_BOTTOM - */ - @NavigationBarPosition - public int getNavBarPosition() { - return mNavigationBarPosition; - } - - /** * A new window has been focused. */ public void focusChangedLw(WindowState lastFocus, WindowState newFocus) { @@ -2746,6 +2699,19 @@ public class DisplayPolicy { mImmersiveModeConfirmation.onLockTaskModeChangedLw(lockTaskState); } + /** Called when a {@link android.os.PowerManager#USER_ACTIVITY_EVENT_TOUCH} is sent. */ + public void onUserActivityEventTouch() { + // If there is keyguard, it may use INPUT_FEATURE_DISABLE_USER_ACTIVITY (InputDispatcher + // won't trigger user activity for touch). So while the device is not interactive, the user + // event is only sent explicitly from SystemUI. + if (mAwake) return; + // If the event is triggered while the display is not awake, the screen may be showing + // dozing UI such as AOD or overlay UI of under display fingerprint. Then set the animating + // state temporarily to make the process more responsive. + final WindowState w = mNotificationShade; + mService.mAtmService.setProcessAnimatingWhileDozing(w != null ? w.getProcess() : null); + } + boolean onSystemUiSettingsChanged() { return mImmersiveModeConfirmation.onSettingChanged(mService.mCurrentUserId); } diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index 8dd58506ef0b..97609a7dd8ba 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -513,19 +513,6 @@ public class DisplayRotation { return true; } - /** - * Utility to get a rotating displaycontent from a Transition. - * @return null if the transition doesn't contain a rotating display. - */ - static DisplayContent getDisplayFromTransition(Transition transition) { - for (int i = transition.mParticipants.size() - 1; i >= 0; --i) { - final WindowContainer wc = transition.mParticipants.valueAt(i); - if (!(wc instanceof DisplayContent)) continue; - return (DisplayContent) wc; - } - return null; - } - private void startRemoteRotation(int fromRotation, int toRotation) { mDisplayContent.mRemoteDisplayChangeController.performRemoteDisplayChange( fromRotation, toRotation, null /* newDisplayAreaInfo */, @@ -545,11 +532,6 @@ public class DisplayRotation { throw new IllegalStateException("Trying to rotate outside a transition"); } mDisplayContent.mTransitionController.collect(mDisplayContent); - // Go through all tasks and collect them before the rotation - // TODO(shell-transitions): move collect() to onConfigurationChange once wallpaper - // handling is synchronized. - mDisplayContent.mTransitionController.collectForDisplayAreaChange(mDisplayContent, - null /* use collecting transition */); } mService.mAtmService.deferWindowLayout(); try { diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index 3e2d7c928936..506396a932fe 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -759,7 +759,7 @@ class InsetsPolicy { InsetsPolicyAnimationControlListener(boolean show, Runnable finishCallback, int types) { super(show, false /* hasCallbacks */, types, BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE, - false /* disable */, 0 /* floatingImeBottomInsets */); + false /* disable */, 0 /* floatingImeBottomInsets */, null); mFinishCallback = finishCallback; mControlCallbacks = new InsetsPolicyAnimationControlCallbacks(this); } diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java index df3109ad33c7..27550d9e8186 100644 --- a/services/core/java/com/android/server/wm/Letterbox.java +++ b/services/core/java/com/android/server/wm/Letterbox.java @@ -56,6 +56,7 @@ public class Letterbox { private final Supplier<Boolean> mHasWallpaperBackgroundSupplier; private final Supplier<Integer> mBlurRadiusSupplier; private final Supplier<Float> mDarkScrimAlphaSupplier; + private final Supplier<SurfaceControl> mParentSurfaceSupplier; private final Rect mOuter = new Rect(); private final Rect mInner = new Rect(); @@ -87,7 +88,8 @@ public class Letterbox { Supplier<Integer> blurRadiusSupplier, Supplier<Float> darkScrimAlphaSupplier, IntConsumer doubleTapCallbackX, - IntConsumer doubleTapCallbackY) { + IntConsumer doubleTapCallbackY, + Supplier<SurfaceControl> parentSurface) { mSurfaceControlFactory = surfaceControlFactory; mTransactionFactory = transactionFactory; mAreCornersRounded = areCornersRounded; @@ -97,6 +99,7 @@ public class Letterbox { mDarkScrimAlphaSupplier = darkScrimAlphaSupplier; mDoubleTapCallbackX = doubleTapCallbackX; mDoubleTapCallbackY = doubleTapCallbackY; + mParentSurfaceSupplier = parentSurface; } /** @@ -121,7 +124,6 @@ public class Letterbox { mFullWindowSurface.layout(outer.left, outer.top, outer.right, outer.bottom, surfaceOrigin); } - /** * Gets the insets between the outer and inner rects. */ @@ -333,6 +335,7 @@ public class Letterbox { private SurfaceControl mSurface; private Color mColor; private boolean mHasWallpaperBackground; + private SurfaceControl mParentSurface; private final Rect mSurfaceFrameRelative = new Rect(); private final Rect mLayoutFrameGlobal = new Rect(); @@ -403,10 +406,12 @@ public class Letterbox { } mColor = mColorSupplier.get(); + mParentSurface = mParentSurfaceSupplier.get(); t.setColor(mSurface, getRgbColorArray()); t.setPosition(mSurface, mSurfaceFrameRelative.left, mSurfaceFrameRelative.top); t.setWindowCrop(mSurface, mSurfaceFrameRelative.width(), mSurfaceFrameRelative.height()); + t.reparent(mSurface, mParentSurface); mHasWallpaperBackground = mHasWallpaperBackgroundSupplier.get(); updateAlphaAndBlur(t); @@ -452,12 +457,13 @@ public class Letterbox { public boolean needsApplySurfaceChanges() { return !mSurfaceFrameRelative.equals(mLayoutFrameRelative) - // If mSurfaceFrameRelative is empty then mHasWallpaperBackground and mColor - // may never be updated in applySurfaceChanges but this doesn't mean that - // update is needed. + // If mSurfaceFrameRelative is empty then mHasWallpaperBackground, mColor, + // and mParentSurface may never be updated in applySurfaceChanges but this + // doesn't mean that update is needed. || !mSurfaceFrameRelative.isEmpty() && (mHasWallpaperBackgroundSupplier.get() != mHasWallpaperBackground - || !mColorSupplier.get().equals(mColor)); + || !mColorSupplier.get().equals(mColor) + || mParentSurfaceSupplier.get() != mParentSurface); } } } diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java index 57c60f4ffc33..a469c6b39e7f 100644 --- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java +++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java @@ -265,7 +265,7 @@ final class LetterboxConfiguration { } /** - * Overrides corners raidus for activities presented in the letterbox mode. If given value < 0, + * Overrides corners radius for activities presented in the letterbox mode. If given value < 0, * both it and a value of {@link * com.android.internal.R.integer.config_letterboxActivityCornersRadius} will be ignored and * corners of the activity won't be rounded. @@ -275,7 +275,7 @@ final class LetterboxConfiguration { } /** - * Resets corners raidus for activities presented in the letterbox mode to {@link + * Resets corners radius for activities presented in the letterbox mode to {@link * com.android.internal.R.integer.config_letterboxActivityCornersRadius}. */ void resetLetterboxActivityCornersRadius() { @@ -291,7 +291,7 @@ final class LetterboxConfiguration { } /** - * Gets corners raidus for activities presented in the letterbox mode. + * Gets corners radius for activities presented in the letterbox mode. */ int getLetterboxActivityCornersRadius() { return mLetterboxActivityCornersRadius; @@ -318,7 +318,7 @@ final class LetterboxConfiguration { /** * Sets color of letterbox background which is used when {@link * #getLetterboxBackgroundType()} is {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} or as - * fallback for other backfround types. + * fallback for other background types. */ void setLetterboxBackgroundColor(Color color) { mLetterboxBackgroundColorOverride = color; @@ -327,7 +327,7 @@ final class LetterboxConfiguration { /** * Sets color ID of letterbox background which is used when {@link * #getLetterboxBackgroundType()} is {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} or as - * fallback for other backfround types. + * fallback for other background types. */ void setLetterboxBackgroundColorResourceId(int colorId) { mLetterboxBackgroundColorResourceIdOverride = colorId; diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index c8ed602b0f1d..317c93e63459 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -89,7 +89,7 @@ final class LetterboxUiController { // Taskbar expanded height. Used to determine whether to crop an app window to display rounded // corners above the taskbar. - private float mExpandedTaskBarHeight; + private final float mExpandedTaskBarHeight; private boolean mShowWallpaperForLetterboxBackground; @@ -120,7 +120,7 @@ final class LetterboxUiController { } } - boolean hasWallpaperBackgroudForLetterbox() { + boolean hasWallpaperBackgroundForLetterbox() { return mShowWallpaperForLetterboxBackground; } @@ -137,6 +137,11 @@ final class LetterboxUiController { void getLetterboxInnerBounds(Rect outBounds) { if (mLetterbox != null) { outBounds.set(mLetterbox.getInnerFrame()); + final WindowState w = mActivityRecord.findMainWindow(); + if (w == null) { + return; + } + adjustBoundsForTaskbar(w, outBounds); } else { outBounds.setEmpty(); } @@ -160,13 +165,17 @@ final class LetterboxUiController { } void updateLetterboxSurface(WindowState winHint) { + updateLetterboxSurface(winHint, mActivityRecord.getSyncTransaction()); + } + + void updateLetterboxSurface(WindowState winHint, Transaction t) { final WindowState w = mActivityRecord.findMainWindow(); if (w != winHint && winHint != null && w != null) { return; } layoutLetterbox(winHint); if (mLetterbox != null && mLetterbox.needsApplySurfaceChanges()) { - mLetterbox.applySurfaceChanges(mActivityRecord.getSyncTransaction()); + mLetterbox.applySurfaceChanges(t); } } @@ -191,14 +200,22 @@ final class LetterboxUiController { mActivityRecord.mWmService.mTransactionFactory, this::shouldLetterboxHaveRoundedCorners, this::getLetterboxBackgroundColor, - this::hasWallpaperBackgroudForLetterbox, + this::hasWallpaperBackgroundForLetterbox, this::getLetterboxWallpaperBlurRadius, this::getLetterboxWallpaperDarkScrimAlpha, this::handleHorizontalDoubleTap, - this::handleVerticalDoubleTap); + this::handleVerticalDoubleTap, + this::getLetterboxParentSurface); mLetterbox.attachInput(w); } - mActivityRecord.getPosition(mTmpPoint); + + if (mActivityRecord.isInLetterboxAnimation()) { + // In this case we attach the letterbox to the task instead of the activity. + mActivityRecord.getTask().getPosition(mTmpPoint); + } else { + mActivityRecord.getPosition(mTmpPoint); + } + // Get the bounds of the "space-to-fill". The transformed bounds have the highest // priority because the activity is launched in a rotated environment. In multi-window // mode, the task-level represents this. In fullscreen-mode, the task container does @@ -215,6 +232,13 @@ final class LetterboxUiController { } } + SurfaceControl getLetterboxParentSurface() { + if (mActivityRecord.isInLetterboxAnimation()) { + return mActivityRecord.getTask().getSurfaceControl(); + } + return mActivityRecord.getSurfaceControl(); + } + private boolean shouldLetterboxHaveRoundedCorners() { // TODO(b/214030873): remove once background is drawn for transparent activities // Letterbox shouldn't have rounded corners if the activity is transparent @@ -436,7 +460,7 @@ final class LetterboxUiController { } break; case LETTERBOX_BACKGROUND_WALLPAPER: - if (hasWallpaperBackgroudForLetterbox()) { + if (hasWallpaperBackgroundForLetterbox()) { // Color is used for translucent scrim that dims wallpaper. return Color.valueOf(Color.BLACK); } @@ -459,15 +483,14 @@ final class LetterboxUiController { private void updateRoundedCorners(WindowState mainWindow) { final SurfaceControl windowSurface = mainWindow.getClientViewRootSurface(); if (windowSurface != null && windowSurface.isValid()) { - Transaction transaction = mActivityRecord.getSyncTransaction(); - - final InsetsState insetsState = mainWindow.getInsetsState(); - final InsetsSource taskbarInsetsSource = - insetsState.peekSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR); - - if (!isLetterboxedNotForDisplayCutout(mainWindow) - || !mLetterboxConfiguration.isLetterboxActivityCornersRounded() - || taskbarInsetsSource == null) { + final Transaction transaction = mActivityRecord.getSyncTransaction(); + + if (!requiresRoundedCorners(mainWindow) || mActivityRecord.isInLetterboxAnimation()) { + // We don't want corner radius on the window. + // In the case the ActivityRecord requires a letterboxed animation we never want + // rounded corners on the window because rounded corners are applied at the + // animation-bounds surface level and rounded corners on the window would interfere + // with that leading to unexpected rounded corner positioning during the animation. transaction .setWindowCrop(windowSurface, null) .setCornerRadius(windowSurface, 0); @@ -476,48 +499,89 @@ final class LetterboxUiController { Rect cropBounds = null; - // Rounded corners should be displayed above the taskbar. When taskbar is hidden, - // an insets frame is equal to a navigation bar which shouldn't affect position of - // rounded corners since apps are expected to handle navigation bar inset. - // This condition checks whether the taskbar is visible. - // Do not crop the taskbar inset if the window is in immersive mode - the user can - // swipe to show/hide the taskbar as an overlay. - if (taskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight - && taskbarInsetsSource.isVisible()) { + if (hasVisibleTaskbar(mainWindow)) { cropBounds = new Rect(mActivityRecord.getBounds()); // Activity bounds are in screen coordinates while (0,0) for activity's surface // control is at the top left corner of an app window so offsetting bounds // accordingly. cropBounds.offsetTo(0, 0); - // Rounded cornerners should be displayed above the taskbar. - cropBounds.bottom = - Math.min(cropBounds.bottom, taskbarInsetsSource.getFrame().top); - if (mActivityRecord.inSizeCompatMode() - && mActivityRecord.getSizeCompatScale() < 1.0f) { - cropBounds.scale(1.0f / mActivityRecord.getSizeCompatScale()); - } + // Rounded corners should be displayed above the taskbar. + adjustBoundsForTaskbarUnchecked(mainWindow, cropBounds); } transaction .setWindowCrop(windowSurface, cropBounds) - .setCornerRadius(windowSurface, getRoundedCorners(insetsState)); + .setCornerRadius(windowSurface, getRoundedCornersRadius(mainWindow)); } } - // Returns rounded corners radius based on override in + private boolean requiresRoundedCorners(WindowState mainWindow) { + final InsetsSource taskbarInsetsSource = getTaskbarInsetsSource(mainWindow); + + return isLetterboxedNotForDisplayCutout(mainWindow) + && mLetterboxConfiguration.isLetterboxActivityCornersRounded() + && taskbarInsetsSource != null; + } + + // Returns rounded corners radius the letterboxed activity should have based on override in // R.integer.config_letterboxActivityCornersRadius or min device bottom corner radii. // Device corners can be different on the right and left sides but we use the same radius // for all corners for consistency and pick a minimal bottom one for consistency with a // taskbar rounded corners. - private int getRoundedCorners(InsetsState insetsState) { + int getRoundedCornersRadius(WindowState mainWindow) { + if (!requiresRoundedCorners(mainWindow)) { + return 0; + } + if (mLetterboxConfiguration.getLetterboxActivityCornersRadius() >= 0) { return mLetterboxConfiguration.getLetterboxActivityCornersRadius(); } + + final InsetsState insetsState = mainWindow.getInsetsState(); return Math.min( getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_LEFT), getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_RIGHT)); } + /** + * Returns whether the taskbar is visible. Returns false if the window is in immersive mode, + * since the user can swipe to show/hide the taskbar as an overlay. + */ + private boolean hasVisibleTaskbar(WindowState mainWindow) { + final InsetsSource taskbarInsetsSource = getTaskbarInsetsSource(mainWindow); + + return taskbarInsetsSource != null + && taskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight + && taskbarInsetsSource.isVisible(); + } + + private InsetsSource getTaskbarInsetsSource(WindowState mainWindow) { + final InsetsState insetsState = mainWindow.getInsetsState(); + return insetsState.peekSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR); + } + + private void adjustBoundsForTaskbar(WindowState mainWindow, Rect bounds) { + // Rounded corners should be displayed above the taskbar. When taskbar is hidden, + // an insets frame is equal to a navigation bar which shouldn't affect position of + // rounded corners since apps are expected to handle navigation bar inset. + // This condition checks whether the taskbar is visible. + // Do not crop the taskbar inset if the window is in immersive mode - the user can + // swipe to show/hide the taskbar as an overlay. + if (hasVisibleTaskbar(mainWindow)) { + adjustBoundsForTaskbarUnchecked(mainWindow, bounds); + } + } + + private void adjustBoundsForTaskbarUnchecked(WindowState mainWindow, Rect bounds) { + // Rounded corners should be displayed above the taskbar. + bounds.bottom = + Math.min(bounds.bottom, getTaskbarInsetsSource(mainWindow).getFrame().top); + if (mActivityRecord.inSizeCompatMode() + && mActivityRecord.getSizeCompatScale() < 1.0f) { + bounds.scale(1.0f / mActivityRecord.getSizeCompatScale()); + } + } + private int getInsetsStateCornerRadius( InsetsState insetsState, @RoundedCorner.Position int position) { RoundedCorner corner = insetsState.getRoundedCorners().getRoundedCorner(position); @@ -592,7 +656,7 @@ final class LetterboxUiController { + letterboxBackgroundTypeToString( mLetterboxConfiguration.getLetterboxBackgroundType())); pw.println(prefix + " letterboxCornerRadius=" - + getRoundedCorners(mainWin.getInsetsState())); + + getRoundedCornersRadius(mainWin)); if (mLetterboxConfiguration.getLetterboxBackgroundType() == LETTERBOX_BACKGROUND_WALLPAPER) { pw.println(prefix + " isLetterboxWallpaperBlurSupported=" diff --git a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java index 64749cf94ddf..a89894db4b4b 100644 --- a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java +++ b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java @@ -101,7 +101,6 @@ public class PhysicalDisplaySwitchTransitionLauncher { if (t != null) { mDisplayContent.mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY); - mTransitionController.collectForDisplayAreaChange(mDisplayContent, t); mTransition = t; } } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index aab9d5bc8cf1..856430dae6c4 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -514,7 +514,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> void onChildPositionChanged(WindowContainer child) { mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, !mWmService.mPerDisplayFocusEnabled /* updateInputWindows */); - mTaskSupervisor.updateTopResumedActivityIfNeeded(); + mTaskSupervisor.updateTopResumedActivityIfNeeded("onChildPositionChanged"); } @Override @@ -847,6 +847,10 @@ class RootWindowContainer extends WindowContainer<DisplayContent> if (recentsAnimationController != null) { recentsAnimationController.checkAnimationReady(defaultDisplay.mWallpaperController); } + final BackNaviAnimationController bnac = mWmService.getBackNaviAnimationController(); + if (bnac != null) { + bnac.checkAnimationReady(defaultDisplay.mWallpaperController); + } for (int displayNdx = 0; displayNdx < mChildren.size(); ++displayNdx) { final DisplayContent displayContent = mChildren.get(displayNdx); diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java index c7f8a1e2068a..f3670e49f01e 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java @@ -27,7 +27,10 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.Nullable; +import android.graphics.BitmapShader; +import android.graphics.Canvas; import android.graphics.Insets; +import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.Rect; import android.hardware.power.Boost; @@ -374,43 +377,45 @@ class SurfaceAnimationRunner { final int targetSurfaceWidth = bounds.width(); if (maxExtensionInsets.left < 0) { - final Rect edgeBounds = new Rect(0, 0, 1, targetSurfaceHeight); + final Rect edgeBounds = new Rect(bounds.left, bounds.top, bounds.left + 1, + bounds.bottom); final Rect extensionRect = new Rect(0, 0, -maxExtensionInsets.left, targetSurfaceHeight); - final int xPos = maxExtensionInsets.left; - final int yPos = 0; + final int xPos = bounds.left + maxExtensionInsets.left; + final int yPos = bounds.top; createExtensionSurface(leash, edgeBounds, extensionRect, xPos, yPos, "Left Edge Extension", transaction); } if (maxExtensionInsets.top < 0) { - final Rect edgeBounds = new Rect(0, 0, targetSurfaceWidth, 1); + final Rect edgeBounds = new Rect(bounds.left, bounds.top, targetSurfaceWidth, + bounds.top + 1); final Rect extensionRect = new Rect(0, 0, targetSurfaceWidth, -maxExtensionInsets.top); - final int xPos = 0; - final int yPos = maxExtensionInsets.top; + final int xPos = bounds.left; + final int yPos = bounds.top + maxExtensionInsets.top; createExtensionSurface(leash, edgeBounds, extensionRect, xPos, yPos, "Top Edge Extension", transaction); } if (maxExtensionInsets.right < 0) { - final Rect edgeBounds = new Rect(targetSurfaceWidth - 1, 0, - targetSurfaceWidth, targetSurfaceHeight); + final Rect edgeBounds = new Rect(bounds.right - 1, bounds.top, bounds.right, + bounds.bottom); final Rect extensionRect = new Rect(0, 0, -maxExtensionInsets.right, targetSurfaceHeight); - final int xPos = targetSurfaceWidth; - final int yPos = 0; + final int xPos = bounds.right; + final int yPos = bounds.top; createExtensionSurface(leash, edgeBounds, extensionRect, xPos, yPos, "Right Edge Extension", transaction); } if (maxExtensionInsets.bottom < 0) { - final Rect edgeBounds = new Rect(0, targetSurfaceHeight - 1, - targetSurfaceWidth, targetSurfaceHeight); + final Rect edgeBounds = new Rect(bounds.left, bounds.bottom - 1, + bounds.right, bounds.bottom); final Rect extensionRect = new Rect(0, 0, targetSurfaceWidth, -maxExtensionInsets.bottom); - final int xPos = maxExtensionInsets.left; - final int yPos = targetSurfaceHeight; + final int xPos = bounds.left; + final int yPos = bounds.bottom; createExtensionSurface(leash, edgeBounds, extensionRect, xPos, yPos, "Bottom Edge Extension", transaction); } @@ -453,17 +458,21 @@ class SurfaceAnimationRunner { .setHidden(true) .setCallsite("DefaultTransitionHandler#startAnimation") .setOpaque(true) - .setBufferSize(edgeBounds.width(), edgeBounds.height()) + .setBufferSize(extensionRect.width(), extensionRect.height()) .build(); + BitmapShader shader = new BitmapShader(edgeBuffer.asBitmap(), + android.graphics.Shader.TileMode.CLAMP, + android.graphics.Shader.TileMode.CLAMP); + final Paint paint = new Paint(); + paint.setShader(shader); + final Surface surface = new Surface(edgeExtensionLayer); - surface.attachAndQueueBufferWithColorSpace(edgeBuffer.getHardwareBuffer(), - edgeBuffer.getColorSpace()); + Canvas c = surface.lockHardwareCanvas(); + c.drawRect(extensionRect, paint); + surface.unlockCanvasAndPost(c); surface.release(); - final float scaleX = getScaleXForExtensionSurface(edgeBounds, extensionRect); - final float scaleY = getScaleYForExtensionSurface(edgeBounds, extensionRect); - synchronized (mEdgeExtensionLock) { if (!mEdgeExtensions.containsKey(leash)) { // The animation leash has already been removed, so we don't want to attach the @@ -472,7 +481,6 @@ class SurfaceAnimationRunner { return; } - startTransaction.setScale(edgeExtensionLayer, scaleX, scaleY); startTransaction.reparent(edgeExtensionLayer, leash); startTransaction.setLayer(edgeExtensionLayer, Integer.MIN_VALUE); startTransaction.setPosition(edgeExtensionLayer, xPos, yPos); @@ -508,8 +516,6 @@ class SurfaceAnimationRunner { throw new RuntimeException("Unexpected edgeBounds and extensionRect heights"); } - - private static final class RunningAnimation { final AnimationSpec mAnimSpec; final SurfaceControl mLeash; diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 75552e079575..18b0e3311a94 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -920,7 +920,7 @@ class Task extends TaskFragment { // If the original state is resumed, there is no state change to update focused app. // So here makes sure the activity focus is set if it is the top. if (r.isState(RESUMED) && r == mRootWindowContainer.getTopResumedActivity()) { - mAtmService.setResumedActivityUncheckLocked(r, reason); + mAtmService.setLastResumedActivityUncheckLocked(r, reason); } } if (!animate) { @@ -1889,8 +1889,7 @@ class Task extends TaskFragment { } final int newWinMode = getWindowingMode(); - if ((prevWinMode != newWinMode) && (mDisplayContent != null) - && shouldStartChangeTransition(prevWinMode, newWinMode)) { + if (shouldStartChangeTransition(prevWinMode, mTmpPrevBounds)) { initializeChangeTransition(mTmpPrevBounds); } @@ -2141,10 +2140,16 @@ class Task extends TaskFragment { bounds.offset(horizontalDiff, verticalDiff); } - private boolean shouldStartChangeTransition(int prevWinMode, int newWinMode) { + private boolean shouldStartChangeTransition(int prevWinMode, @NonNull Rect prevBounds) { if (!isLeafTask() || !canStartChangeTransition()) { return false; } + final int newWinMode = getWindowingMode(); + if (mTransitionController.inTransition(this)) { + final Rect newBounds = getConfiguration().windowConfiguration.getBounds(); + return prevWinMode != newWinMode || prevBounds.width() != newBounds.width() + || prevBounds.height() != newBounds.height(); + } // Only do an animation into and out-of freeform mode for now. Other mode // transition animations are currently handled by system-ui. return (prevWinMode == WINDOWING_MODE_FREEFORM) != (newWinMode == WINDOWING_MODE_FREEFORM); @@ -2439,11 +2444,7 @@ class Task extends TaskFragment { focusableTask.moveToFront(myReason); // Top display focused root task is changed, update top resumed activity if needed. if (rootTask.getTopResumedActivity() != null) { - mTaskSupervisor.updateTopResumedActivityIfNeeded(); - // Set focused app directly because if the next focused activity is already resumed - // (e.g. the next top activity is on a different display), there won't have activity - // state change to update it. - mAtmService.setResumedActivityUncheckLocked(rootTask.getTopResumedActivity(), reason); + mTaskSupervisor.updateTopResumedActivityIfNeeded(reason); } return rootTask; } diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 8220cae74dc8..0f46c4f166ae 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -323,6 +323,10 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { // Clear preferred top because the adding focusable task has a higher z-order. mPreferredTopFocusableRootTask = null; } + + // Update the top resumed activity because the preferred top focusable task may be changed. + mAtmService.mTaskSupervisor.updateTopResumedActivityIfNeeded("addChildTask"); + mAtmService.updateSleepIfNeededLocked(); onRootTaskOrderChanged(task); } @@ -416,12 +420,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { } // Update the top resumed activity because the preferred top focusable task may be changed. - mAtmService.mTaskSupervisor.updateTopResumedActivityIfNeeded(); - - final ActivityRecord r = child.getTopResumedActivity(); - if (r != null && r == mRootWindowContainer.getTopResumedActivity()) { - mAtmService.setResumedActivityUncheckLocked(r, "positionChildAt"); - } + mAtmService.mTaskSupervisor.updateTopResumedActivityIfNeeded("positionChildTaskAt"); if (mChildren.indexOf(child) != oldPosition) { onRootTaskOrderChanged(child); diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index f3f21103a7e8..da731e842aea 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -98,6 +98,7 @@ import com.android.internal.util.function.pooled.PooledLambda; import com.android.internal.util.function.pooled.PooledPredicate; import com.android.server.am.HostingRecord; import com.android.server.pm.parsing.pkg.AndroidPackage; +import com.android.server.wm.utils.WmDisplayCutout; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -460,7 +461,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { final ActivityRecord prevR = mResumedActivity; mResumedActivity = r; - mTaskSupervisor.updateTopResumedActivityIfNeeded(); + mTaskSupervisor.updateTopResumedActivityIfNeeded(reason); if (r == null && prevR.mDisplayContent != null && prevR.mDisplayContent.getFocusedRootTask() == null) { // Only need to notify DWPC when no activity will resume. @@ -773,9 +774,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { Slog.v(TAG, "set resumed activity to:" + record + " reason:" + reason); } setResumedActivity(record, reason + " - onActivityStateChanged"); - if (record == mRootWindowContainer.getTopResumedActivity()) { - mAtmService.setResumedActivityUncheckLocked(record, reason); - } mTaskSupervisor.mRecentTasks.add(record.getTask()); } } @@ -1621,7 +1619,8 @@ class TaskFragment extends WindowContainer<WindowContainer> { ProtoLog.d(WM_DEBUG_STATES, "Auto-PIP allowed, entering PIP mode " + "directly: %s, didAutoPip: %b", prev, didAutoPip); } else { - schedulePauseActivity(prev, userLeaving, pauseImmediately, reason); + schedulePauseActivity(prev, userLeaving, pauseImmediately, + false /* autoEnteringPip */, reason); } } else { mPausingActivity = null; @@ -1675,7 +1674,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { } void schedulePauseActivity(ActivityRecord prev, boolean userLeaving, - boolean pauseImmediately, String reason) { + boolean pauseImmediately, boolean autoEnteringPip, String reason) { ProtoLog.v(WM_DEBUG_STATES, "Enqueueing pending pause: %s", prev); try { EventLogTags.writeWmPauseActivity(prev.mUserId, System.identityHashCode(prev), @@ -1683,7 +1682,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { mAtmService.getLifecycleManager().scheduleTransaction(prev.app.getThread(), prev.token, PauseActivityItem.obtain(prev.finishing, userLeaving, - prev.configChangeFlags, pauseImmediately)); + prev.configChangeFlags, pauseImmediately, autoEnteringPip)); } catch (Exception e) { // Ignore exception, if process died other code will cleanup. Slog.w(TAG, "Exception thrown during pause", e); @@ -2210,11 +2209,13 @@ class TaskFragment extends WindowContainer<WindowContainer> { mTmpBounds.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight); final DisplayPolicy policy = rootTask.mDisplayContent.getDisplayPolicy(); - policy.getNonDecorInsetsLw(displayInfo.rotation, - displayInfo.displayCutout, mTmpInsets); + final WmDisplayCutout cutout = + rootTask.mDisplayContent.calculateDisplayCutoutForRotation(displayInfo.rotation); + final DisplayFrames displayFrames = policy.getSimulatedDisplayFrames(displayInfo.rotation, + displayInfo.logicalWidth, displayInfo.logicalHeight, cutout); + policy.getNonDecorInsetsWithSimulatedFrame(displayFrames, mTmpInsets); intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, mTmpInsets); - - policy.convertNonDecorInsetsToStableInsets(mTmpInsets, displayInfo.rotation); + policy.getStableInsetsWithSimulatedFrame(displayFrames, mTmpInsets); intersectWithInsetsIfFits(outStableBounds, mTmpBounds, mTmpInsets); } diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index 88059e1a0d04..d615583f4d7f 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -49,7 +49,9 @@ import android.window.ITaskFragmentOrganizer; import android.window.ITaskFragmentOrganizerController; import android.window.TaskFragmentInfo; import android.window.TaskFragmentTransaction; +import android.window.WindowContainerTransaction; +import com.android.internal.protolog.ProtoLogGroup; import com.android.internal.protolog.common.ProtoLog; import java.lang.annotation.Retention; @@ -68,6 +70,8 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr private final ActivityTaskManagerService mAtmService; private final WindowManagerGlobalLock mGlobalLock; + private final WindowOrganizerController mWindowOrganizerController; + /** * A Map which manages the relationship between * {@link ITaskFragmentOrganizer} and {@link TaskFragmentOrganizerState} @@ -82,9 +86,11 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr private final ArraySet<Task> mTmpTaskSet = new ArraySet<>(); - TaskFragmentOrganizerController(ActivityTaskManagerService atm) { - mAtmService = atm; + TaskFragmentOrganizerController(@NonNull ActivityTaskManagerService atm, + @NonNull WindowOrganizerController windowOrganizerController) { + mAtmService = requireNonNull(atm); mGlobalLock = atm.mGlobalLock; + mWindowOrganizerController = requireNonNull(windowOrganizerController); } /** @@ -131,6 +137,14 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr private final SparseArray<RemoteAnimationDefinition> mRemoteAnimationDefinitions = new SparseArray<>(); + /** + * List of {@link TaskFragmentTransaction#getTransactionToken()} that have been sent to the + * organizer. If the transaction is sent during a transition, the + * {@link TransitionController} will wait until the transaction is finished. + * @see #onTransactionFinished(IBinder) + */ + private final List<IBinder> mRunningTransactions = new ArrayList<>(); + TaskFragmentOrganizerState(ITaskFragmentOrganizer organizer, int pid, int uid) { mOrganizer = organizer; mOrganizerPid = pid; @@ -176,6 +190,10 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr taskFragment.removeImmediately(); mOrganizedTaskFragments.remove(taskFragment); } + for (int i = mRunningTransactions.size() - 1; i >= 0; i--) { + // Cleanup any running transaction to unblock the current transition. + onTransactionFinished(mRunningTransactions.get(i)); + } mOrganizer.asBinder().unlinkToDeath(this, 0 /*flags*/); } @@ -320,6 +338,40 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr .setActivityIntent(activity.intent) .setActivityToken(activityToken); } + + void dispatchTransaction(@NonNull TaskFragmentTransaction transaction) { + if (transaction.isEmpty()) { + return; + } + try { + mOrganizer.onTransactionReady(transaction); + } catch (RemoteException e) { + Slog.d(TAG, "Exception sending TaskFragmentTransaction", e); + return; + } + onTransactionStarted(transaction.getTransactionToken()); + } + + /** Called when the transaction is sent to the organizer. */ + void onTransactionStarted(@NonNull IBinder transactionToken) { + if (!mWindowOrganizerController.getTransitionController().isCollecting()) { + return; + } + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + "Defer transition ready for TaskFragmentTransaction=%s", transactionToken); + mRunningTransactions.add(transactionToken); + mWindowOrganizerController.getTransitionController().deferTransitionReady(); + } + + /** Called when the transaction is finished. */ + void onTransactionFinished(@NonNull IBinder transactionToken) { + if (!mRunningTransactions.remove(transactionToken)) { + return; + } + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + "Continue transition ready for TaskFragmentTransaction=%s", transactionToken); + mWindowOrganizerController.getTransitionController().continueTransitionReady(); + } } @Nullable @@ -336,7 +388,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } @Override - public void registerOrganizer(ITaskFragmentOrganizer organizer) { + public void registerOrganizer(@NonNull ITaskFragmentOrganizer organizer) { final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); synchronized (mGlobalLock) { @@ -354,7 +406,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } @Override - public void unregisterOrganizer(ITaskFragmentOrganizer organizer) { + public void unregisterOrganizer(@NonNull ITaskFragmentOrganizer organizer) { validateAndGetState(organizer); final int pid = Binder.getCallingPid(); final long uid = Binder.getCallingUid(); @@ -372,8 +424,8 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } @Override - public void registerRemoteAnimations(ITaskFragmentOrganizer organizer, int taskId, - RemoteAnimationDefinition definition) { + public void registerRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer, int taskId, + @NonNull RemoteAnimationDefinition definition) { final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); synchronized (mGlobalLock) { @@ -398,7 +450,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } @Override - public void unregisterRemoteAnimations(ITaskFragmentOrganizer organizer, int taskId) { + public void unregisterRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer, int taskId) { final int pid = Binder.getCallingPid(); final long uid = Binder.getCallingUid(); synchronized (mGlobalLock) { @@ -416,6 +468,17 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } } + @Override + public void onTransactionHandled(@NonNull ITaskFragmentOrganizer organizer, + @NonNull IBinder transactionToken, @NonNull WindowContainerTransaction wct) { + synchronized (mGlobalLock) { + // Keep the calling identity to avoid unsecure change. + mWindowOrganizerController.applyTransaction(wct); + final TaskFragmentOrganizerState state = validateAndGetState(organizer); + state.onTransactionFinished(transactionToken); + } + } + /** * Gets the {@link RemoteAnimationDefinition} set on the given organizer if exists. Returns * {@code null} if it doesn't, or if the organizer has activity(ies) embedded in untrusted mode. @@ -775,13 +838,13 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } final int organizerNum = mPendingTaskFragmentEvents.size(); for (int i = 0; i < organizerNum; i++) { - final ITaskFragmentOrganizer organizer = mTaskFragmentOrganizerState.get( - mPendingTaskFragmentEvents.keyAt(i)).mOrganizer; - dispatchPendingEvents(organizer, mPendingTaskFragmentEvents.valueAt(i)); + final TaskFragmentOrganizerState state = + mTaskFragmentOrganizerState.get(mPendingTaskFragmentEvents.keyAt(i)); + dispatchPendingEvents(state, mPendingTaskFragmentEvents.valueAt(i)); } } - void dispatchPendingEvents(@NonNull ITaskFragmentOrganizer organizer, + void dispatchPendingEvents(@NonNull TaskFragmentOrganizerState state, @NonNull List<PendingTaskFragmentEvent> pendingEvents) { if (pendingEvents.isEmpty()) { return; @@ -817,7 +880,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr if (mTmpTaskSet.add(task)) { // Make sure the organizer know about the Task config. transaction.addChange(prepareChange(new PendingTaskFragmentEvent.Builder( - PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED, organizer) + PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED, state.mOrganizer) .setTask(task) .build())); } @@ -825,7 +888,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr transaction.addChange(prepareChange(event)); } mTmpTaskSet.clear(); - dispatchTransactionInfo(organizer, transaction); + state.dispatchTransaction(transaction); pendingEvents.removeAll(candidateEvents); } @@ -855,6 +918,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } final ITaskFragmentOrganizer organizer = taskFragment.getTaskFragmentOrganizer(); + final TaskFragmentOrganizerState state = validateAndGetState(organizer); final TaskFragmentTransaction transaction = new TaskFragmentTransaction(); // Make sure the organizer know about the Task config. transaction.addChange(prepareChange(new PendingTaskFragmentEvent.Builder( @@ -862,22 +926,10 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr .setTask(taskFragment.getTask()) .build())); transaction.addChange(prepareChange(event)); - dispatchTransactionInfo(event.mTaskFragmentOrg, transaction); + state.dispatchTransaction(transaction); mPendingTaskFragmentEvents.get(organizer.asBinder()).remove(event); } - private void dispatchTransactionInfo(@NonNull ITaskFragmentOrganizer organizer, - @NonNull TaskFragmentTransaction transaction) { - if (transaction.isEmpty()) { - return; - } - try { - organizer.onTransactionReady(transaction); - } catch (RemoteException e) { - Slog.d(TAG, "Exception sending TaskFragmentTransaction", e); - } - } - @Nullable private TaskFragmentTransaction.Change prepareChange( @NonNull PendingTaskFragmentEvent event) { diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index fa2ab31904a3..80b7514d3502 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; @@ -68,6 +69,7 @@ import android.app.ActivityManager; import android.content.pm.ActivityInfo; import android.graphics.Point; import android.graphics.Rect; +import android.hardware.HardwareBuffer; import android.os.Binder; import android.os.IBinder; import android.os.IRemoteCallback; @@ -205,6 +207,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe /** @see #setCanPipOnFinish */ private boolean mCanPipOnFinish = true; + private boolean mIsSeamlessRotation = false; + private IContainerFreezer mContainerFreezer = null; + Transition(@TransitionType int type, @TransitionFlags int flags, TransitionController controller, BLASTSyncEngine syncEngine) { mType = type; @@ -265,10 +270,31 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe return mTargetDisplays.contains(dc); } + /** Set a transition to be a seamless-rotation. */ void setSeamlessRotation(@NonNull WindowContainer wc) { final ChangeInfo info = mChanges.get(wc); if (info == null) return; info.mFlags = info.mFlags | ChangeInfo.FLAG_SEAMLESS_ROTATION; + onSeamlessRotating(wc.getDisplayContent()); + } + + /** + * Called when it's been determined that this is transition is a seamless rotation. This should + * be called before any WM changes have happened. + */ + void onSeamlessRotating(@NonNull DisplayContent dc) { + // Don't need to do anything special if everything is using BLAST sync already. + if (mSyncEngine.getSyncSet(mSyncId).mSyncMethod == BLASTSyncEngine.METHOD_BLAST) return; + if (mContainerFreezer == null) { + mContainerFreezer = new ScreenshotFreezer(); + } + mIsSeamlessRotation = true; + final WindowState top = dc.getDisplayPolicy().getTopFullscreenOpaqueWindow(); + if (top != null) { + top.mSyncMethodOverride = BLASTSyncEngine.METHOD_BLAST; + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Override sync-method for %s " + + "because seamless rotating", top.getName()); + } } /** @@ -285,6 +311,11 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe } } + /** Only for testing. */ + void setContainerFreezer(IContainerFreezer freezer) { + mContainerFreezer = freezer; + } + @TransitionState int getState() { return mState; @@ -314,13 +345,18 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe return mState == STATE_COLLECTING || mState == STATE_STARTED; } - /** Starts collecting phase. Once this starts, all relevant surface operations are sync. */ + @VisibleForTesting void startCollecting(long timeoutMs) { + startCollecting(timeoutMs, TransitionController.SYNC_METHOD); + } + + /** Starts collecting phase. Once this starts, all relevant surface operations are sync. */ + void startCollecting(long timeoutMs, int method) { if (mState != STATE_PENDING) { throw new IllegalStateException("Attempting to re-use a transition"); } mState = STATE_COLLECTING; - mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG); + mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG, method); mController.mTransitionTracer.logState(this); } @@ -415,6 +451,37 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe } /** + * Records that a particular container is changing visibly (ie. something about it is changing + * while it remains visible). This only effects windows that are already in the collecting + * transition. + */ + void collectVisibleChange(WindowContainer wc) { + if (mSyncEngine.getSyncSet(mSyncId).mSyncMethod == BLASTSyncEngine.METHOD_BLAST) { + // All windows are synced already. + return; + } + if (!isInTransition(wc)) return; + + if (mContainerFreezer == null) { + mContainerFreezer = new ScreenshotFreezer(); + } + Transition.ChangeInfo change = mChanges.get(wc); + if (change == null || !change.mVisible || !wc.isVisibleRequested()) return; + // Note: many more tests have already been done by caller. + mContainerFreezer.freeze(wc, change.mAbsoluteBounds); + } + + /** + * @return {@code true} if `wc` is a participant or is a descendant of one. + */ + boolean isInTransition(WindowContainer wc) { + for (WindowContainer p = wc; p != null; p = p.getParent()) { + if (mParticipants.contains(p)) return true; + } + return false; + } + + /** * Specifies configuration change explicitly for the window container, so it can be chosen as * transition target. This is usually used with transition mode * {@link android.view.WindowManager#TRANSIT_CHANGE}. @@ -531,6 +598,10 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe displays.add(target.getDisplayContent()); } } + // Remove screenshot layers if necessary + if (mContainerFreezer != null) { + mContainerFreezer.cleanUp(t); + } // Need to update layers on involved displays since they were all paused while // the animation played. This puts the layers back into the correct order. mController.mBuildingFinishLayers = true; @@ -817,6 +888,19 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe transaction); if (mOverrideOptions != null) { info.setAnimationOptions(mOverrideOptions); + if (mOverrideOptions.getType() == ANIM_OPEN_CROSS_PROFILE_APPS) { + for (int i = 0; i < mTargets.size(); ++i) { + final TransitionInfo.Change c = info.getChanges().get(i); + final ActivityRecord ar = mTargets.get(i).asActivityRecord(); + if (ar == null || c.getMode() != TRANSIT_OPEN) continue; + int flags = c.getFlags(); + flags |= ar.mUserId == ar.mWmService.mCurrentUserId + ? TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL + : TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL; + c.setFlags(flags); + break; + } + } } // TODO(b/188669821): Move to animation impl in shell. @@ -1641,6 +1725,19 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe return mainWin.getAttrs().rotationAnimation; } + /** Applies the new configuration and returns {@code true} if there is a display change. */ + boolean applyDisplayChangeIfNeeded() { + boolean changed = false; + for (int i = mParticipants.size() - 1; i >= 0; --i) { + final WindowContainer<?> wc = mParticipants.valueAt(i); + final DisplayContent dc = wc.asDisplayContent(); + if (dc == null || !mChanges.get(dc).hasChanged(dc)) continue; + dc.sendNewConfiguration(); + changed = true; + } + return changed; + } + boolean getLegacyIsReady() { return isCollecting() && mSyncId >= 0; } @@ -1802,6 +1899,8 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe /** This undoes one call to {@link #deferTransitionReady}. */ void continueTransitionReady() { --mReadyTracker.mDeferReadyDepth; + // Apply ready in case it is waiting for the previous defer call. + applyReady(); } /** @@ -1967,4 +2066,111 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe return sortedTargets; } } + + /** + * Interface for freezing a container's content during sync preparation. Really just one impl + * but broken into an interface for testing (since you can't take screenshots in unit tests). + */ + interface IContainerFreezer { + /** + * Makes sure a particular window is "frozen" for the remainder of a sync. + * + * @return whether the freeze was successful. It fails if `wc` is already in a frozen window + * or is not visible/ready. + */ + boolean freeze(@NonNull WindowContainer wc, @NonNull Rect bounds); + + /** Populates `t` with operations that clean-up any state created to set-up the freeze. */ + void cleanUp(SurfaceControl.Transaction t); + } + + /** + * Freezes container content by taking a screenshot. Because screenshots are heavy, usage of + * any container "freeze" is currently explicit. WM code needs to be prudent about which + * containers to freeze. + */ + @VisibleForTesting + private class ScreenshotFreezer implements IContainerFreezer { + /** Values are the screenshot "surfaces" or null if it was frozen via BLAST override. */ + private final ArrayMap<WindowContainer, SurfaceControl> mSnapshots = new ArrayMap<>(); + + /** Takes a screenshot and puts it at the top of the container's surface. */ + @Override + public boolean freeze(@NonNull WindowContainer wc, @NonNull Rect bounds) { + if (!wc.isVisibleRequested()) return false; + + // Check if any parents have already been "frozen". If so, `wc` is already part of that + // snapshot, so just skip it. + for (WindowContainer p = wc; p != null; p = p.getParent()) { + if (mSnapshots.containsKey(p)) return false; + } + + if (mIsSeamlessRotation) { + WindowState top = wc.getDisplayContent() == null ? null + : wc.getDisplayContent().getDisplayPolicy().getTopFullscreenOpaqueWindow(); + if (top != null && (top == wc || top.isDescendantOf(wc))) { + // Don't use screenshots for seamless windows: these will use BLAST even if not + // BLAST mode. + mSnapshots.put(wc, null); + return true; + } + } + + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Screenshotting %s [%s]", + wc.toString(), bounds.toString()); + + Rect cropBounds = new Rect(bounds); + cropBounds.offsetTo(0, 0); + SurfaceControl.LayerCaptureArgs captureArgs = + new SurfaceControl.LayerCaptureArgs.Builder(wc.getSurfaceControl()) + .setSourceCrop(cropBounds) + .setCaptureSecureLayers(true) + .setAllowProtected(true) + .build(); + SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = + SurfaceControl.captureLayers(captureArgs); + final HardwareBuffer buffer = screenshotBuffer == null ? null + : screenshotBuffer.getHardwareBuffer(); + if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) { + // This can happen when display is not ready. + Slog.w(TAG, "Failed to capture screenshot for " + wc); + return false; + } + SurfaceControl snapshotSurface = wc.makeAnimationLeash() + .setName("transition snapshot: " + wc.toString()) + .setOpaque(true) + .setParent(wc.getSurfaceControl()) + .setSecure(screenshotBuffer.containsSecureLayers()) + .setCallsite("Transition.ScreenshotSync") + .setBLASTLayer() + .build(); + mSnapshots.put(wc, snapshotSurface); + SurfaceControl.Transaction t = wc.mWmService.mTransactionFactory.get(); + + t.setBuffer(snapshotSurface, buffer); + t.setDataSpace(snapshotSurface, screenshotBuffer.getColorSpace().getDataSpace()); + t.show(snapshotSurface); + + // Place it on top of anything else in the container. + t.setLayer(snapshotSurface, Integer.MAX_VALUE); + t.apply(); + t.close(); + + // Detach the screenshot on the sync transaction (the screenshot is just meant to + // freeze the window until the sync transaction is applied (with all its other + // corresponding changes), so this is how we unfreeze it. + wc.getSyncTransaction().reparent(snapshotSurface, null /* newParent */); + return true; + } + + @Override + public void cleanUp(SurfaceControl.Transaction t) { + for (int i = 0; i < mSnapshots.size(); ++i) { + SurfaceControl snap = mSnapshots.valueAt(i); + // May be null if it was frozen via BLAST override. + if (snap == null) continue; + t.remove(snap); + } + } + } } diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 4f324f22aa5e..f62efbfbd08d 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -46,6 +46,7 @@ import android.window.TransitionInfo; import android.window.TransitionRequestInfo; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLogGroup; import com.android.internal.protolog.common.ProtoLog; import com.android.server.LocalServices; @@ -64,6 +65,11 @@ class TransitionController { private static final boolean SHELL_TRANSITIONS_ROTATION = SystemProperties.getBoolean("persist.wm.debug.shell_transit_rotate", false); + /** Which sync method to use for transition syncs. */ + static final int SYNC_METHOD = + android.os.SystemProperties.getBoolean("persist.wm.debug.shell_transit_blast", true) + ? BLASTSyncEngine.METHOD_BLAST : BLASTSyncEngine.METHOD_NONE; + /** The same as legacy APP_TRANSITION_TIMEOUT_MS. */ private static final int DEFAULT_TIMEOUT_MS = 5000; /** Less duration for CHANGE type because it does not involve app startup. */ @@ -160,6 +166,12 @@ class TransitionController { /** Starts Collecting */ void moveToCollecting(@NonNull Transition transition) { + moveToCollecting(transition, SYNC_METHOD); + } + + /** Starts Collecting */ + @VisibleForTesting + void moveToCollecting(@NonNull Transition transition, int method) { if (mCollectingTransition != null) { throw new IllegalStateException("Simultaneous transition collection not supported."); } @@ -167,7 +179,7 @@ class TransitionController { // Distinguish change type because the response time is usually expected to be not too long. final long timeoutMs = transition.mType == TRANSIT_CHANGE ? CHANGE_TIMEOUT_MS : DEFAULT_TIMEOUT_MS; - mCollectingTransition.startCollecting(timeoutMs); + mCollectingTransition.startCollecting(timeoutMs, method); ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Start collecting in Transition: %s", mCollectingTransition); dispatchLegacyAppTransitionPending(); @@ -228,10 +240,7 @@ class TransitionController { */ boolean inCollectingTransition(@NonNull WindowContainer wc) { if (!isCollecting()) return false; - for (WindowContainer p = wc; p != null; p = p.getParent()) { - if (mCollectingTransition.mParticipants.contains(p)) return true; - } - return false; + return mCollectingTransition.isInTransition(wc); } /** @@ -247,9 +256,7 @@ class TransitionController { */ boolean inPlayingTransition(@NonNull WindowContainer wc) { for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) { - for (WindowContainer p = wc; p != null; p = p.getParent()) { - if (mPlayingTransitions.get(i).mParticipants.contains(p)) return true; - } + if (mPlayingTransitions.get(i).isInTransition(wc)) return true; } return false; } @@ -463,13 +470,13 @@ class TransitionController { } /** - * Collects the window containers which need to be synced with the changing display (e.g. - * rotating) to the given transition or the current collecting transition. + * Collects the window containers which need to be synced with the changing display area into + * the current collecting transition. */ - void collectForDisplayAreaChange(@NonNull DisplayArea<?> wc, @Nullable Transition incoming) { - if (incoming == null) incoming = mCollectingTransition; - if (incoming == null) return; - final Transition transition = incoming; + void collectForDisplayAreaChange(@NonNull DisplayArea<?> wc) { + final Transition transition = mCollectingTransition; + if (transition == null || !transition.mParticipants.contains(wc)) return; + transition.collectVisibleChange(wc); // Collect all visible tasks. wc.forAllLeafTasks(task -> { if (task.isVisible()) { @@ -489,6 +496,16 @@ class TransitionController { } } + /** + * Records that a particular container is changing visibly (ie. something about it is changing + * while it remains visible). This only effects windows that are already in the collecting + * transition. + */ + void collectVisibleChange(WindowContainer wc) { + if (!isCollecting()) return; + mCollectingTransition.collectVisibleChange(wc); + } + /** @see Transition#mStatusBarTransitionDelay */ void setStatusBarTransitionDelay(long delay) { if (mCollectingTransition == null) return; diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 6245005606d7..e7c0a8aba285 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -186,7 +186,7 @@ class WallpaperController { && animatingContainer.getAnimation() != null && animatingContainer.getAnimation().getShowWallpaper(); final boolean hasWallpaper = w.hasWallpaper() || animationWallpaper; - if (isRecentsTransitionTarget(w)) { + if (isRecentsTransitionTarget(w) || isBackAnimationTarget(w)) { if (DEBUG_WALLPAPER) Slog.v(TAG, "Found recents animation wallpaper target: " + w); mFindResults.setWallpaperTarget(w); return true; @@ -226,6 +226,13 @@ class WallpaperController { return controller != null && controller.isWallpaperVisible(w); } + private boolean isBackAnimationTarget(WindowState w) { + // The window is either the back activity or is in the task animating by the back gesture. + final BackNaviAnimationController bthController = mService.getBackNaviAnimationController(); + return bthController != null && bthController.isWallpaperVisible(w); + } + + /** * @see #computeLastWallpaperZoomOut() */ diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java index 6ee30bb956f0..8fdaec613ad5 100644 --- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java +++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java @@ -128,12 +128,15 @@ class WallpaperWindowToken extends WindowToken { if (visible && wallpaperTarget != null) { final RecentsAnimationController recentsAnimationController = mWmService.getRecentsAnimationController(); + final BackNaviAnimationController bac = mWmService.getBackNaviAnimationController(); if (recentsAnimationController != null && recentsAnimationController.isAnimatingTask(wallpaperTarget.getTask())) { // If the Recents animation is running, and the wallpaper target is the animating // task we want the wallpaper to be rotated in the same orientation as the // RecentsAnimation's target (e.g the launcher) recentsAnimationController.linkFixedRotationTransformIfNeeded(this); + } else if (bac != null && bac.isAnimatingTask(wallpaperTarget.getTask())) { + bac.linkFixedRotationTransformIfNeeded(this); } else if ((wallpaperTarget.mActivityRecord == null // Ignore invisible activity because it may be moving to background. || wallpaperTarget.mActivityRecord.mVisibleRequested) diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 25193d0b5ba3..92e52de2c01f 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -343,6 +343,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< BLASTSyncEngine.SyncGroup mSyncGroup = null; final SurfaceControl.Transaction mSyncTransaction; @SyncState int mSyncState = SYNC_STATE_NONE; + int mSyncMethodOverride = BLASTSyncEngine.METHOD_UNDEFINED; private final List<WindowContainerListener> mListeners = new ArrayList<>(); @@ -2825,6 +2826,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< */ void initializeChangeTransition(Rect startBounds, @Nullable SurfaceControl freezeTarget) { if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) { + mDisplayContent.mTransitionController.collectVisibleChange(this); // TODO(b/207070762): request shell transition for activityEmbedding change. return; } @@ -3015,6 +3017,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< final float windowCornerRadius = !inMultiWindowMode() ? getDisplayContent().getWindowCornerRadius() : 0; + if (asActivityRecord() != null + && asActivityRecord().isNeedsLetterboxedAnimation()) { + asActivityRecord().getLetterboxInnerBounds(mTmpRect); + } AnimationAdapter adapter = new LocalAnimationAdapter( new WindowAnimationSpec(a, mTmpPoint, mTmpRect, getDisplayContent().mAppTransition.canSkipFirstFrame(), @@ -3662,6 +3668,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< boolean onSyncFinishedDrawing() { if (mSyncState == SYNC_STATE_NONE) return false; mSyncState = SYNC_STATE_READY; + mSyncMethodOverride = BLASTSyncEngine.METHOD_UNDEFINED; mWmService.mWindowPlacerLocked.requestTraversal(); ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "onSyncFinishedDrawing %s", this); return true; @@ -3680,6 +3687,13 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< mSyncGroup = group; } + @Nullable + BLASTSyncEngine.SyncGroup getSyncGroup() { + if (mSyncGroup != null) return mSyncGroup; + if (mParent != null) return mParent.getSyncGroup(); + return null; + } + /** * Prepares this container for participation in a sync-group. This includes preparing all its * children. @@ -3719,6 +3733,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } if (cancel && mSyncGroup != null) mSyncGroup.onCancelSync(this); mSyncState = SYNC_STATE_NONE; + mSyncMethodOverride = BLASTSyncEngine.METHOD_UNDEFINED; mSyncGroup = null; } @@ -3821,6 +3836,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< // disable this when shell transitions is disabled. if (mTransitionController.isShellTransitionsEnabled()) { mSyncState = SYNC_STATE_NONE; + mSyncMethodOverride = BLASTSyncEngine.METHOD_UNDEFINED; } prepareSync(); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 6415918e2ecc..285e0ac1c67a 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -89,10 +89,10 @@ import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED; import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_RELAUNCH; +import static android.view.WindowManager.fixScale; import static android.view.WindowManagerGlobal.ADD_OKAY; import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW; import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED; -import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID; import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER; import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW; import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN; @@ -321,6 +321,7 @@ import com.android.server.policy.WindowManagerPolicy; import com.android.server.policy.WindowManagerPolicy.ScreenOffListener; import com.android.server.power.ShutdownThread; import com.android.server.utils.PriorityDump; +import com.android.server.wm.utils.WmDisplayCutout; import dalvik.annotation.optimization.NeverCompile; @@ -1323,15 +1324,10 @@ public class WindowManagerService extends IWindowManager.Stub }, UserHandle.ALL, suspendPackagesFilter, null, null); // Get persisted window scale setting - mWindowAnimationScaleSetting = Settings.Global.getFloat(resolver, - Settings.Global.WINDOW_ANIMATION_SCALE, mWindowAnimationScaleSetting); - mTransitionAnimationScaleSetting = Settings.Global.getFloat(resolver, - Settings.Global.TRANSITION_ANIMATION_SCALE, - context.getResources().getFloat( - R.dimen.config_appTransitionAnimationDurationScaleDefault)); - - setAnimatorDurationScale(Settings.Global.getFloat(resolver, - Settings.Global.ANIMATOR_DURATION_SCALE, mAnimatorDurationScaleSetting)); + mWindowAnimationScaleSetting = getWindowAnimationScaleSetting(); + mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting(); + + setAnimatorDurationScale(getAnimatorDurationScaleSetting()); mForceDesktopModeOnExternalDisplays = Settings.Global.getInt(resolver, DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0) != 0; @@ -1405,6 +1401,22 @@ public class WindowManagerService extends IWindowManager.Stub lightRadius); } + private float getTransitionAnimationScaleSetting() { + return fixScale(Settings.Global.getFloat(mContext.getContentResolver(), + Settings.Global.TRANSITION_ANIMATION_SCALE, mContext.getResources().getFloat( + R.dimen.config_appTransitionAnimationDurationScaleDefault))); + } + + private float getAnimatorDurationScaleSetting() { + return fixScale(Settings.Global.getFloat(mContext.getContentResolver(), + Settings.Global.ANIMATOR_DURATION_SCALE, mAnimatorDurationScaleSetting)); + } + + private float getWindowAnimationScaleSetting() { + return fixScale(Settings.Global.getFloat(mContext.getContentResolver(), + Settings.Global.WINDOW_ANIMATION_SCALE, mWindowAnimationScaleSetting)); + } + /** * Called after all entities (such as the {@link ActivityManagerService}) have been set up and * associated with the {@link WindowManagerService}. @@ -1858,7 +1870,8 @@ public class WindowManagerService extends IWindowManager.Stub ProtoLog.v(WM_DEBUG_ADD_REMOVE, "addWindow: New client %s" + ": window=%s Callers=%s", client.asBinder(), win, Debug.getCallers(5)); - if (win.isVisibleRequestedOrAdding() && displayContent.updateOrientation()) { + if ((win.isVisibleRequestedOrAdding() && displayContent.updateOrientation()) + || win.providesNonDecorInsets()) { displayContent.sendNewConfiguration(); } @@ -2575,7 +2588,7 @@ public class WindowManagerService extends IWindowManager.Stub final int maybeSyncSeqId; if (mUseBLASTSync && win.useBLASTSync() && viewVisibility != View.GONE && win.mSyncSeqId > lastSyncSeqId) { - maybeSyncSeqId = win.mSyncSeqId; + maybeSyncSeqId = win.shouldSyncWithBuffers() ? win.mSyncSeqId : -1; win.markRedrawForSyncReported(); } else { maybeSyncSeqId = -1; @@ -3411,11 +3424,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - static float fixScale(float scale) { - if (scale < 0) scale = 0; - else if (scale > 20) scale = 20; - return Math.abs(scale); - } @Override public void setAnimationScale(int which, float scale) { @@ -5340,24 +5348,16 @@ public class WindowManagerService extends IWindowManager.Stub final int mode = msg.arg1; switch (mode) { case WINDOW_ANIMATION_SCALE: { - mWindowAnimationScaleSetting = Settings.Global.getFloat( - mContext.getContentResolver(), - Settings.Global.WINDOW_ANIMATION_SCALE, - mWindowAnimationScaleSetting); + mWindowAnimationScaleSetting = getWindowAnimationScaleSetting(); break; } case TRANSITION_ANIMATION_SCALE: { - mTransitionAnimationScaleSetting = Settings.Global.getFloat( - mContext.getContentResolver(), - Settings.Global.TRANSITION_ANIMATION_SCALE, - mTransitionAnimationScaleSetting); + mTransitionAnimationScaleSetting = + getTransitionAnimationScaleSetting(); break; } case ANIMATION_DURATION_SCALE: { - mAnimatorDurationScaleSetting = Settings.Global.getFloat( - mContext.getContentResolver(), - Settings.Global.ANIMATOR_DURATION_SCALE, - mAnimatorDurationScaleSetting); + mAnimatorDurationScaleSetting = getAnimatorDurationScaleSetting(); dispatchNewAnimatorScaleLocked(null); break; } @@ -6384,27 +6384,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - /** - * Used by ActivityManager to determine where to position an app with aspect ratio shorter then - * the screen is. - * @see DisplayPolicy#getNavBarPosition() - */ - @Override - @WindowManagerPolicy.NavigationBarPosition - public int getNavBarPosition(int displayId) { - synchronized (mGlobalLock) { - // Perform layout if it was scheduled before to make sure that we get correct nav bar - // position when doing rotations. - final DisplayContent displayContent = mRoot.getDisplayContent(displayId); - if (displayContent == null) { - Slog.w(TAG, "getNavBarPosition with invalid displayId=" + displayId - + " callers=" + Debug.getCallers(3)); - return NAV_BAR_INVALID; - } - return displayContent.getDisplayPolicy().getNavBarPosition(); - } - } - @Override public void createInputConsumer(IBinder token, String name, int displayId, InputChannel inputChannel) { @@ -7243,7 +7222,9 @@ public class WindowManagerService extends IWindowManager.Stub final DisplayContent dc = mRoot.getDisplayContent(displayId); if (dc != null) { final DisplayInfo di = dc.getDisplayInfo(); - dc.getDisplayPolicy().getStableInsetsLw(di.rotation, di.displayCutout, outInsets); + final WmDisplayCutout cutout = dc.calculateDisplayCutoutForRotation(di.rotation); + dc.getDisplayPolicy().getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight, + cutout, outInsets); } } @@ -9299,4 +9280,9 @@ public class WindowManagerService extends IWindowManager.Stub "Unexpected letterbox background type: " + letterboxBackgroundType); } } + + BackNaviAnimationController getBackNaviAnimationController() { + return mAtmService.mBackNavigationController != null + ? mAtmService.mBackNavigationController.mBackNaviAnimationController : null; + } } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 3b9cd368a93b..f83925507512 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -147,7 +147,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub mGlobalLock = atm.mGlobalLock; mTaskOrganizerController = new TaskOrganizerController(mService); mDisplayAreaOrganizerController = new DisplayAreaOrganizerController(mService); - mTaskFragmentOrganizerController = new TaskFragmentOrganizerController(atm); + mTaskFragmentOrganizerController = new TaskFragmentOrganizerController(atm, this); } void setWindowManager(WindowManagerService wms) { @@ -397,17 +397,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub mService.deferWindowLayout(); mService.mTaskSupervisor.setDeferRootVisibilityUpdate(true /* deferUpdate */); try { - if (transition != null) { - // First check if we have a display rotation transition and if so, update it. - final DisplayContent dc = DisplayRotation.getDisplayFromTransition(transition); - if (dc != null && transition.mChanges.get(dc).hasChanged(dc)) { - // Go through all tasks and collect them before the rotation - // TODO(shell-transitions): move collect() to onConfigurationChange once - // wallpaper handling is synchronized. - dc.mTransitionController.collectForDisplayAreaChange(dc, transition); - dc.sendNewConfiguration(); - effects |= TRANSACT_EFFECTS_LIFECYCLE; - } + if (transition != null && transition.applyDisplayChangeIfNeeded()) { + effects |= TRANSACT_EFFECTS_LIFECYCLE; } final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps(); final int hopSize = hops.size(); @@ -428,15 +419,6 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub addToSyncSet(syncId, wc); } if (transition != null) transition.collect(wc); - final DisplayArea da = wc.asDisplayArea(); - // Only check DisplayArea here as a similar thing is done for DisplayContent above. - if (da != null && wc.asDisplayContent() == null - && entry.getValue().getWindowingMode() != da.getWindowingMode()) { - // Go through all tasks and collect them before changing the windowing mode of a - // display-level container. - // TODO(shell-transitions): handle this more elegantly. - da.mTransitionController.collectForDisplayAreaChange(da, transition); - } if ((entry.getValue().getChangeMask() & WindowContainerTransaction.Change.CHANGE_FORCE_NO_PIP) != 0) { @@ -1423,7 +1405,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub private BLASTSyncEngine.SyncGroup prepareSyncWithOrganizer( IWindowContainerTransactionCallback callback) { final BLASTSyncEngine.SyncGroup s = mService.mWindowManager.mSyncEngine - .prepareSyncSet(this, ""); + .prepareSyncSet(this, "", BLASTSyncEngine.METHOD_BLAST); mTransactionCallbacksByPendingSyncId.put(s.mSyncId, callback); return s; } diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 87b0c8b77904..202fe558f938 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -25,6 +25,7 @@ import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION; import static com.android.internal.util.Preconditions.checkArgument; +import static com.android.server.am.ProcessList.INVALID_ADJ; import static com.android.server.wm.ActivityRecord.State.DESTROYED; import static com.android.server.wm.ActivityRecord.State.DESTROYING; import static com.android.server.wm.ActivityRecord.State.PAUSED; @@ -123,6 +124,8 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio private volatile int mCurProcState = PROCESS_STATE_NONEXISTENT; // Last reported process state; private volatile int mRepProcState = PROCESS_STATE_NONEXISTENT; + // Currently computed oom adj score + private volatile int mCurAdj = INVALID_ADJ; // are we in the process of crashing? private volatile boolean mCrashing; // does the app have a not responding dialog? @@ -317,6 +320,14 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio return mCurProcState; } + public void setCurrentAdj(int curAdj) { + mCurAdj = curAdj; + } + + int getCurrentAdj() { + return mCurAdj; + } + /** * Sets the computed process state from the oom adjustment calculation. This is frequently * called in activity manager's lock, so don't use window manager lock here. diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 41bcbf6736dc..d79011be7931 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -243,6 +243,7 @@ import android.view.View; import android.view.ViewDebug; import android.view.ViewTreeObserver; import android.view.WindowInfo; +import android.view.WindowInsets; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowManager; import android.view.animation.Animation; @@ -391,7 +392,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP */ int mSyncSeqId = 0; - /** The last syncId associated with a prepareSync or 0 when no sync is active. */ + /** The last syncId associated with a BLAST prepareSync or 0 when no BLAST sync is active. */ int mPrepareSyncSeqId = 0; /** @@ -1897,6 +1898,19 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return (mPolicyVisibility & POLICY_VISIBILITY_ALL) == POLICY_VISIBILITY_ALL; } + boolean providesNonDecorInsets() { + if (mProvidedInsetsSources == null) { + return false; + } + for (int i = mProvidedInsetsSources.size() - 1; i >= 0; i--) { + final int type = mProvidedInsetsSources.keyAt(i); + if ((InsetsState.toPublicType(type) & WindowInsets.Type.navigationBars()) != 0) { + return true; + } + } + return false; + } + void clearPolicyVisibilityFlag(int policyVisibilityFlag) { mPolicyVisibility &= ~policyVisibilityFlag; mWmService.scheduleAnimationLocked(); @@ -2609,14 +2623,19 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } removeImmediately(); - // Removing a visible window will effect the computed orientation - // So just update orientation if needed. + boolean sentNewConfig = false; if (wasVisible) { + // Removing a visible window will effect the computed orientation + // So just update orientation if needed. final DisplayContent displayContent = getDisplayContent(); if (displayContent.updateOrientation()) { displayContent.sendNewConfiguration(); + sentNewConfig = true; } } + if (!sentNewConfig && providesNonDecorInsets()) { + getDisplayContent().sendNewConfiguration(); + } mWmService.updateFocusedWindowLocked(isFocused() ? UPDATE_FOCUS_REMOVING_FOCUS : UPDATE_FOCUS_NORMAL, @@ -3803,6 +3822,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return wpc != null && wpc.registeredForDisplayAreaConfigChanges(); } + WindowProcessController getProcess() { + return mWpcForDisplayAreaConfigChanges; + } + /** * Fills the given window frames and merged configuration for the client. * @@ -3889,9 +3912,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP fillClientWindowFramesAndConfiguration(mClientWindowFrames, mLastReportedConfiguration, true /* useLatestConfig */, false /* relayoutVisible */); final boolean syncRedraw = shouldSendRedrawForSync(); + final boolean syncWithBuffers = syncRedraw && shouldSyncWithBuffers(); final boolean reportDraw = syncRedraw || drawPending; final boolean isDragResizeChanged = isDragResizeChanged(); - final boolean forceRelayout = syncRedraw || isDragResizeChanged; + final boolean forceRelayout = syncWithBuffers || isDragResizeChanged; final DisplayContent displayContent = getDisplayContent(); final boolean alwaysConsumeSystemBars = displayContent.getDisplayPolicy().areSystemBarsForcedShownLw(); @@ -3917,7 +3941,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP try { mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration, getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId, - mSyncSeqId, resizeMode); + syncWithBuffers ? mSyncSeqId : -1, resizeMode); if (drawPending && prevRotation >= 0 && prevRotation != mLastReportedConfiguration .getMergedConfiguration().windowConfiguration.getRotation()) { mOrientationChangeRedrawRequestTime = SystemClock.elapsedRealtime(); @@ -5932,7 +5956,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } mSyncSeqId++; - mPrepareSyncSeqId = mSyncSeqId; + if (getSyncMethod() == BLASTSyncEngine.METHOD_BLAST) { + mPrepareSyncSeqId = mSyncSeqId; + } requestRedrawForSync(); return true; } @@ -6005,6 +6031,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP postDrawTransaction = null; skipLayout = true; } else if (syncActive) { + // Currently in a Sync that is using BLAST. if (!syncStillPending) { onSyncFinishedDrawing(); } @@ -6013,6 +6040,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // Consume the transaction because the sync group will merge it. postDrawTransaction = null; } + } else if (useBLASTSync()) { + // Sync that is not using BLAST + onSyncFinishedDrawing(); } final boolean layoutNeeded = @@ -6049,7 +6079,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } boolean hasWallpaperForLetterboxBackground() { - return mActivityRecord != null && mActivityRecord.hasWallpaperBackgroudForLetterbox(); + return mActivityRecord != null && mActivityRecord.hasWallpaperBackgroundForLetterbox(); } /** @@ -6071,6 +6101,18 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return useBLASTSync(); } + int getSyncMethod() { + final BLASTSyncEngine.SyncGroup syncGroup = getSyncGroup(); + if (syncGroup == null) return BLASTSyncEngine.METHOD_NONE; + if (mSyncMethodOverride != BLASTSyncEngine.METHOD_UNDEFINED) return mSyncMethodOverride; + return syncGroup.mSyncMethod; + } + + boolean shouldSyncWithBuffers() { + if (!mDrawHandlers.isEmpty()) return true; + return getSyncMethod() == BLASTSyncEngine.METHOD_BLAST; + } + void requestRedrawForSync() { mRedrawForSyncReported = false; } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt new file mode 100644 index 000000000000..3c3172ba3a13 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm + +import android.content.pm.PackageManager +import android.content.pm.UserInfo +import android.os.Build +import android.util.Log +import com.android.server.testutils.any +import com.android.server.testutils.spy +import com.android.server.testutils.whenever +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mockito.doAnswer + +@RunWith(JUnit4::class) +class DeletePackageHelperTest { + + @Rule + @JvmField + val rule = MockSystemRule() + + private lateinit var mPms: PackageManagerService + private lateinit var mUserManagerInternal: UserManagerInternal + + @Before + @Throws(Exception::class) + fun setup() { + Log.i("system.out", "setup", Exception()) + rule.system().stageNominalSystemState() + rule.system().stageScanExistingPackage( + "a.data.package", 1L, rule.system().dataAppDirectory) + + mUserManagerInternal = rule.mocks().injector.userManagerInternal + whenever(mUserManagerInternal.getUserIds()).thenReturn(intArrayOf(0, 1)) + + mPms = createPackageManagerService() + doAnswer { false }.`when`(mPms).isPackageDeviceAdmin(any(), any()) + doAnswer { null }.`when`(mPms).freezePackageForDelete(any(), any(), any(), any()) + } + + private fun createPackageManagerService(): PackageManagerService { + return spy(PackageManagerService(rule.mocks().injector, + false /*coreOnly*/, + false /*factoryTest*/, + MockSystem.DEFAULT_VERSION_INFO.fingerprint, + false /*isEngBuild*/, + false /*isUserDebugBuild*/, + Build.VERSION_CODES.CUR_DEVELOPMENT, + Build.VERSION.INCREMENTAL)) + } + + @Test + fun deleteSystemPackageFailsIfNotAdminAndNotProfile() { + val ps = mPms.mSettings.getPackageLPr("a.data.package") + whenever(PackageManagerServiceUtils.isSystemApp(ps)).thenReturn(true) + whenever(mUserManagerInternal.getUserInfo(1)).thenReturn(UserInfo(1, "test", 0)) + whenever(mUserManagerInternal.getProfileParentId(1)).thenReturn(1) + + val dph = DeletePackageHelper(mPms) + val result = dph.deletePackageX("a.data.package", 1L, 1, 0, false) + + assertThat(result).isEqualTo(PackageManager.DELETE_FAILED_USER_RESTRICTED) + } + + @Test + fun deleteSystemPackageFailsIfProfileOfNonAdmin() { + val userId = 1 + val parentId = 5 + val ps = mPms.mSettings.getPackageLPr("a.data.package") + whenever(PackageManagerServiceUtils.isSystemApp(ps)).thenReturn(true) + whenever(mUserManagerInternal.getUserInfo(userId)).thenReturn( + UserInfo(userId, "test", UserInfo.FLAG_PROFILE)) + whenever(mUserManagerInternal.getProfileParentId(userId)).thenReturn(parentId) + whenever(mUserManagerInternal.getUserInfo(parentId)).thenReturn( + UserInfo(userId, "testparent", 0)) + + val dph = DeletePackageHelper(mPms) + val result = dph.deletePackageX("a.data.package", 1L, userId, 0, false) + + assertThat(result).isEqualTo(PackageManager.DELETE_FAILED_USER_RESTRICTED) + } + + @Test + fun deleteSystemPackageSucceedsIfAdmin() { + val ps = mPms.mSettings.getPackageLPr("a.data.package") + whenever(PackageManagerServiceUtils.isSystemApp(ps)).thenReturn(true) + whenever(mUserManagerInternal.getUserInfo(1)).thenReturn( + UserInfo(1, "test", UserInfo.FLAG_ADMIN)) + + val dph = DeletePackageHelper(mPms) + val result = dph.deletePackageX("a.data.package", 1L, 1, + PackageManager.DELETE_SYSTEM_APP, false) + + assertThat(result).isEqualTo(PackageManager.DELETE_SUCCEEDED) + } + + @Test + fun deleteSystemPackageSucceedsIfProfileOfAdmin() { + val userId = 1 + val parentId = 5 + val ps = mPms.mSettings.getPackageLPr("a.data.package") + whenever(PackageManagerServiceUtils.isSystemApp(ps)).thenReturn(true) + whenever(mUserManagerInternal.getUserInfo(userId)).thenReturn( + UserInfo(userId, "test", UserInfo.FLAG_PROFILE)) + whenever(mUserManagerInternal.getProfileParentId(userId)).thenReturn(parentId) + whenever(mUserManagerInternal.getUserInfo(parentId)).thenReturn( + UserInfo(userId, "testparent", UserInfo.FLAG_ADMIN)) + + val dph = DeletePackageHelper(mPms) + val result = dph.deletePackageX("a.data.package", 1L, userId, + PackageManager.DELETE_SYSTEM_APP, false) + + assertThat(result).isEqualTo(PackageManager.DELETE_SUCCEEDED) + } + + @Test + fun deleteSystemPackageSucceedsIfNotAdminButDeleteSystemAppSpecified() { + val ps = mPms.mSettings.getPackageLPr("a.data.package") + whenever(PackageManagerServiceUtils.isSystemApp(ps)).thenReturn(true) + whenever(mUserManagerInternal.getUserInfo(1)).thenReturn(UserInfo(1, "test", 0)) + whenever(mUserManagerInternal.getProfileParentId(1)).thenReturn(1) + + val dph = DeletePackageHelper(mPms) + val result = dph.deletePackageX("a.data.package", 1L, 1, + PackageManager.DELETE_SYSTEM_APP, false) + + assertThat(result).isEqualTo(PackageManager.DELETE_SUCCEEDED) + } +} diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java index e2c3a94fc187..4c939f077940 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java @@ -83,6 +83,7 @@ public class VirtualAudioControllerTest { VirtualDeviceParams.ACTIVITY_POLICY_DEFAULT_ALLOWED, /* activityListener= */ null, /* activityBlockedCallback= */ null, + /* secureWindowCallback= */ null, /* deviceProfile= */ DEVICE_PROFILE_APP_STREAMING); } diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java index 34b40c716b4f..b1ad8ec1cb66 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java @@ -16,53 +16,79 @@ package com.android.server.pm; +import static android.os.UserManager.DISALLOW_USER_SWITCH; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.ActivityManager; +import android.app.PropertyInvalidatedCache; +import android.content.Context; import android.content.pm.UserInfo; import android.os.Bundle; import android.os.FileUtils; +import android.os.Looper; import android.os.Parcelable; import android.os.UserHandle; import android.os.UserManager; import android.platform.test.annotations.Postsubmit; import android.support.test.uiautomator.UiDevice; -import android.test.AndroidTestCase; -import android.text.TextUtils; import android.util.AtomicFile; import androidx.test.InstrumentationRegistry; -import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.LocalServices; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; import java.io.File; import java.io.IOException; import java.util.Arrays; +/** Test {@link UserManagerService} functionality. */ @Postsubmit -@SmallTest -public class UserManagerServiceTest extends AndroidTestCase { +@RunWith(AndroidJUnit4.class) +public class UserManagerServiceTest { private static String[] STRING_ARRAY = new String[] {"<tag", "<![CDATA["}; private File restrictionsFile; private int tempUserId = UserHandle.USER_NULL; + private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext(); + private UserManagerService mUserManagerService; + + @Before + public void setup() throws Exception { + // Currently UserManagerService cannot be instantiated twice inside a VM without a cleanup + // TODO: Remove once UMS supports proper dependency injection + if (Looper.myLooper() == null) { + Looper.prepare(); + } + // Disable binder caches in this process. + PropertyInvalidatedCache.disableForTestMode(); + + LocalServices.removeServiceForTest(UserManagerInternal.class); + mUserManagerService = new UserManagerService(InstrumentationRegistry.getContext()); - @Override - protected void setUp() throws Exception { - super.setUp(); restrictionsFile = new File(mContext.getCacheDir(), "restrictions.xml"); restrictionsFile.delete(); } - @Override - protected void tearDown() throws Exception { + @After + public void teardown() throws Exception { restrictionsFile.delete(); if (tempUserId != UserHandle.USER_NULL) { UserManager.get(mContext).removeUser(tempUserId); } - super.tearDown(); } + @Test public void testWriteReadApplicationRestrictions() throws IOException { AtomicFile atomicFile = new AtomicFile(restrictionsFile); Bundle bundle = createBundle(); UserManagerService.writeApplicationRestrictionsLAr(bundle, atomicFile); - assertTrue(atomicFile.getBaseFile().exists()); + assertThat(atomicFile.getBaseFile().exists()).isTrue(); String s = FileUtils.readTextFile(restrictionsFile, 10000, ""); System.out.println("restrictionsFile: " + s); bundle = UserManagerService.readApplicationRestrictionsLAr(atomicFile); @@ -70,22 +96,22 @@ public class UserManagerServiceTest extends AndroidTestCase { assertBundle(bundle); } + @Test public void testAddUserWithAccount() { UserManager um = UserManager.get(mContext); UserInfo user = um.createUser("Test User", 0); - assertNotNull(user); + assertThat(user).isNotNull(); tempUserId = user.id; String accountName = "Test Account"; um.setUserAccount(tempUserId, accountName); - assertEquals(accountName, um.getUserAccount(tempUserId)); + assertThat(um.getUserAccount(tempUserId)).isEqualTo(accountName); } + @Test public void testUserSystemPackageWhitelist() throws Exception { String cmd = "cmd user report-system-user-package-whitelist-problems --critical-only"; final String result = runShellCommand(cmd); - if (!TextUtils.isEmpty(result)) { - fail("Command '" + cmd + " reported errors:\n" + result); - } + assertThat(result).isEmpty(); } private Bundle createBundle() { @@ -114,26 +140,141 @@ public class UserManagerServiceTest extends AndroidTestCase { } private void assertBundle(Bundle bundle) { - assertFalse(bundle.getBoolean("boolean_0")); - assertTrue(bundle.getBoolean("boolean_1")); - assertEquals(100, bundle.getInt("integer")); - assertEquals("", bundle.getString("empty")); - assertEquals("text", bundle.getString("string")); - assertEquals(Arrays.asList(STRING_ARRAY), Arrays.asList(bundle.getStringArray("string[]"))); + assertThat(bundle.getBoolean("boolean_0")).isFalse(); + assertThat(bundle.getBoolean("boolean_1")).isTrue(); + assertThat(bundle.getInt("integer")).isEqualTo(100); + assertThat(bundle.getString("empty")).isEqualTo(""); + assertThat(bundle.getString("string")).isEqualTo("text"); + assertThat(Arrays.asList(bundle.getStringArray("string[]"))) + .isEqualTo(Arrays.asList(STRING_ARRAY)); Parcelable[] bundle_array = bundle.getParcelableArray("bundle_array"); - assertEquals(2, bundle_array.length); + assertThat(bundle_array.length).isEqualTo(2); Bundle bundle1 = (Bundle) bundle_array[0]; - assertEquals("bundle_array_string", bundle1.getString("bundle_array_string")); - assertNotNull(bundle1.getBundle("bundle_array_bundle")); + assertThat(bundle1.getString("bundle_array_string")) + .isEqualTo("bundle_array_string"); + assertThat(bundle1.getBundle("bundle_array_bundle")).isNotNull(); Bundle bundle2 = (Bundle) bundle_array[1]; - assertEquals("bundle_array_string2", bundle2.getString("bundle_array_string2")); + assertThat(bundle2.getString("bundle_array_string2")) + .isEqualTo("bundle_array_string2"); Bundle childBundle = bundle.getBundle("bundle"); - assertEquals("bundle_string", childBundle.getString("bundle_string")); - assertEquals(1, childBundle.getInt("bundle_int")); + assertThat(childBundle.getString("bundle_string")) + .isEqualTo("bundle_string"); + assertThat(childBundle.getInt("bundle_int")).isEqualTo(1); + } + + @Test + public void assertHasUserRestriction() throws Exception { + int userId = ActivityManager.getCurrentUser(); + + mUserManagerService.setUserRestriction(DISALLOW_USER_SWITCH, true, userId); + assertThat(mUserManagerService.hasUserRestriction(DISALLOW_USER_SWITCH, userId)).isTrue(); + + mUserManagerService.setUserRestriction(DISALLOW_USER_SWITCH, false, userId); + assertThat(mUserManagerService.hasUserRestriction(DISALLOW_USER_SWITCH, userId)).isFalse(); + } + + @Test + public void assertIsUserSwitcherEnabledOnMultiUserSettings() throws Exception { + int userId = ActivityManager.getCurrentUser(); + resetUserSwitcherEnabled(); + + setUserSwitch(false); + assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isFalse(); + + setUserSwitch(true); + assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue(); + } + + @Test + public void assertIsUserSwitcherEnabledOnMaxSupportedUsers() throws Exception { + int userId = ActivityManager.getCurrentUser(); + setMaxSupportedUsers(1); + + assertThat(UserManager.supportsMultipleUsers()).isFalse(); + assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isFalse(); + + setMaxSupportedUsers(8); + + assertThat(UserManager.supportsMultipleUsers()).isTrue(); + assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue(); + } + + + @Test + public void assertIsUserSwitcherEnabledOnShowMultiuserUI() throws Exception { + int userId = ActivityManager.getCurrentUser(); + setShowMultiuserUI(false); + + assertThat(UserManager.supportsMultipleUsers()).isFalse(); + assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isFalse(); + + setShowMultiuserUI(true); + + assertThat(UserManager.supportsMultipleUsers()).isTrue(); + assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue(); + } + + @Test + public void assertIsUserSwitcherEnabledOnUserRestrictions() throws Exception { + int userId = ActivityManager.getCurrentUser(); + resetUserSwitcherEnabled(); + + mUserManagerService.setUserRestriction(DISALLOW_USER_SWITCH, true, userId); + assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isFalse(); + + mUserManagerService.setUserRestriction(DISALLOW_USER_SWITCH, false, userId); + assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue(); + } + + @Test + public void assertIsUserSwitcherEnabledOnDemoMode() throws Exception { + int userId = ActivityManager.getCurrentUser(); + resetUserSwitcherEnabled(); + + setDeviceDemoMode(true); + assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isFalse(); + + setDeviceDemoMode(false); + assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue(); + } + + private void resetUserSwitcherEnabled() throws Exception { + int userId = ActivityManager.getCurrentUser(); + setUserSwitch(true); + setShowMultiuserUI(true); + setDeviceDemoMode(false); + setMaxSupportedUsers(8); + mUserManagerService.setUserRestriction(DISALLOW_USER_SWITCH, false, userId); + } + + private void setUserSwitch(boolean enabled) { + android.provider.Settings.Global.putInt(mContext.getContentResolver(), + android.provider.Settings.Global.USER_SWITCHER_ENABLED, enabled ? 1 : 0); } + private void setDeviceDemoMode(boolean enabled) { + android.provider.Settings.Global.putInt(mContext.getContentResolver(), + android.provider.Settings.Global.DEVICE_DEMO_MODE, enabled ? 1 : 0); + } + + private static String runShellCommand(String cmd) throws Exception { return UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) .executeShellCommand(cmd); } + + private static String setSystemProperty(String name, String value) throws Exception { + final String oldValue = runShellCommand("getprop " + name); + assertThat(runShellCommand("setprop " + name + " " + value)) + .isEqualTo(""); + return oldValue; + } + + private static void setMaxSupportedUsers(int max) throws Exception { + setSystemProperty("fw.max_users", String.valueOf(max)); + } + + public static void setShowMultiuserUI(boolean show) throws Exception { + setSystemProperty("fw.show_multiuserui", String.valueOf(show)); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java index 911fb6a87e96..08c2c6e6f26e 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java @@ -1301,6 +1301,21 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { } @Test + public void testA11yCrossUserEventNotSent() throws Exception { + final Notification n = new Builder(getContext(), "test") + .setSmallIcon(android.R.drawable.sym_def_app_icon).build(); + int userId = mUser.getIdentifier() + 1; + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 0, mTag, mUid, + mPid, n, UserHandle.of(userId), null, System.currentTimeMillis()); + NotificationRecord r = new NotificationRecord(getContext(), sbn, + new NotificationChannel("test", "test", IMPORTANCE_HIGH)); + + mService.buzzBeepBlinkLocked(r); + + verify(mAccessibilityService, never()).sendAccessibilityEvent(any(), anyInt()); + } + + @Test public void testLightsScreenOn() { mService.mScreenOn = true; NotificationRecord r = getLightsNotification(); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 8171540c3acc..d78ca1bd7b67 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -4239,6 +4239,59 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testSubstituteAppName_hasPermission() throws RemoteException { + String subName = "Substitute Name"; + when(mPackageManager.checkPermission( + eq(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME), any(), anyInt())) + .thenReturn(PERMISSION_GRANTED); + Bundle extras = new Bundle(); + extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, subName); + Notification.Builder nb = new Notification.Builder(mContext, + mTestNotificationChannel.getId()) + .addExtras(extras); + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, + "testSubstituteAppNamePermission", mUid, 0, + nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); + waitForIdle(); + NotificationRecord posted = mService.findNotificationLocked( + PKG, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId()); + + assertTrue(posted.getNotification().extras + .containsKey(Notification.EXTRA_SUBSTITUTE_APP_NAME)); + assertEquals(posted.getNotification().extras + .getString(Notification.EXTRA_SUBSTITUTE_APP_NAME), subName); + } + + @Test + public void testSubstituteAppName_noPermission() throws RemoteException { + when(mPackageManager.checkPermission( + eq(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME), any(), anyInt())) + .thenReturn(PERMISSION_DENIED); + Bundle extras = new Bundle(); + extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, "Substitute Name"); + Notification.Builder nb = new Notification.Builder(mContext, + mTestNotificationChannel.getId()) + .addExtras(extras); + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, + "testSubstituteAppNamePermission", mUid, 0, + nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); + waitForIdle(); + NotificationRecord posted = mService.findNotificationLocked( + PKG, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId()); + + assertFalse(posted.getNotification().extras + .containsKey(Notification.EXTRA_SUBSTITUTE_APP_NAME)); + } + + @Test public void testGetNotificationCountLocked() { String sampleTagToExclude = null; int sampleIdToExclude = 0; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java index c12f0a965146..d72cfc70fc02 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java @@ -17,7 +17,9 @@ package com.android.server.notification; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.contains; import static org.mockito.ArgumentMatchers.eq; @@ -30,6 +32,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Notification; +import android.app.NotificationChannel; import android.app.Person; import android.content.ContentProvider; import android.content.ContentResolver; @@ -39,8 +42,11 @@ import android.net.Uri; import android.os.Bundle; import android.os.UserManager; import android.provider.ContactsContract; +import android.service.notification.StatusBarNotification; import android.test.suitebuilder.annotation.SmallTest; import android.text.SpannableString; +import android.util.ArraySet; +import android.util.LruCache; import androidx.test.runner.AndroidJUnit4; @@ -323,6 +329,69 @@ public class ValidateNotificationPeopleTest extends UiServiceTestCase { isNull()); // sort order } + @Test + public void testValidatePeople_needsLookupWhenNoCache() { + final Context mockContext = mock(Context.class); + final ContentResolver mockContentResolver = mock(ContentResolver.class); + when(mockContext.getContentResolver()).thenReturn(mockContentResolver); + final NotificationUsageStats mockNotificationUsageStats = + mock(NotificationUsageStats.class); + + // Create validator with empty cache + ValidateNotificationPeople vnp = new ValidateNotificationPeople(); + LruCache cache = new LruCache<String, ValidateNotificationPeople.LookupResult>(5); + vnp.initForTests(mockContext, mockNotificationUsageStats, cache); + + NotificationRecord record = getNotificationRecord(); + String[] callNumber = new String[]{"tel:12345678910"}; + setNotificationPeople(record, callNumber); + + // Returned ranking reconsideration not null indicates that there is a lookup to be done + RankingReconsideration rr = vnp.validatePeople(mockContext, record); + assertNotNull(rr); + } + + @Test + public void testValidatePeople_noLookupWhenCached_andPopulatesContactInfo() { + final Context mockContext = mock(Context.class); + final ContentResolver mockContentResolver = mock(ContentResolver.class); + when(mockContext.getContentResolver()).thenReturn(mockContentResolver); + when(mockContext.getUserId()).thenReturn(1); + final NotificationUsageStats mockNotificationUsageStats = + mock(NotificationUsageStats.class); + + // Information to be passed in & returned from the lookup result + String lookup = "lookup:contactinfohere"; + String lookupTel = "16175551234"; + float affinity = 0.7f; + + // Create a fake LookupResult for the data we'll pass in + LruCache cache = new LruCache<String, ValidateNotificationPeople.LookupResult>(5); + ValidateNotificationPeople.LookupResult lr = + mock(ValidateNotificationPeople.LookupResult.class); + when(lr.getAffinity()).thenReturn(affinity); + when(lr.getPhoneNumbers()).thenReturn(new ArraySet<>(new String[]{lookupTel})); + when(lr.isExpired()).thenReturn(false); + cache.put(ValidateNotificationPeople.getCacheKey(1, lookup), lr); + + // Create validator with the established cache + ValidateNotificationPeople vnp = new ValidateNotificationPeople(); + vnp.initForTests(mockContext, mockNotificationUsageStats, cache); + + NotificationRecord record = getNotificationRecord(); + String[] peopleInfo = new String[]{lookup}; + setNotificationPeople(record, peopleInfo); + + // Returned ranking reconsideration null indicates that there is no pending work to be done + RankingReconsideration rr = vnp.validatePeople(mockContext, record); + assertNull(rr); + + // Confirm that the affinity & phone number made it into our record + assertEquals(affinity, record.getContactAffinity(), 1e-8); + assertNotNull(record.getPhoneNumbers()); + assertTrue(record.getPhoneNumbers().contains(lookupTel)); + } + // Creates a cursor that points to one item of Contacts data with the specified // columns. private Cursor makeMockCursor(int id, String lookupKey, int starred, int hasPhone) { @@ -365,4 +434,17 @@ public class ValidateNotificationPeopleTest extends UiServiceTestCase { String resultString = Arrays.toString(result); assertEquals(message + ": arrays differ", expectedString, resultString); } + + private NotificationRecord getNotificationRecord() { + StatusBarNotification sbn = mock(StatusBarNotification.class); + Notification notification = mock(Notification.class); + when(sbn.getNotification()).thenReturn(notification); + return new NotificationRecord(mContext, sbn, mock(NotificationChannel.class)); + } + + private void setNotificationPeople(NotificationRecord r, String[] people) { + Bundle extras = new Bundle(); + extras.putObject(Notification.EXTRA_PEOPLE_LIST, people); + r.getSbn().getNotification().extras = extras; + } } 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 8b350f4d0ac7..a8b864bab553 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -158,6 +158,7 @@ import androidx.test.filters.MediumTest; import com.android.internal.R; import com.android.server.wm.ActivityRecord.State; +import com.android.server.wm.utils.WmDisplayCutout; import org.junit.Assert; import org.junit.Before; @@ -551,7 +552,8 @@ public class ActivityRecordTests extends WindowTestsBase { final Rect insets = new Rect(); final DisplayInfo displayInfo = task.mDisplayContent.getDisplayInfo(); final DisplayPolicy policy = task.mDisplayContent.getDisplayPolicy(); - policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.displayCutout, insets); + policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.logicalWidth, + displayInfo.logicalHeight, WmDisplayCutout.NO_CUTOUT, insets); policy.convertNonDecorInsetsToStableInsets(insets, displayInfo.rotation); Task.intersectWithInsetsIfFits(stableRect, stableRect, insets); @@ -592,7 +594,8 @@ public class ActivityRecordTests extends WindowTestsBase { final Rect insets = new Rect(); final DisplayInfo displayInfo = rootTask.mDisplayContent.getDisplayInfo(); final DisplayPolicy policy = rootTask.mDisplayContent.getDisplayPolicy(); - policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.displayCutout, insets); + policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.logicalWidth, + displayInfo.logicalHeight, WmDisplayCutout.NO_CUTOUT, insets); policy.convertNonDecorInsetsToStableInsets(insets, displayInfo.rotation); Task.intersectWithInsetsIfFits(stableRect, stableRect, insets); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java index 75ecfd870eb2..d5e336b1cf2f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java @@ -228,7 +228,7 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase { mAtm.getTaskChangeNotificationController(); spyOn(taskChangeNotifier); - mAtm.setResumedActivityUncheckLocked(fullScreenActivityA, "resumeA"); + mAtm.setLastResumedActivityUncheckLocked(fullScreenActivityA, "resumeA"); verify(taskChangeNotifier).notifyTaskFocusChanged(eq(taskA.mTaskId) /* taskId */, eq(true) /* focused */); reset(taskChangeNotifier); @@ -237,7 +237,7 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase { .build(); final Task taskB = fullScreenActivityB.getTask(); - mAtm.setResumedActivityUncheckLocked(fullScreenActivityB, "resumeB"); + mAtm.setLastResumedActivityUncheckLocked(fullScreenActivityB, "resumeB"); verify(taskChangeNotifier).notifyTaskFocusChanged(eq(taskA.mTaskId) /* taskId */, eq(false) /* focused */); verify(taskChangeNotifier).notifyTaskFocusChanged(eq(taskB.mTaskId) /* taskId */, @@ -295,6 +295,7 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase { activity1.moveFocusableActivityToTop("test"); assertEquals(activity1.getUid(), pendingTopUid[0]); verify(mAtm).updateOomAdj(); + verify(mAtm).setLastResumedActivityUncheckLocked(any(), eq("test")); } /** 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 c2ca0a227f26..1cd0b198ff5a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java @@ -80,10 +80,12 @@ public class BackNavigationControllerTests extends WindowTestsBase { IOnBackInvokedCallback callback = withSystemCallback(task); BackNavigationInfo backNavigationInfo = - mBackNavigationController.startBackNavigation(true, null); + mBackNavigationController.startBackNavigation(true, null, null); assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull(); - assertThat(backNavigationInfo.getDepartingAnimationTarget()).isNotNull(); - assertThat(backNavigationInfo.getTaskWindowConfiguration()).isNotNull(); + if (!BackNavigationController.USE_TRANSITION) { + assertThat(backNavigationInfo.getDepartingAnimationTarget()).isNotNull(); + assertThat(backNavigationInfo.getTaskWindowConfiguration()).isNotNull(); + } assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback); assertThat(typeToString(backNavigationInfo.getType())) .isEqualTo(typeToString(BackNavigationInfo.TYPE_RETURN_TO_HOME)); @@ -233,7 +235,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { @Nullable private BackNavigationInfo startBackNavigation() { - return mBackNavigationController.startBackNavigation(true, null); + return mBackNavigationController.startBackNavigation(true, null, null); } @NonNull 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 600881ea955c..fd97d910e127 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -1251,7 +1251,15 @@ public class DisplayContentTests extends WindowTestsBase { public void testComputeImeControlTarget() throws Exception { final DisplayContent dc = createNewDisplay(); dc.setRemoteInsetsController(createDisplayWindowInsetsController()); - dc.setImeInputTarget(createWindow(null, TYPE_BASE_APPLICATION, "app")); + dc.mCurrentFocus = createWindow(null, TYPE_BASE_APPLICATION, "app"); + + // Expect returning null IME control target when the focus window has not yet been the + // IME input target (e.g. IME is restarting) in fullscreen windowing mode. + dc.setImeInputTarget(null); + assertFalse(dc.mCurrentFocus.inMultiWindowMode()); + assertNull(dc.computeImeControlTarget()); + + dc.setImeInputTarget(dc.mCurrentFocus); dc.setImeLayeringTarget(dc.getImeInputTarget().getWindowState()); assertEquals(dc.getImeInputTarget().getWindowState(), dc.computeImeControlTarget()); } @@ -1904,10 +1912,10 @@ public class DisplayContentTests extends WindowTestsBase { testPlayer.start(); assertNotEquals(origRot, dc.getConfiguration().windowConfiguration.getRotation()); assertNotNull(testPlayer.mLastReady); - assertEquals(dc, DisplayRotation.getDisplayFromTransition(testPlayer.mLastTransit)); WindowContainerToken dcToken = dc.mRemoteToken.toWindowContainerToken(); assertNotEquals(testPlayer.mLastReady.getChange(dcToken).getEndRotation(), testPlayer.mLastReady.getChange(dcToken).getStartRotation()); + assertTrue(testPlayer.mLastTransit.applyDisplayChangeIfNeeded()); testPlayer.finish(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java index f41fee789bf2..a001eda2f86e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java @@ -25,10 +25,13 @@ import static org.hamcrest.Matchers.equalTo; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; +import android.util.Pair; import android.view.DisplayInfo; import androidx.test.filters.SmallTest; +import com.android.server.wm.utils.WmDisplayCutout; + import org.junit.Rule; import org.junit.Test; import org.junit.rules.ErrorCollector; @@ -46,7 +49,8 @@ public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase { @Test public void portrait() { - final DisplayInfo di = displayInfoForRotation(ROTATION_0, false /* withCutout */); + final Pair<DisplayInfo, WmDisplayCutout> di = + displayInfoForRotation(ROTATION_0, false /* withCutout */); verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT); verifyNonDecorInsets(di, 0, 0, 0, NAV_BAR_HEIGHT); @@ -55,7 +59,8 @@ public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase { @Test public void portrait_withCutout() { - final DisplayInfo di = displayInfoForRotation(ROTATION_0, true /* withCutout */); + final Pair<DisplayInfo, WmDisplayCutout> di = + displayInfoForRotation(ROTATION_0, true /* withCutout */); verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT); verifyNonDecorInsets(di, 0, DISPLAY_CUTOUT_HEIGHT, 0, NAV_BAR_HEIGHT); @@ -64,7 +69,8 @@ public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase { @Test public void landscape() { - final DisplayInfo di = displayInfoForRotation(ROTATION_90, false /* withCutout */); + final Pair<DisplayInfo, WmDisplayCutout> di = + displayInfoForRotation(ROTATION_90, false /* withCutout */); if (mDisplayPolicy.navigationBarCanMove()) { verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); @@ -79,7 +85,8 @@ public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase { @Test public void landscape_withCutout() { - final DisplayInfo di = displayInfoForRotation(ROTATION_90, true /* withCutout */); + final Pair<DisplayInfo, WmDisplayCutout> di = + displayInfoForRotation(ROTATION_90, true /* withCutout */); if (mDisplayPolicy.navigationBarCanMove()) { verifyStableInsets(di, DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); @@ -94,7 +101,8 @@ public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase { @Test public void seascape() { - final DisplayInfo di = displayInfoForRotation(ROTATION_270, false /* withCutout */); + final Pair<DisplayInfo, WmDisplayCutout> di = + displayInfoForRotation(ROTATION_270, false /* withCutout */); if (mDisplayPolicy.navigationBarCanMove()) { verifyStableInsets(di, NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, 0, 0); @@ -109,7 +117,8 @@ public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase { @Test public void seascape_withCutout() { - final DisplayInfo di = displayInfoForRotation(ROTATION_270, true /* withCutout */); + final Pair<DisplayInfo, WmDisplayCutout> di = + displayInfoForRotation(ROTATION_270, true /* withCutout */); if (mDisplayPolicy.navigationBarCanMove()) { verifyStableInsets(di, NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, DISPLAY_CUTOUT_HEIGHT, 0); @@ -124,7 +133,8 @@ public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase { @Test public void upsideDown() { - final DisplayInfo di = displayInfoForRotation(ROTATION_180, false /* withCutout */); + final Pair<DisplayInfo, WmDisplayCutout> di = + displayInfoForRotation(ROTATION_180, false /* withCutout */); verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT); verifyNonDecorInsets(di, 0, 0, 0, NAV_BAR_HEIGHT); @@ -133,28 +143,34 @@ public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase { @Test public void upsideDown_withCutout() { - final DisplayInfo di = displayInfoForRotation(ROTATION_180, true /* withCutout */); + final Pair<DisplayInfo, WmDisplayCutout> di = + displayInfoForRotation(ROTATION_180, true /* withCutout */); verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT + DISPLAY_CUTOUT_HEIGHT); verifyNonDecorInsets(di, 0, 0, 0, NAV_BAR_HEIGHT + DISPLAY_CUTOUT_HEIGHT); verifyConsistency(di); } - private void verifyStableInsets(DisplayInfo di, int left, int top, int right, int bottom) { - mErrorCollector.checkThat("stableInsets", getStableInsetsLw(di), equalTo(new Rect( - left, top, right, bottom))); + private void verifyStableInsets(Pair<DisplayInfo, WmDisplayCutout> diPair, int left, int top, + int right, int bottom) { + mErrorCollector.checkThat("stableInsets", getStableInsetsLw(diPair.first, diPair.second), + equalTo(new Rect(left, top, right, bottom))); } - private void verifyNonDecorInsets(DisplayInfo di, int left, int top, int right, int bottom) { - mErrorCollector.checkThat("nonDecorInsets", getNonDecorInsetsLw(di), equalTo(new Rect( + private void verifyNonDecorInsets(Pair<DisplayInfo, WmDisplayCutout> diPair, int left, int top, + int right, int bottom) { + mErrorCollector.checkThat("nonDecorInsets", + getNonDecorInsetsLw(diPair.first, diPair.second), equalTo(new Rect( left, top, right, bottom))); } - private void verifyConsistency(DisplayInfo di) { - verifyConsistency("configDisplay", di, getStableInsetsLw(di), - getConfigDisplayWidth(di), getConfigDisplayHeight(di)); - verifyConsistency("nonDecorDisplay", di, getNonDecorInsetsLw(di), - getNonDecorDisplayWidth(di), getNonDecorDisplayHeight(di)); + private void verifyConsistency(Pair<DisplayInfo, WmDisplayCutout> diPair) { + final DisplayInfo di = diPair.first; + final WmDisplayCutout cutout = diPair.second; + verifyConsistency("configDisplay", di, getStableInsetsLw(di, cutout), + getConfigDisplayWidth(di, cutout), getConfigDisplayHeight(di, cutout)); + verifyConsistency("nonDecorDisplay", di, getNonDecorInsetsLw(di, cutout), + getNonDecorDisplayWidth(di, cutout), getNonDecorDisplayHeight(di, cutout)); } private void verifyConsistency(String what, DisplayInfo di, Rect insets, int width, @@ -165,39 +181,42 @@ public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase { equalTo(di.logicalHeight - insets.top - insets.bottom)); } - private Rect getStableInsetsLw(DisplayInfo di) { + private Rect getStableInsetsLw(DisplayInfo di, WmDisplayCutout cutout) { Rect result = new Rect(); - mDisplayPolicy.getStableInsetsLw(di.rotation, di.displayCutout, result); + mDisplayPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight, + cutout, result); return result; } - private Rect getNonDecorInsetsLw(DisplayInfo di) { + private Rect getNonDecorInsetsLw(DisplayInfo di, WmDisplayCutout cutout) { Rect result = new Rect(); - mDisplayPolicy.getNonDecorInsetsLw(di.rotation, di.displayCutout, result); + mDisplayPolicy.getNonDecorInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight, + cutout, result); return result; } - private int getNonDecorDisplayWidth(DisplayInfo di) { - return mDisplayPolicy.getNonDecorDisplayWidth(di.logicalWidth, di.logicalHeight, - di.rotation, 0 /* ui */, di.displayCutout); + private int getNonDecorDisplayWidth(DisplayInfo di, WmDisplayCutout cutout) { + return mDisplayPolicy.getNonDecorDisplayFrame(di.logicalWidth, di.logicalHeight, + di.rotation, cutout).width(); } - private int getNonDecorDisplayHeight(DisplayInfo di) { - return mDisplayPolicy.getNonDecorDisplayHeight(di.logicalHeight, di.rotation, - di.displayCutout); + private int getNonDecorDisplayHeight(DisplayInfo di, WmDisplayCutout cutout) { + return mDisplayPolicy.getNonDecorDisplayFrame(di.logicalWidth, di.logicalHeight, + di.rotation, cutout).height(); } - private int getConfigDisplayWidth(DisplayInfo di) { - return mDisplayPolicy.getConfigDisplayWidth(di.logicalWidth, di.logicalHeight, - di.rotation, 0 /* ui */, di.displayCutout); + private int getConfigDisplayWidth(DisplayInfo di, WmDisplayCutout cutout) { + return mDisplayPolicy.getConfigDisplaySize(di.logicalWidth, di.logicalHeight, + di.rotation, cutout).x; } - private int getConfigDisplayHeight(DisplayInfo di) { - return mDisplayPolicy.getConfigDisplayHeight(di.logicalWidth, di.logicalHeight, - di.rotation, 0 /* ui */, di.displayCutout); + private int getConfigDisplayHeight(DisplayInfo di, WmDisplayCutout cutout) { + return mDisplayPolicy.getConfigDisplaySize(di.logicalWidth, di.logicalHeight, + di.rotation, cutout).y; } - private static DisplayInfo displayInfoForRotation(int rotation, boolean withDisplayCutout) { - return displayInfoAndCutoutForRotation(rotation, withDisplayCutout, false).first; + private static Pair<DisplayInfo, WmDisplayCutout> displayInfoForRotation(int rotation, + boolean withDisplayCutout) { + return displayInfoAndCutoutForRotation(rotation, withDisplayCutout, false); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java index e502f2fbd173..d400a4c9daca 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java @@ -56,6 +56,7 @@ public class LetterboxTest { private boolean mHasWallpaperBackground = false; private int mBlurRadius = 0; private float mDarkScrimAlpha = 0.5f; + private SurfaceControl mParentSurface = mock(SurfaceControl.class); @Before public void setUp() throws Exception { @@ -63,7 +64,8 @@ public class LetterboxTest { mLetterbox = new Letterbox(mSurfaces, StubTransaction::new, () -> mAreCornersRounded, () -> Color.valueOf(mColor), () -> mHasWallpaperBackground, () -> mBlurRadius, () -> mDarkScrimAlpha, - /* doubleTapCallbackX= */ x -> {}, /* doubleTapCallbackY= */ y -> {}); + /* doubleTapCallbackX= */ x -> {}, /* doubleTapCallbackY= */ y -> {}, + () -> mParentSurface); mTransaction = spy(StubTransaction.class); } @@ -205,6 +207,22 @@ public class LetterboxTest { } @Test + public void testNeedsApplySurfaceChanges_setParentSurface() { + mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000)); + mLetterbox.applySurfaceChanges(mTransaction); + + verify(mTransaction).reparent(mSurfaces.top, mParentSurface); + assertFalse(mLetterbox.needsApplySurfaceChanges()); + + mParentSurface = mock(SurfaceControl.class); + + assertTrue(mLetterbox.needsApplySurfaceChanges()); + + mLetterbox.applySurfaceChanges(mTransaction); + verify(mTransaction).reparent(mSurfaces.top, mParentSurface); + } + + @Test public void testApplySurfaceChanges_cornersNotRounded_surfaceFullWindowSurfaceNotCreated() { mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000)); mLetterbox.applySurfaceChanges(mTransaction); diff --git a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java index 420ea8e63562..d3aa073c84d8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java @@ -16,13 +16,18 @@ package com.android.server.wm; +import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.server.wm.BLASTSyncEngine.METHOD_BLAST; +import static com.android.server.wm.BLASTSyncEngine.METHOD_NONE; import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; import static com.android.server.wm.WindowContainer.POSITION_TOP; import static com.android.server.wm.WindowContainer.SYNC_STATE_NONE; +import static com.android.server.wm.WindowState.BLAST_TIMEOUT_DURATION; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -67,7 +72,7 @@ public class SyncEngineTests extends WindowTestsBase { BLASTSyncEngine.TransactionReadyListener listener = mock( BLASTSyncEngine.TransactionReadyListener.class); - int id = bse.startSyncSet(listener); + int id = startSyncSet(bse, listener); bse.addToSyncSet(id, mockWC); // Make sure a traversal is requested verify(mWm.mWindowPlacerLocked, times(1)).requestTraversal(); @@ -95,7 +100,7 @@ public class SyncEngineTests extends WindowTestsBase { BLASTSyncEngine.TransactionReadyListener listener = mock( BLASTSyncEngine.TransactionReadyListener.class); - int id = bse.startSyncSet(listener); + int id = startSyncSet(bse, listener); bse.addToSyncSet(id, mockWC); bse.setReady(id); // Make sure traversals requested (one for add and another for setReady) @@ -119,7 +124,7 @@ public class SyncEngineTests extends WindowTestsBase { BLASTSyncEngine.TransactionReadyListener listener = mock( BLASTSyncEngine.TransactionReadyListener.class); - int id = bse.startSyncSet(listener); + int id = startSyncSet(bse, listener); bse.addToSyncSet(id, mockWC); bse.setReady(id); // Make sure traversals requested (one for add and another for setReady) @@ -147,7 +152,7 @@ public class SyncEngineTests extends WindowTestsBase { BLASTSyncEngine.TransactionReadyListener listener = mock( BLASTSyncEngine.TransactionReadyListener.class); - int id = bse.startSyncSet(listener); + int id = startSyncSet(bse, listener); bse.addToSyncSet(id, parentWC); bse.setReady(id); bse.onSurfacePlacement(); @@ -180,7 +185,7 @@ public class SyncEngineTests extends WindowTestsBase { BLASTSyncEngine.TransactionReadyListener listener = mock( BLASTSyncEngine.TransactionReadyListener.class); - int id = bse.startSyncSet(listener); + int id = startSyncSet(bse, listener); bse.addToSyncSet(id, parentWC); bse.setReady(id); bse.onSurfacePlacement(); @@ -211,7 +216,7 @@ public class SyncEngineTests extends WindowTestsBase { BLASTSyncEngine.TransactionReadyListener listener = mock( BLASTSyncEngine.TransactionReadyListener.class); - int id = bse.startSyncSet(listener); + int id = startSyncSet(bse, listener); bse.addToSyncSet(id, parentWC); bse.setReady(id); bse.onSurfacePlacement(); @@ -243,7 +248,7 @@ public class SyncEngineTests extends WindowTestsBase { BLASTSyncEngine.TransactionReadyListener listener = mock( BLASTSyncEngine.TransactionReadyListener.class); - int id = bse.startSyncSet(listener); + int id = startSyncSet(bse, listener); bse.addToSyncSet(id, parentWC); bse.setReady(id); bse.onSurfacePlacement(); @@ -278,7 +283,7 @@ public class SyncEngineTests extends WindowTestsBase { BLASTSyncEngine.TransactionReadyListener listener = mock( BLASTSyncEngine.TransactionReadyListener.class); - int id = bse.startSyncSet(listener); + int id = startSyncSet(bse, listener); bse.addToSyncSet(id, parentWC); bse.setReady(id); bse.onSurfacePlacement(); @@ -317,7 +322,7 @@ public class SyncEngineTests extends WindowTestsBase { BLASTSyncEngine.TransactionReadyListener listener = mock( BLASTSyncEngine.TransactionReadyListener.class); - int id = bse.startSyncSet(listener); + int id = startSyncSet(bse, listener); bse.addToSyncSet(id, parentWC); final BLASTSyncEngine.SyncGroup syncGroup = parentWC.mSyncGroup; bse.setReady(id); @@ -350,6 +355,33 @@ public class SyncEngineTests extends WindowTestsBase { assertEquals(SYNC_STATE_NONE, botChildWC.mSyncState); } + @Test + public void testNonBlastMethod() { + mAppWindow = createWindow(null, TYPE_BASE_APPLICATION, "mAppWindow"); + + final BLASTSyncEngine bse = createTestBLASTSyncEngine(); + + BLASTSyncEngine.TransactionReadyListener listener = mock( + BLASTSyncEngine.TransactionReadyListener.class); + + int id = startSyncSet(bse, listener, METHOD_NONE); + bse.addToSyncSet(id, mAppWindow.mToken); + mAppWindow.prepareSync(); + assertFalse(mAppWindow.shouldSyncWithBuffers()); + + mAppWindow.removeImmediately(); + } + + static int startSyncSet(BLASTSyncEngine engine, + BLASTSyncEngine.TransactionReadyListener listener) { + return startSyncSet(engine, listener, METHOD_BLAST); + } + + static int startSyncSet(BLASTSyncEngine engine, + BLASTSyncEngine.TransactionReadyListener listener, int method) { + return engine.startSyncSet(listener, BLAST_TIMEOUT_DURATION, "", method); + } + static class TestWindowContainer extends WindowContainer { final boolean mWaiter; boolean mVisibleRequested = true; diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index 8332cb4209ef..9274eb3f1490 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -25,6 +25,7 @@ import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED; @@ -46,7 +47,6 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -65,6 +65,7 @@ import android.window.TaskFragmentCreationParams; import android.window.TaskFragmentInfo; import android.window.TaskFragmentOrganizer; import android.window.TaskFragmentOrganizerToken; +import android.window.TaskFragmentTransaction; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import android.window.WindowContainerTransactionCallback; @@ -90,6 +91,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { private TaskFragmentOrganizerController mController; private WindowOrganizerController mWindowOrganizerController; + private TransitionController mTransitionController; private TaskFragmentOrganizer mOrganizer; private TaskFragmentOrganizerToken mOrganizerToken; private ITaskFragmentOrganizer mIOrganizer; @@ -107,9 +109,10 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { private Task mTask; @Before - public void setup() { + public void setup() throws RemoteException { MockitoAnnotations.initMocks(this); mWindowOrganizerController = mAtm.mWindowOrganizerController; + mTransitionController = mWindowOrganizerController.mTransitionController; mController = mWindowOrganizerController.mTaskFragmentOrganizerController; mOrganizer = new TaskFragmentOrganizer(Runnable::run); mOrganizerToken = mOrganizer.getOrganizerToken(); @@ -128,11 +131,16 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { spyOn(mController); spyOn(mOrganizer); spyOn(mTaskFragment); + spyOn(mWindowOrganizerController); + spyOn(mTransitionController); doReturn(mIOrganizer).when(mTaskFragment).getTaskFragmentOrganizer(); doReturn(mTaskFragmentInfo).when(mTaskFragment).getTaskFragmentInfo(); doReturn(new SurfaceControl()).when(mTaskFragment).getSurfaceControl(); doReturn(mFragmentToken).when(mTaskFragment).getFragmentToken(); doReturn(new Configuration()).when(mTaskFragmentInfo).getConfiguration(); + + // To prevent it from calling the real server. + doNothing().when(mOrganizer).onTransactionHandled(any(), any()); } @Test @@ -158,7 +166,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mController.dispatchPendingEvents(); - verify(mOrganizer, never()).onTaskFragmentAppeared(any()); + verify(mOrganizer, never()).onTaskFragmentAppeared(any(), any()); // Send callback when the TaskFragment is attached. setupMockParent(mTaskFragment, mTask); @@ -166,7 +174,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mController.dispatchPendingEvents(); - verify(mOrganizer).onTaskFragmentAppeared(any()); + verify(mOrganizer).onTaskFragmentAppeared(any(), any()); } @Test @@ -179,13 +187,13 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mTaskFragment); mController.dispatchPendingEvents(); - verify(mOrganizer, never()).onTaskFragmentInfoChanged(any()); + verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any()); // Call onTaskFragmentAppeared first. mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mController.dispatchPendingEvents(); - verify(mOrganizer).onTaskFragmentAppeared(any()); + verify(mOrganizer).onTaskFragmentAppeared(any(), any()); // No callback if the info is not changed. doReturn(true).when(mTaskFragmentInfo).equalsForTaskFragmentOrganizer(any()); @@ -195,7 +203,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mTaskFragment); mController.dispatchPendingEvents(); - verify(mOrganizer, never()).onTaskFragmentInfoChanged(any()); + verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any()); // Trigger callback if the info is changed. doReturn(false).when(mTaskFragmentInfo).equalsForTaskFragmentOrganizer(any()); @@ -204,7 +212,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mTaskFragment); mController.dispatchPendingEvents(); - verify(mOrganizer).onTaskFragmentInfoChanged(eq(mTaskFragmentInfo)); + verify(mOrganizer).onTaskFragmentInfoChanged(any(), eq(mTaskFragmentInfo)); } @Test @@ -215,7 +223,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mController.onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mController.dispatchPendingEvents(); - verify(mOrganizer).onTaskFragmentVanished(any()); + verify(mOrganizer).onTaskFragmentVanished(any(), any()); } @Test @@ -228,10 +236,10 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mController.onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mController.dispatchPendingEvents(); - verify(mOrganizer, never()).onTaskFragmentAppeared(any()); - verify(mOrganizer, never()).onTaskFragmentInfoChanged(any()); - verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(anyInt(), any()); - verify(mOrganizer).onTaskFragmentVanished(eq(mTaskFragmentInfo)); + verify(mOrganizer, never()).onTaskFragmentAppeared(any(), any()); + verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any()); + verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(any(), anyInt(), any()); + verify(mOrganizer).onTaskFragmentVanished(any(), eq(mTaskFragmentInfo)); // Not trigger onTaskFragmentInfoChanged. // Call onTaskFragmentAppeared before calling onTaskFragmentInfoChanged. @@ -244,10 +252,10 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mController.onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mController.dispatchPendingEvents(); - verify(mOrganizer, never()).onTaskFragmentAppeared(any()); - verify(mOrganizer, never()).onTaskFragmentInfoChanged(any()); - verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(anyInt(), any()); - verify(mOrganizer).onTaskFragmentVanished(eq(mTaskFragmentInfo)); + verify(mOrganizer, never()).onTaskFragmentAppeared(any(), any()); + verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any()); + verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(any(), anyInt(), any()); + verify(mOrganizer).onTaskFragmentVanished(any(), eq(mTaskFragmentInfo)); } @Test @@ -260,7 +268,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mController.dispatchPendingEvents(); - verify(mOrganizer).onTaskFragmentParentInfoChanged(eq(mTask.mTaskId), any()); + verify(mOrganizer).onTaskFragmentParentInfoChanged(any(), eq(mTask.mTaskId), any()); // No extra callback if the info is not changed. clearInvocations(mOrganizer); @@ -269,7 +277,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mController.dispatchPendingEvents(); - verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(anyInt(), any()); + verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(any(), anyInt(), any()); // Trigger callback if the size is changed. mTask.getConfiguration().smallestScreenWidthDp = 100; @@ -277,7 +285,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mController.dispatchPendingEvents(); - verify(mOrganizer).onTaskFragmentParentInfoChanged(eq(mTask.mTaskId), any()); + verify(mOrganizer).onTaskFragmentParentInfoChanged(any(), eq(mTask.mTaskId), any()); // Trigger callback if the windowing mode is changed. clearInvocations(mOrganizer); @@ -286,7 +294,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mController.dispatchPendingEvents(); - verify(mOrganizer).onTaskFragmentParentInfoChanged(eq(mTask.mTaskId), any()); + verify(mOrganizer).onTaskFragmentParentInfoChanged(any(), eq(mTask.mTaskId), any()); } @Test @@ -298,7 +306,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mErrorToken, null /* taskFragment */, -1 /* opType */, exception); mController.dispatchPendingEvents(); - verify(mOrganizer).onTaskFragmentError(eq(mErrorToken), eq(null), eq(-1), + verify(mOrganizer).onTaskFragmentError(any(), eq(mErrorToken), eq(null), eq(-1), eq(exception)); } @@ -318,14 +326,14 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mController.onActivityReparentedToTask(activity); mController.dispatchPendingEvents(); - verify(mOrganizer, never()).onActivityReparentedToTask(anyInt(), any(), any()); + verify(mOrganizer, never()).onActivityReparentedToTask(any(), anyInt(), any(), any()); // Notify organizer if it was embedded before entered Pip. activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer; mController.onActivityReparentedToTask(activity); mController.dispatchPendingEvents(); - verify(mOrganizer).onActivityReparentedToTask(eq(task.mTaskId), eq(activity.intent), + verify(mOrganizer).onActivityReparentedToTask(any(), eq(task.mTaskId), eq(activity.intent), eq(activity.token)); // Notify organizer if there is any embedded in the Task. @@ -341,7 +349,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mController.dispatchPendingEvents(); verify(mOrganizer, times(2)) - .onActivityReparentedToTask(eq(task.mTaskId), eq(activity.intent), + .onActivityReparentedToTask(any(), eq(task.mTaskId), eq(activity.intent), eq(activity.token)); } @@ -371,7 +379,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mController.dispatchPendingEvents(); // Allow organizer to reparent activity in other process using the temporary token. - verify(mOrganizer).onActivityReparentedToTask(eq(task.mTaskId), eq(activity.intent), + verify(mOrganizer).onActivityReparentedToTask(any(), eq(task.mTaskId), eq(activity.intent), token.capture()); final IBinder temporaryToken = token.getValue(); assertNotEquals(activity.token, temporaryToken); @@ -801,7 +809,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mController.dispatchPendingEvents(); // Verifies that event was not sent - verify(mOrganizer, never()).onTaskFragmentInfoChanged(any()); + verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any()); } @Test @@ -827,7 +835,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mController.dispatchPendingEvents(); // Verifies that event was not sent - verify(mOrganizer, never()).onTaskFragmentInfoChanged(any()); + verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any()); // Mock the task becomes visible, and activity resumed doReturn(true).when(task).shouldBeVisible(any()); @@ -835,7 +843,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // Verifies that event is sent. mController.dispatchPendingEvents(); - verify(mOrganizer).onTaskFragmentInfoChanged(any()); + verify(mOrganizer).onTaskFragmentInfoChanged(any(), any()); } /** @@ -866,10 +874,10 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { assertFalse(parentTask.shouldBeVisible(null)); // Verify the info changed callback still occurred despite the task being invisible - reset(mOrganizer); + clearInvocations(mOrganizer); mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment); mController.dispatchPendingEvents(); - verify(mOrganizer).onTaskFragmentInfoChanged(any()); + verify(mOrganizer).onTaskFragmentInfoChanged(any(), any()); } /** @@ -887,8 +895,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { .createActivityCount(1) .build(); final ActivityRecord embeddedActivity = taskFragment.getTopNonFinishingActivity(); - // Add another activity in the Task so that it always contains a non-finishing activitiy. - final ActivityRecord nonEmbeddedActivity = createActivityRecord(task); + // Add another activity in the Task so that it always contains a non-finishing activity. + createActivityRecord(task); assertTrue(task.shouldBeVisible(null)); // Dispatch pending info changed event from creating the activity @@ -896,21 +904,21 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { taskFragment.mTaskFragmentAppearedSent = true; mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment); mController.dispatchPendingEvents(); - verify(mOrganizer).onTaskFragmentInfoChanged(any()); + verify(mOrganizer).onTaskFragmentInfoChanged(any(), any()); // Verify the info changed callback is not called when the task is invisible - reset(mOrganizer); + clearInvocations(mOrganizer); doReturn(false).when(task).shouldBeVisible(any()); mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment); mController.dispatchPendingEvents(); - verify(mOrganizer, never()).onTaskFragmentInfoChanged(any()); + verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any()); // Finish the embedded activity, and verify the info changed callback is called because the // TaskFragment is becoming empty. embeddedActivity.finishing = true; mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment); mController.dispatchPendingEvents(); - verify(mOrganizer).onTaskFragmentInfoChanged(any()); + verify(mOrganizer).onTaskFragmentInfoChanged(any(), any()); } /** @@ -1020,7 +1028,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // The pending event will be dispatched on the handler (from requestTraversal). waitHandlerIdle(mWm.mAnimationHandler); - verify(mOrganizer).onTaskFragmentError(eq(mErrorToken), any(), + verify(mOrganizer).onTaskFragmentError(any(), eq(mErrorToken), any(), eq(HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT), any(SecurityException.class)); } @@ -1059,7 +1067,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // The pending event will be dispatched on the handler (from requestTraversal). waitHandlerIdle(mWm.mAnimationHandler); - verify(mOrganizer).onTaskFragmentError(eq(mErrorToken), any(), + verify(mOrganizer).onTaskFragmentError(any(), eq(mErrorToken), any(), eq(HIERARCHY_OP_TYPE_REPARENT_CHILDREN), any(SecurityException.class)); } @@ -1092,6 +1100,40 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { .that(mTaskFragment.getBounds()).isEqualTo(task.getBounds()); } + @Test + public void testOnTransactionReady_invokeOnTransactionHandled() { + mController.registerOrganizer(mIOrganizer); + final TaskFragmentTransaction transaction = new TaskFragmentTransaction(); + mOrganizer.onTransactionReady(transaction); + + // Organizer should always trigger #onTransactionHandled when receives #onTransactionReady + verify(mOrganizer).onTransactionHandled(eq(transaction.getTransactionToken()), any()); + verify(mOrganizer, never()).applyTransaction(any()); + } + + @Test + public void testDispatchTransaction_deferTransitionReady() { + mController.registerOrganizer(mIOrganizer); + setupMockParent(mTaskFragment, mTask); + final ArgumentCaptor<IBinder> tokenCaptor = ArgumentCaptor.forClass(IBinder.class); + final ArgumentCaptor<WindowContainerTransaction> wctCaptor = + ArgumentCaptor.forClass(WindowContainerTransaction.class); + doReturn(true).when(mTransitionController).isCollecting(); + + mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); + mController.dispatchPendingEvents(); + + // Defer transition when send TaskFragment transaction during transition collection. + verify(mTransitionController).deferTransitionReady(); + verify(mOrganizer).onTransactionHandled(tokenCaptor.capture(), wctCaptor.capture()); + + mController.onTransactionHandled(mIOrganizer, tokenCaptor.getValue(), wctCaptor.getValue()); + + // Apply the organizer change and continue transition. + verify(mWindowOrganizerController).applyTransaction(wctCaptor.getValue()); + verify(mTransitionController).continueTransitionReady(); + } + /** * Creates a {@link TaskFragment} with the {@link WindowContainerTransaction}. Calls * {@link WindowOrganizerController#applyTransaction} to apply the transaction, diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java index 1e64e469fe7f..f5304d00faab 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java @@ -18,6 +18,13 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS; +import static android.view.InsetsState.ITYPE_BOTTOM_DISPLAY_CUTOUT; +import static android.view.InsetsState.ITYPE_CLIMATE_BAR; +import static android.view.InsetsState.ITYPE_LEFT_DISPLAY_CUTOUT; +import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; +import static android.view.InsetsState.ITYPE_RIGHT_DISPLAY_CUTOUT; +import static android.view.InsetsState.ITYPE_STATUS_BAR; +import static android.view.InsetsState.ITYPE_TOP_DISPLAY_CUTOUT; import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM; import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; @@ -26,12 +33,9 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.doReturn; - import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; @@ -43,6 +47,7 @@ import android.util.DisplayMetrics; import android.view.Display; import android.view.DisplayCutout; import android.view.DisplayInfo; +import android.view.WindowInsets; import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry; @@ -204,7 +209,26 @@ class TestDisplayContent extends DisplayContent { doReturn(true).when(newDisplay).supportsSystemDecorations(); doReturn(true).when(displayPolicy).hasNavigationBar(); doReturn(NAV_BAR_BOTTOM).when(displayPolicy).navigationBarPosition(anyInt()); - doReturn(20).when(displayPolicy).getNavigationBarHeight(anyInt()); + doReturn(Insets.of(0, 0, 0, 20)).when(displayPolicy).getInsets(any(), + eq(WindowInsets.Type.displayCutout() | WindowInsets.Type.navigationBars())); + doReturn(Insets.of(0, 20, 0, 20)).when(displayPolicy).getInsets(any(), + eq(WindowInsets.Type.displayCutout() | WindowInsets.Type.navigationBars() + | WindowInsets.Type.statusBars())); + final int[] nonDecorTypes = new int[]{ + ITYPE_TOP_DISPLAY_CUTOUT, ITYPE_RIGHT_DISPLAY_CUTOUT, + ITYPE_BOTTOM_DISPLAY_CUTOUT, ITYPE_LEFT_DISPLAY_CUTOUT, ITYPE_NAVIGATION_BAR + }; + doReturn(Insets.of(0, 0, 0, 20)).when(displayPolicy).getInsetsWithInternalTypes( + any(), + eq(nonDecorTypes)); + final int[] stableTypes = new int[]{ + ITYPE_TOP_DISPLAY_CUTOUT, ITYPE_RIGHT_DISPLAY_CUTOUT, + ITYPE_BOTTOM_DISPLAY_CUTOUT, ITYPE_LEFT_DISPLAY_CUTOUT, + ITYPE_NAVIGATION_BAR, ITYPE_STATUS_BAR, ITYPE_CLIMATE_BAR + }; + doReturn(Insets.of(0, 20, 0, 20)).when(displayPolicy).getInsetsWithInternalTypes( + any(), + eq(stableTypes)); } else { doReturn(false).when(displayPolicy).hasNavigationBar(); doReturn(false).when(displayPolicy).hasStatusBar(); diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java index 851be9d77348..d2cb7ba5d311 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -245,7 +245,7 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { } @Override - public void userActivity() { + public void userActivity(int displayGroupId, int event) { } @Override 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 0f13fb20a06e..77d920ac3fae 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -56,6 +56,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import android.content.res.Configuration; import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; @@ -72,6 +73,7 @@ import android.window.RemoteTransition; import android.window.TaskFragmentOrganizer; import android.window.TransitionInfo; +import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; import org.junit.Test; @@ -1114,6 +1116,36 @@ public class TransitionTests extends WindowTestsBase { assertTrue(targets.contains(activity)); } + @Test + public void testTransitionVisibleChange() { + registerTestTransitionPlayer(); + final ActivityRecord app = createActivityRecord(mDisplayContent); + final Transition transition = new Transition(TRANSIT_OPEN, 0 /* flags */, + app.mTransitionController, mWm.mSyncEngine); + app.mTransitionController.moveToCollecting(transition, BLASTSyncEngine.METHOD_NONE); + final ArrayList<WindowContainer> freezeCalls = new ArrayList<>(); + transition.setContainerFreezer(new Transition.IContainerFreezer() { + @Override + public boolean freeze(@NonNull WindowContainer wc, @NonNull Rect bounds) { + freezeCalls.add(wc); + return true; + } + + @Override + public void cleanUp(SurfaceControl.Transaction t) { + } + }); + final Task task = app.getTask(); + transition.collect(task); + final Rect bounds = new Rect(task.getBounds()); + Configuration c = new Configuration(task.getRequestedOverrideConfiguration()); + bounds.inset(10, 10); + c.windowConfiguration.setBounds(bounds); + task.onRequestedOverrideConfigurationChanged(c); + assertTrue(freezeCalls.contains(task)); + transition.abort(); + } + private static void makeTaskOrganized(Task... tasks) { final ITaskOrganizer organizer = mock(ITaskOrganizer.class); for (Task t : tasks) { 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 7d4e6fa53a64..24fc93aa644e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -42,8 +42,10 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static com.android.server.wm.ActivityRecord.State.RESUMED; +import static com.android.server.wm.BLASTSyncEngine.METHOD_BLAST; import static com.android.server.wm.WindowContainer.POSITION_TOP; import static com.android.server.wm.WindowContainer.SYNC_STATE_READY; +import static com.android.server.wm.WindowState.BLAST_TIMEOUT_DURATION; import static com.google.common.truth.Truth.assertThat; @@ -1000,7 +1002,7 @@ public class WindowOrganizerTests extends WindowTestsBase { BLASTSyncEngine.TransactionReadyListener transactionListener = mock(BLASTSyncEngine.TransactionReadyListener.class); - int id = bse.startSyncSet(transactionListener); + int id = bse.startSyncSet(transactionListener, BLAST_TIMEOUT_DURATION, "", METHOD_BLAST); bse.addToSyncSet(id, task); bse.setReady(id); bse.onSurfacePlacement(); 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 ee5f36412df2..77e12f40f72e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -323,6 +323,10 @@ class WindowTestsBase extends SystemServiceTestsBase { mNavBarWindow.mAttrs.gravity = Gravity.BOTTOM; mNavBarWindow.mAttrs.paramsForRotation = new WindowManager.LayoutParams[4]; mNavBarWindow.mAttrs.setFitInsetsTypes(0); + mNavBarWindow.mAttrs.layoutInDisplayCutoutMode = + LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + mNavBarWindow.mAttrs.privateFlags |= + WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT; for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) { mNavBarWindow.mAttrs.paramsForRotation[rot] = getNavBarLayoutParamsForRotation(rot); @@ -379,6 +383,9 @@ class WindowTestsBase extends SystemServiceTestsBase { lp.height = height; lp.gravity = gravity; lp.setFitInsetsTypes(0); + lp.privateFlags |= + WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT; + lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; return lp; } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index 25db81fa2667..bde9c3dfd641 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -18,6 +18,8 @@ package com.android.server.voiceinteraction; import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD; import static android.Manifest.permission.RECORD_AUDIO; +import static android.service.attention.AttentionService.PROXIMITY_UNKNOWN; +import static android.service.voice.HotwordDetectedResult.EXTRA_PROXIMITY_METERS; import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_EXTERNAL; import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_MICROPHONE; import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS; @@ -56,6 +58,7 @@ import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPH import android.annotation.NonNull; import android.annotation.Nullable; +import android.attention.AttentionManagerInternal; import android.content.ComponentName; import android.content.ContentCaptureOptions; import android.content.Context; @@ -182,6 +185,12 @@ final class HotwordDetectionConnection { final int mUser; final Context mContext; + @Nullable final AttentionManagerInternal mAttentionManagerInternal; + + final AttentionManagerInternal.ProximityUpdateCallbackInternal mProximityCallbackInternal = + this::setProximityMeters; + + volatile HotwordDetectionServiceIdentity mIdentity; private IMicrophoneHotwordDetectionVoiceInteractionCallback mSoftwareCallback; private Instant mLastRestartInstant; @@ -202,6 +211,8 @@ final class HotwordDetectionConnection { private @NonNull ServiceConnection mRemoteHotwordDetectionService; private IBinder mAudioFlinger; private boolean mDebugHotwordLogging = false; + @GuardedBy("mLock") + private double mProximityMeters = PROXIMITY_UNKNOWN; HotwordDetectionConnection(Object lock, Context context, int voiceInteractionServiceUid, Identity voiceInteractorIdentity, ComponentName serviceName, int userId, @@ -229,6 +240,10 @@ final class HotwordDetectionConnection { mServiceConnectionFactory = new ServiceConnectionFactory(intent, bindInstantServiceAllowed); mRemoteHotwordDetectionService = mServiceConnectionFactory.createLocked(); + mAttentionManagerInternal = LocalServices.getService(AttentionManagerInternal.class); + if (mAttentionManagerInternal != null) { + mAttentionManagerInternal.onStartProximityUpdates(mProximityCallbackInternal); + } mLastRestartInstant = Instant.now(); updateStateAfterProcessStart(options, sharedMemory); @@ -393,6 +408,9 @@ final class HotwordDetectionConnection { if (mAudioFlinger != null) { mAudioFlinger.unlinkToDeath(mAudioServerDeathRecipient, /* flags= */ 0); } + if (mAttentionManagerInternal != null) { + mAttentionManagerInternal.onStopProximityUpdates(mProximityCallbackInternal); + } } void updateStateLocked(PersistableBundle options, SharedMemory sharedMemory) { @@ -460,6 +478,7 @@ final class HotwordDetectionConnection { mSoftwareCallback.onError(); return; } + saveProximityMetersToBundle(result); mSoftwareCallback.onDetected(result, null, null); if (result != null) { Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result) @@ -564,6 +583,7 @@ final class HotwordDetectionConnection { externalCallback.onError(CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION); return; } + saveProximityMetersToBundle(result); externalCallback.onKeyphraseDetected(recognitionEvent, result); if (result != null) { Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result) @@ -643,6 +663,7 @@ final class HotwordDetectionConnection { externalCallback.onError(CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION); return; } + saveProximityMetersToBundle(result); externalCallback.onKeyphraseDetected(recognitionEvent, result); if (result != null) { Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result) @@ -1191,6 +1212,20 @@ final class HotwordDetectionConnection { }); } + private void saveProximityMetersToBundle(HotwordDetectedResult result) { + synchronized (mLock) { + if (result != null && mProximityMeters != PROXIMITY_UNKNOWN) { + result.getExtras().putDouble(EXTRA_PROXIMITY_METERS, mProximityMeters); + } + } + } + + private void setProximityMeters(double proximityMeters) { + synchronized (mLock) { + mProximityMeters = proximityMeters; + } + } + private static void bestEffortClose(Closeable... closeables) { for (Closeable closeable : closeables) { bestEffortClose(closeable); diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java index ebe9b5706bf8..edd6dd3468ef 100644 --- a/tests/testables/src/android/testing/TestableLooper.java +++ b/tests/testables/src/android/testing/TestableLooper.java @@ -30,6 +30,7 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.lang.reflect.Field; import java.util.Map; /** @@ -45,6 +46,9 @@ public class TestableLooper { * catch crashes. */ public static final boolean HOLD_MAIN_THREAD = false; + private static final Field MESSAGE_QUEUE_MESSAGES_FIELD; + private static final Field MESSAGE_NEXT_FIELD; + private static final Field MESSAGE_WHEN_FIELD; private Looper mLooper; private MessageQueue mQueue; @@ -54,6 +58,19 @@ public class TestableLooper { private Runnable mEmptyMessage; private TestLooperManager mQueueWrapper; + static { + try { + MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages"); + MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true); + MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next"); + MESSAGE_NEXT_FIELD.setAccessible(true); + MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when"); + MESSAGE_WHEN_FIELD.setAccessible(true); + } catch (NoSuchFieldException e) { + throw new RuntimeException("Failed to initialize TestableLooper", e); + } + } + public TestableLooper(Looper l) throws Exception { this(acquireLooperManager(l), l); } @@ -119,6 +136,33 @@ public class TestableLooper { while (processQueuedMessages() != 0) ; } + public void moveTimeForward(long milliSeconds) { + try { + Message msg = getMessageLinkedList(); + while (msg != null) { + long updatedWhen = msg.getWhen() - milliSeconds; + if (updatedWhen < 0) { + updatedWhen = 0; + } + MESSAGE_WHEN_FIELD.set(msg, updatedWhen); + msg = (Message) MESSAGE_NEXT_FIELD.get(msg); + } + } catch (IllegalAccessException e) { + throw new RuntimeException("Access failed in TestableLooper: set - Message.when", e); + } + } + + private Message getMessageLinkedList() { + try { + MessageQueue queue = mLooper.getQueue(); + return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue); + } catch (IllegalAccessException e) { + throw new RuntimeException( + "Access failed in TestableLooper: get - MessageQueue.mMessages", + e); + } + } + private int processQueuedMessages() { int count = 0; mEmptyMessage = () -> { }; diff --git a/tests/testables/tests/src/android/testing/TestableLooperTest.java b/tests/testables/tests/src/android/testing/TestableLooperTest.java index 25f6a48871d3..0f491b86626c 100644 --- a/tests/testables/tests/src/android/testing/TestableLooperTest.java +++ b/tests/testables/tests/src/android/testing/TestableLooperTest.java @@ -19,15 +19,19 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.InOrder; import android.os.Handler; import android.os.Looper; @@ -162,7 +166,7 @@ public class TestableLooperTest { @Test public void testCorrectLooperExecution() throws Exception { - boolean[] hasRun = new boolean[] { false }; + boolean[] hasRun = new boolean[]{false}; Runnable r = () -> { assertEquals("Should run on main looper", Looper.getMainLooper(), Looper.myLooper()); hasRun[0] = true; @@ -177,4 +181,63 @@ public class TestableLooperTest { testableLooper.destroy(); } } + + @Test + public void testDelayedDispatchNoTimeMove() { + Handler handler = spy(new Handler(mTestableLooper.getLooper())); + InOrder inOrder = inOrder(handler); + + final Message messageA = handler.obtainMessage(1); + final Message messageB = handler.obtainMessage(2); + + handler.sendMessageDelayed(messageA, 0); + handler.sendMessageDelayed(messageB, 0); + + mTestableLooper.processAllMessages(); + + inOrder.verify(handler).dispatchMessage(messageA); + inOrder.verify(handler).dispatchMessage(messageB); + } + + @Test + public void testDelayedMessageDoesntSend() { + Handler handler = spy(new Handler(mTestableLooper.getLooper())); + InOrder inOrder = inOrder(handler); + + final Message messageA = handler.obtainMessage(1); + final Message messageB = handler.obtainMessage(2); + final Message messageC = handler.obtainMessage(3); + + handler.sendMessageDelayed(messageA, 0); + handler.sendMessageDelayed(messageB, 0); + handler.sendMessageDelayed(messageC, 500); + + mTestableLooper.processAllMessages(); + + inOrder.verify(handler).dispatchMessage(messageA); + inOrder.verify(handler).dispatchMessage(messageB); + verify(handler, never()).dispatchMessage(messageC); + } + + @Test + public void testMessageSendsAfterDelay() { + Handler handler = spy(new Handler(mTestableLooper.getLooper())); + InOrder inOrder = inOrder(handler); + + final Message messageA = handler.obtainMessage(1); + final Message messageB = handler.obtainMessage(2); + final Message messageC = handler.obtainMessage(3); + + handler.sendMessageDelayed(messageA, 0); + handler.sendMessageDelayed(messageB, 0); + handler.sendMessageDelayed(messageC, 500); + + mTestableLooper.moveTimeForward(500); + mTestableLooper.processAllMessages(); + + inOrder.verify(handler).dispatchMessage(messageA); + inOrder.verify(handler).dispatchMessage(messageB); + inOrder.verify(handler).dispatchMessage(messageC); + } + } diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java index 3b201f9d20dd..e4add8098105 100644 --- a/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java +++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java @@ -16,6 +16,9 @@ package android.net.vcn.persistablebundleutils; +import static android.net.vcn.persistablebundleutils.IkeSessionParamsUtils.IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION; +import static android.net.vcn.persistablebundleutils.IkeSessionParamsUtils.IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES; +import static android.net.vcn.persistablebundleutils.IkeSessionParamsUtils.isIkeOptionValid; import static android.system.OsConstants.AF_INET; import static android.system.OsConstants.AF_INET6; import static android.telephony.TelephonyManager.APPTYPE_USIM; @@ -134,15 +137,37 @@ public class IkeSessionParamsUtilsTest { verifyPersistableBundleEncodeDecodeIsLossless(params); } + private static IkeSessionParams.Builder createBuilderMinimumWithEap() throws Exception { + final X509Certificate serverCaCert = createCertFromPemFile("self-signed-ca.pem"); + + final byte[] eapId = "test@android.net".getBytes(StandardCharsets.US_ASCII); + final int subId = 1; + final EapSessionConfig eapConfig = + new EapSessionConfig.Builder() + .setEapIdentity(eapId) + .setEapSimConfig(subId, APPTYPE_USIM) + .setEapAkaConfig(subId, APPTYPE_USIM) + .build(); + return createBuilderMinimum().setAuthEap(serverCaCert, eapConfig); + } + @Test public void testEncodeDecodeParamsWithIkeOptions() throws Exception { - final IkeSessionParams params = - createBuilderMinimum() + final IkeSessionParams.Builder builder = + createBuilderMinimumWithEap() .addIkeOption(IkeSessionParams.IKE_OPTION_ACCEPT_ANY_REMOTE_ID) + .addIkeOption(IkeSessionParams.IKE_OPTION_EAP_ONLY_AUTH) .addIkeOption(IkeSessionParams.IKE_OPTION_MOBIKE) + .addIkeOption(IkeSessionParams.IKE_OPTION_FORCE_PORT_4500) .addIkeOption(IkeSessionParams.IKE_OPTION_INITIAL_CONTACT) - .build(); - verifyPersistableBundleEncodeDecodeIsLossless(params); + .addIkeOption(IkeSessionParams.IKE_OPTION_REKEY_MOBILITY); + if (isIkeOptionValid(IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION)) { + builder.addIkeOption(IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION); + } + if (isIkeOptionValid(IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES)) { + builder.addIkeOption(IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES); + } + verifyPersistableBundleEncodeDecodeIsLossless(builder.build()); } private static InputStream openAssetsFile(String fileName) throws Exception { @@ -176,19 +201,7 @@ public class IkeSessionParamsUtilsTest { @Test public void testEncodeRecodeParamsWithEapAuth() throws Exception { - final X509Certificate serverCaCert = createCertFromPemFile("self-signed-ca.pem"); - - final byte[] eapId = "test@android.net".getBytes(StandardCharsets.US_ASCII); - final int subId = 1; - final EapSessionConfig eapConfig = - new EapSessionConfig.Builder() - .setEapIdentity(eapId) - .setEapSimConfig(subId, APPTYPE_USIM) - .setEapAkaConfig(subId, APPTYPE_USIM) - .build(); - - final IkeSessionParams params = - createBuilderMinimum().setAuthEap(serverCaCert, eapConfig).build(); + final IkeSessionParams params = createBuilderMinimumWithEap().build(); verifyPersistableBundleEncodeDecodeIsLossless(params); } } diff --git a/tools/validatekeymaps/Android.bp b/tools/validatekeymaps/Android.bp index ff24d160b917..25373f9e9e2f 100644 --- a/tools/validatekeymaps/Android.bp +++ b/tools/validatekeymaps/Android.bp @@ -21,6 +21,7 @@ cc_binary_host { cflags: [ "-Wall", "-Werror", + "-Wextra", ], static_libs: [ @@ -31,6 +32,9 @@ cc_binary_host { "liblog", "libui-types", ], + shared_libs: [ + "libvintf", + ], target: { host_linux: { static_libs: [ diff --git a/tools/validatekeymaps/Main.cpp b/tools/validatekeymaps/Main.cpp index 817effd24a2d..0d7d5f949a08 100644 --- a/tools/validatekeymaps/Main.cpp +++ b/tools/validatekeymaps/Main.cpp @@ -141,6 +141,11 @@ static bool validateFile(const char* filename) { } base::Result<std::shared_ptr<KeyLayoutMap>> ret = KeyLayoutMap::load(filename); if (!ret.ok()) { + if (ret.error().message() == "Missing kernel config") { + // It means the layout is valid, but won't be loaded on this device because + // this layout requires a certain kernel config. + return true; + } error("Error %s parsing key layout file.\n\n", ret.error().message().c_str()); return false; } |